X-Git-Url: http://git.localhorst.tv/?a=blobdiff_plain;f=src%2Fcreature%2Fcreature.cpp;h=ff3a81c06c050b3a8013f5ddc6d44e06ef63696a;hb=a4d4cc133ff1a8b9ab209b436ee94579930eb948;hp=1cf4a3a5c511aa16e9874fd30b71dce9215c10f7;hpb=7e02b21428efa3ebec14a34d0c1f81e81d362bfc;p=blobs.git diff --git a/src/creature/creature.cpp b/src/creature/creature.cpp index 1cf4a3a..ff3a81c 100644 --- a/src/creature/creature.cpp +++ b/src/creature/creature.cpp @@ -6,10 +6,12 @@ #include "Situation.hpp" #include "Steering.hpp" +#include "AttackGoal.hpp" #include "BlobBackgroundTask.hpp" #include "Goal.hpp" #include "IdleGoal.hpp" #include "../app/Assets.hpp" +#include "../graphics/color.hpp" #include "../math/const.hpp" #include "../ui/string.hpp" #include "../world/Body.hpp" @@ -19,12 +21,16 @@ #include #include +#include #include #include #include #include +using blobs::graphics::hsl2rgb; +using blobs::graphics::rgb2hsl; + namespace blobs { namespace creature { @@ -33,6 +39,7 @@ Composition::Composition(const world::Set &resources) : resources(resources) , components() , total_mass(0.0) +, total_volume(0.0) , state_mass{0.0} { } @@ -64,6 +71,7 @@ void Composition::Add(int res, double amount) { std::sort(components.begin(), components.end(), CompositionCompare); state_mass[resources[res].state] += amount; total_mass += amount; + total_volume += amount / resources[res].density; } bool Composition::Has(int res) const noexcept { @@ -139,6 +147,8 @@ Creature::Creature(world::Simulation &sim) , goals() , situation() , steering(*this) +, heading_target(0.0, 0.0, -1.0) +, heading_manual(false) , perception_range(1.0) , perception_range_squared(1.0) , perception_omni_range(1.0) @@ -177,17 +187,17 @@ void Creature::Ingest(int res, double amount) noexcept { // 30% of solids stays in body AddMass(res, amount * 0.3 * composition.Compatibility(res)); } else { - // 5% of fluids stays in body - AddMass(res, amount * 0.05 * composition.Compatibility(res)); + // 10% of fluids stays in body + AddMass(res, amount * 0.1 * composition.Compatibility(res)); } math::GaloisLFSR &random = sim.Assets().random; if (random.UNorm() < AdaptChance()) { // change color to be slightly more like resource glm::dvec3 color(rgb2hsl(sim.Resources()[res].base_color)); // solids affect base color, others highlight - double p = sim.Resources()[res].state == world::Resource::SOLID ? 0 : 1; - double q = random.UInt(3); // hue, sat, or val - double r = random.UInt(2); // mean or deviation + int p = sim.Resources()[res].state == world::Resource::SOLID ? 0 : 1; + int q = random.UInt(3); // hue, sat, or val + int r = random.UInt(2); // mean or deviation math::Distribution *d = nullptr; double ref = 0.0; if (p == 0) { @@ -221,16 +231,30 @@ void Creature::Ingest(int res, double amount) noexcept { } else if (diff > 0.5) { diff -= 1.0; } - // move ±15% of distance - d->Mean(std::fmod(d->Mean() + diff * random.SNorm() * 0.15, 1.0)); + // move 0-15% of distance + d->Mean(std::fmod(d->Mean() + diff * random.UNorm() * 0.15, 1.0)); } else { - d->Mean(glm::clamp(d->Mean() + diff * random.SNorm() * 0.15, 0.0, 1.0)); + d->Mean(glm::clamp(d->Mean() + diff * random.UNorm() * 0.15, 0.0, 1.0)); } } else { // scale by ±15%, enforce bounds d->StandardDeviation(glm::clamp(d->StandardDeviation() * (1.0 + random.SNorm() * 0.15), 0.0001, 0.5)); } } + if (sim.Resources()[res].state == world::Resource::LIQUID && random.UNorm() < AdaptChance()) { + // change texture randomly + // TODO: make change depending on surroundings and/or resource + int p = random.UInt(2); // back or side + int q = random.UInt(2); // mean or deviation + math::Distribution &d = p ? genome.skin_side : genome.skin_back; + if (q == 0) { + // move ± one standard deviation + d.Mean(d.Mean() + (random.SNorm() * d.StandardDeviation())); + } else { + // scale by ±10%, enforce bounds + d.StandardDeviation(glm::clamp(d.StandardDeviation() * (1.0 + random.SNorm() * 0.1), 0.0001, 0.5)); + } + } } void Creature::DoWork(double amount) noexcept { @@ -427,6 +451,14 @@ void Creature::AddGoal(std::unique_ptr &&g) { goals.emplace_back(std::move(g)); } +void Creature::SetBackgroundTask(std::unique_ptr &&g) { + bg_task = std::move(g); +} + +Goal &Creature::BackgroundTask() { + return *bg_task; +} + namespace { bool GoalCompare(const std::unique_ptr &a, const std::unique_ptr &b) { @@ -467,23 +499,27 @@ void Creature::TickState(double dt) { state.pos += f.vel * dt; state.vel += f.acc * dt; situation.EnforceConstraints(state); - if (glm::length2(state.vel) > 0.000001) { - glm::dvec3 nvel(glm::normalize(state.vel)); - double ang = glm::angle(nvel, state.dir); - double turn_rate = PI * 0.75 * dt; - if (ang < turn_rate) { - state.dir = glm::normalize(state.vel); - } else if (std::abs(ang - PI) < 0.001) { - state.dir = glm::rotate(state.dir, turn_rate, situation.GetPlanet().NormalAt(state.pos)); - } else { - state.dir = glm::rotate(state.dir, turn_rate, glm::normalize(glm::cross(state.dir, nvel))); + + if (!heading_manual && glm::length2(state.vel) > 0.000001) { + const glm::dvec3 normal(situation.GetPlanet().NormalAt(state.pos)); + const glm::dvec3 tangent(state.vel - (normal * glm::dot(state.vel, normal))); + if (glm::length2(tangent) > 0.000001) { + heading_target = glm::normalize(tangent); } } + double ang = glm::angle(heading_target, state.dir); + double turn_rate = PI * 0.75 * dt; + if (ang < turn_rate) { + state.dir = heading_target; + heading_manual = false; + } else { + state.dir = glm::rotate(state.dir, turn_rate, glm::normalize(glm::cross(state.dir, heading_target))); + } + situation.SetState(state); // work is force times distance - // exclude gravity for no apparent reason - // actually, this should solely be based on steering force - DoWork(glm::length(f.acc - situation.GetPlanet().GravityAt(state.pos)) * Mass() * glm::length(f.vel) * dt); + // keep 10% of gravity as a kind of background burn + DoWork(glm::length(f.acc - (0.9 * situation.GetPlanet().GravityAt(state.pos))) * Mass() * glm::length(f.vel) * dt); } Situation::Derivative Creature::Step(const Situation::Derivative &ds, double dt) const noexcept { @@ -591,6 +627,10 @@ glm::dmat4 Creature::CollisionTransform() const noexcept { * glm::translate(glm::dvec3(0.0, half_size, 0.0)); } +void Creature::OnCollide(Creature &other) { + memory.TrackCollision(other); +} + glm::dmat4 Creature::LocalTransform() noexcept { const double half_size = size * 0.5; return CollisionTransform() @@ -610,7 +650,16 @@ void Creature::BuildVAO() { vao->ReserveAttributes(6 * 4, GL_STATIC_DRAW); { auto attrib = vao->MapAttributes(GL_WRITE_ONLY); - const float offset = 1.0f; + constexpr float offset = 1.0f; + constexpr float max_tex = 5.999f; + const float tex[6] = { + 0.0f, // face + float(std::floor(skin_side * max_tex)), // left + float(std::floor(skin_back * max_tex)), // top + float(std::floor(skin_back * max_tex)), // back + float(std::floor(skin_side * max_tex)), // right + 0.0f, // bottom + }; for (int surface = 0; surface < 6; ++surface) { const float tex_u_begin = surface < 3 ? 1.0f : 0.0f; const float tex_u_end = surface < 3 ? 0.0f : 1.0f; @@ -623,7 +672,7 @@ void Creature::BuildVAO() { attrib[4 * surface + 0].normal[(surface + 2) % 3] = surface < 3 ? 1.0f : -1.0f; attrib[4 * surface + 0].texture.x = tex_u_begin; attrib[4 * surface + 0].texture.y = 1.0f; - attrib[4 * surface + 0].texture.z = surface; + attrib[4 * surface + 0].texture.z = tex[surface]; attrib[4 * surface + 1].position[(surface + 0) % 3] = -offset; attrib[4 * surface + 1].position[(surface + 1) % 3] = offset; @@ -633,7 +682,7 @@ void Creature::BuildVAO() { attrib[4 * surface + 1].normal[(surface + 2) % 3] = surface < 3 ? 1.0f : -1.0f; attrib[4 * surface + 1].texture.x = tex_u_end; attrib[4 * surface + 1].texture.y = 1.0f; - attrib[4 * surface + 1].texture.z = surface; + attrib[4 * surface + 1].texture.z = tex[surface]; attrib[4 * surface + 2].position[(surface + 0) % 3] = offset; attrib[4 * surface + 2].position[(surface + 1) % 3] = -offset; @@ -643,7 +692,7 @@ void Creature::BuildVAO() { attrib[4 * surface + 2].normal[(surface + 2) % 3] = surface < 3 ? 1.0f : -1.0f; attrib[4 * surface + 2].texture.x = tex_u_begin; attrib[4 * surface + 2].texture.y = 0.0f; - attrib[4 * surface + 2].texture.z = surface; + attrib[4 * surface + 2].texture.z = tex[surface]; attrib[4 * surface + 3].position[(surface + 0) % 3] = offset; attrib[4 * surface + 3].position[(surface + 1) % 3] = offset; @@ -653,7 +702,7 @@ void Creature::BuildVAO() { attrib[4 * surface + 3].normal[(surface + 2) % 3] = surface < 3 ? 1.0f : -1.0f; attrib[4 * surface + 3].texture.x = tex_u_end; attrib[4 * surface + 3].texture.y = 0.0f; - attrib[4 * surface + 3].texture.z = surface; + attrib[4 * surface + 3].texture.z = tex[surface]; } } vao->BindElements(); @@ -695,6 +744,7 @@ void Spawn(Creature &c, world::Planet &p) { p.AddCreature(&c); c.GetSituation().SetPlanetSurface(p, glm::dvec3(0.0, 0.0, p.Radius())); c.GetSituation().Heading(glm::dvec3(1.0, 0.0, 0.0)); + c.HeadingTarget(glm::dvec3(1.0, 0.0, 0.0)); // probe surrounding area for common resources int start = p.SideLength() / 2 - 2; @@ -737,7 +787,7 @@ void Spawn(Creature &c, world::Planet &p) { double color_divisor = 0.0; if (p.HasAtmosphere()) { - c.AddMass(p.Atmosphere(), 0.01); + c.AddMass(p.Atmosphere(), 0.005); color_avg += c.GetSimulation().Resources()[p.Atmosphere()].base_color * 0.1; color_divisor += 0.1; } @@ -764,6 +814,9 @@ void Spawn(Creature &c, world::Planet &p) { genome.highlight_saturation = { 1.0 - hsl.y, 0.01 }; genome.highlight_lightness = { 1.0 - hsl.z, 0.01 }; + genome.skin_side = { 0.5, 0.01 }; + genome.skin_back = { 0.5, 0.01 }; + genome.Configure(c); } @@ -795,6 +848,8 @@ void Genome::Configure(Creature &c) const { ); c.BaseColor(hsl2rgb(base_color)); c.HighlightColor(hsl2rgb(highlight_color)); + c.BackSkin(glm::clamp(skin_back.FakeNormal(random.SNorm()), 0.0, 1.0)); + c.SideSkin(glm::clamp(skin_side.FakeNormal(random.SNorm()), 0.0, 1.0)); c.SetBackgroundTask(std::unique_ptr(new BlobBackgroundTask(c))); c.AddGoal(std::unique_ptr(new IdleGoal(c))); } @@ -807,13 +862,16 @@ void Split(Creature &c) { a->Name(c.GetSimulation().Assets().name.Sequential()); c.GetGenome().Configure(*a); for (const auto &cmp : c.GetComposition()) { - a->AddMass(cmp.resource, cmp.value * 0.5); + // require at least 0.1% + if (cmp.value > 0.002) { + a->AddMass(cmp.resource, cmp.value * 0.5); + } } s.GetPlanet().AddCreature(a); // TODO: duplicate situation somehow a->GetSituation().SetPlanetSurface( s.GetPlanet(), - s.Position() + glm::rotate(s.Heading() * a->Size() * 0.6, PI * 0.5, s.SurfaceNormal())); + s.Position() + glm::rotate(s.Heading() * a->Size() * 0.86, PI * 0.5, s.SurfaceNormal())); a->BuildVAO(); c.GetSimulation().Log() << a->Name() << " was born" << std::endl; @@ -822,12 +880,15 @@ void Split(Creature &c) { b->Name(c.GetSimulation().Assets().name.Sequential()); c.GetGenome().Configure(*b); for (const auto &cmp : c.GetComposition()) { - b->AddMass(cmp.resource, cmp.value * 0.5); + // require at least 0.1% + if (cmp.value > 0.002) { + b->AddMass(cmp.resource, cmp.value * 0.5); + } } s.GetPlanet().AddCreature(b); b->GetSituation().SetPlanetSurface( s.GetPlanet(), - s.Position() + glm::rotate(s.Heading() * b->Size() * 0.6, PI * -0.5, s.SurfaceNormal())); + s.Position() + glm::rotate(s.Heading() * b->Size() * 0.86, PI * -0.5, s.SurfaceNormal())); b->BuildVAO(); c.GetSimulation().Log() << b->Name() << " was born" << std::endl; @@ -844,6 +905,7 @@ Memory::~Memory() { void Memory::Erase() { known_types.clear(); + known_creatures.clear(); } bool Memory::RememberLocation(const Composition &accept, glm::dvec3 &pos) const noexcept { @@ -877,6 +939,21 @@ bool Memory::RememberLocation(const Composition &accept, glm::dvec3 &pos) const } } +void Memory::TrackCollision(Creature &other) { + // TODO: find out whose fault it was + // TODO: source values from personality + Profile &p = known_creatures[&other]; + p.annoyance += 0.1; + const double annoy_fact = p.annoyance / (p.annoyance + 1.0); + if (c.GetSimulation().Assets().random.UNorm() > annoy_fact * 0.1 * (1.0 - c.GetStats().Damage().value)) { + AttackGoal *g = new AttackGoal(c, other); + g->SetDamageTarget(annoy_fact); + g->Urgency(annoy_fact); + c.AddGoal(std::unique_ptr(g)); + p.annoyance *= 0.5; + } +} + void Memory::Tick(double dt) { Situation &s = c.GetSituation(); if (s.OnSurface()) {