From: Daniel Karbach Date: Thu, 29 Oct 2015 07:56:17 +0000 (+0100) Subject: make AI entities avoid world collisions X-Git-Url: http://git.localhorst.tv/?a=commitdiff_plain;h=170c0ff60b9679c954a9e74d5300c9929899b2bd;p=blank.git make AI entities avoid world collisions --- diff --git a/src/ai/AIController.hpp b/src/ai/AIController.hpp index cda3e8a..5cbfd0e 100644 --- a/src/ai/AIController.hpp +++ b/src/ai/AIController.hpp @@ -2,6 +2,7 @@ #define BLANK_AI_AICONTROLLER_HPP_ #include "../app/IntervalTimer.hpp" +#include "../model/geometry.hpp" #include "../world/EntityController.hpp" #include @@ -54,6 +55,11 @@ public: void SetHaltSpeed(float) noexcept; glm::vec3 GetHaltForce(const Entity &, const EntityState &) const noexcept; + void StartAvoidingObstacles() noexcept; + void StopAvoidingObstacles() noexcept; + bool IsAvoidingObstacles() const noexcept; + glm::vec3 GetObstacleAvoidanceForce(const Entity &, const EntityState &) const noexcept; + void StartFleeing() noexcept; void StopFleeing() noexcept; bool IsFleeing() const noexcept; @@ -122,6 +128,10 @@ private: bool halted; float halt_speed; + bool avoid_obstacles; + AABB obstacle_box; + glm::mat4 obstacle_transform; + bool fleeing; Entity *flee_target; float flee_speed; diff --git a/src/ai/ai.cpp b/src/ai/ai.cpp index ff39127..fc6abfc 100644 --- a/src/ai/ai.cpp +++ b/src/ai/ai.cpp @@ -36,6 +36,9 @@ AIController::AIController(World &world, GaloisLFSR &rand) , decision_timer(1.0f) , halted(false) , halt_speed(1.0f) +, avoid_obstacles(true) +, obstacle_box{ glm::vec3(0.0f), glm::vec3(0.0f) } +, obstacle_transform(1.0f) , fleeing(false) , flee_target(nullptr) , flee_speed(5.0f) @@ -73,6 +76,16 @@ void AIController::Update(Entity &e, float dt) { decision_timer.Update(dt); state->Update(*this, e, dt); + if (avoid_obstacles && e.Moving()) { + obstacle_box = e.Bounds(); + obstacle_box.min.z = -e.Speed(); + obstacle_box.max.x = 0.0f; + // our box is oriented for -Z velocity + obstacle_transform = glm::mat4(find_rotation(glm::vec3(0.0f, 0.0f, -1.0f), e.Heading())); + // and positioned relative to the entity's chunk + obstacle_transform[3] = glm::vec4(e.GetState().block_pos, 1.0f); + } + if (wandering) { glm::vec3 displacement( random.SNorm() * wander_disp, @@ -98,6 +111,11 @@ glm::vec3 AIController::ControlForce(const Entity &entity, const EntityState &st return GetHaltForce(entity, state); } glm::vec3 force(0.0f); + if (IsAvoidingObstacles() && entity.Moving()) { + if (MaxOutForce(force, GetObstacleAvoidanceForce(entity, state), entity.MaxControlForce())) { + return force; + } + } if (IsFleeing()) { if (MaxOutForce(force, GetFleeForce(entity, state), entity.MaxControlForce())) { return force; @@ -223,6 +241,63 @@ glm::vec3 AIController::GetHaltForce(const Entity &, const EntityState &state) c return Halt(state, halt_speed); } +// obstacle avoidance + +void AIController::StartAvoidingObstacles() noexcept { + avoid_obstacles = true; +} + +void AIController::StopAvoidingObstacles() noexcept { + avoid_obstacles = false; +} + +bool AIController::IsAvoidingObstacles() const noexcept { + return avoid_obstacles; +} + +namespace { + +std::vector col; + +} + +glm::vec3 AIController::GetObstacleAvoidanceForce(const Entity &e, const EntityState &state) const noexcept { + if (!e.Moving()) { + return glm::vec3(0.0f); + } + col.clear(); + if (!world.Intersection(obstacle_box, obstacle_transform, e.ChunkCoords(), col)) { + return glm::vec3(0.0f); + } + // find the nearest block + WorldCollision *nearest = nullptr; + glm::vec3 difference(0.0f); + float distance = std::numeric_limits::infinity(); + for (WorldCollision &c : col) { + // diff points from block to state + glm::vec3 diff = state.RelativePosition(c.ChunkPos()) - c.BlockCoords(); + float dist = length_squared(diff); + if (dist < distance) { + nearest = &c; + difference = diff; + distance = dist; + } + } + if (!nearest) { + // intersection test lied to us + return glm::vec3(0.0f); + } + // and steer away from it + // to_go is the distance between our position and the + // point on the "velocity ray" closest to obstacle + float to_go = dot(difference, e.Heading()); + // point is our future position if we keep going our way + glm::vec3 point(e.GetState().block_pos + e.Heading() * to_go); + // now steer away in the direction of (point - block) + // with a magniture proportional to speed/distance + return normalize(point - nearest->BlockCoords()) * (e.Speed() / std::sqrt(distance)); +} + // flee void AIController::StartFleeing() noexcept { diff --git a/src/model/geometry.cpp b/src/model/geometry.cpp index e0b5582..a4ac500 100644 --- a/src/model/geometry.cpp +++ b/src/model/geometry.cpp @@ -1,10 +1,41 @@ #include "geometry.hpp" #include +#include +#include +#include namespace blank { +glm::mat3 find_rotation(const glm::vec3 &a, const glm::vec3 &b) noexcept { + glm::vec3 v(cross(a, b)); + if (iszero(v)) { + // a and b are parallel + if (iszero(a - b)) { + // a and b are identical + return glm::mat3(1.0f); + } else { + // a and b are opposite + // create arbitrary unit vector perpendicular to a and + // rotate 180° around it + glm::vec3 arb(a); + if (std::abs(a.x - 1.0f) > std::numeric_limits::epsilon()) { + arb.x += 1.0f; + } else { + arb.y += 1.0f; + } + glm::vec3 axis(normalize(cross(a, arb))); + return glm::mat3(glm::rotate(PI, axis)); + } + } + float mv = length_squared(v); + float c = dot(a, b); + float f = (1 - c) / mv; + glm::mat3 vx(matrixCross3(v)); + return glm::mat3(1.0f) + vx + (pow2(vx) * f); +} + bool Intersection( const Ray &ray, const AABB &aabb, diff --git a/src/model/geometry.hpp b/src/model/geometry.hpp index 248ced3..5065124 100644 --- a/src/model/geometry.hpp +++ b/src/model/geometry.hpp @@ -57,6 +57,9 @@ T manhattan_radius(const glm::tvec3 &v) noexcept { } +glm::mat3 find_rotation(const glm::vec3 &a, const glm::vec3 &b) noexcept; + + struct AABB { glm::vec3 min; glm::vec3 max; diff --git a/src/world/Entity.hpp b/src/world/Entity.hpp index efd9224..69fb0eb 100644 --- a/src/world/Entity.hpp +++ b/src/world/Entity.hpp @@ -63,10 +63,6 @@ public: const glm::vec3 &Velocity() const noexcept { return state.velocity; } - bool Moving() const noexcept { - return dot(Velocity(), Velocity()) > std::numeric_limits::epsilon(); - } - const glm::vec3 &Position() const noexcept { return state.block_pos; } void Position(const glm::ivec3 &, const glm::vec3 &) noexcept; void Position(const glm::vec3 &) noexcept; @@ -96,6 +92,11 @@ public: /// get a ray in entity's face direction originating from center of vision Ray Aim(const Chunk::Pos &chunk_offset) const noexcept; + /// true if this entity's position will change (significantly) the next update + bool Moving() const noexcept { return speed > 0.0f; } + /// magnitude of velocity + float Speed() const noexcept { return speed; } + /// normalized velocity or heading if standing still const glm::vec3 &Heading() const noexcept { return heading; } void SetState(const EntityState &s) noexcept { state = s; UpdateModel(); } @@ -133,7 +134,7 @@ private: /// if this entity has no model, the eyes are assumed to /// be at local origin and oriented towards -Z glm::mat4 view_local; - /// normalized velocity or heading if standing still + float speed; glm::vec3 heading; // TODO: I'd prefer a drag solution diff --git a/src/world/World.hpp b/src/world/World.hpp index ab563a7..e29474e 100644 --- a/src/world/World.hpp +++ b/src/world/World.hpp @@ -62,11 +62,16 @@ public: EntityCollision &); /// check if given entity intersects with the world -// bool Intersection(const Entity &e, std::vector &col) { -// return Intersection(e, e.GetState(), col); -// } bool Intersection(const Entity &e, const EntityState &, std::vector &); + /// check if given box (M * AABB) intersects with the world + /// M is assumed to be calculated in reference to given chunk coords + bool Intersection( + const AABB &box, + const glm::mat4 &M, + const glm::ivec3 &reference, + std::vector &); + const BlockTypeRegistry &BlockTypes() noexcept { return block_type; } ChunkStore &Chunks() noexcept { return chunks; } diff --git a/src/world/world.cpp b/src/world/world.cpp index 4f73243..ffd30c0 100644 --- a/src/world/world.cpp +++ b/src/world/world.cpp @@ -157,9 +157,11 @@ void Entity::UpdateView() noexcept { } void Entity::UpdateHeading() noexcept { - if (Moving()) { - heading = normalize(Velocity()); + speed = length(Velocity()); + if (speed > std::numeric_limits::epsilon()) { + heading = Velocity() / speed; } else { + speed = 0.0f; // use -Z (forward axis) of local view transform heading = -glm::vec3(view_local[2]); } @@ -474,6 +476,15 @@ bool World::Intersection(const Entity &e, const EntityState &s, std::vector &col +) { bool any = false; for (Chunk &cur_chunk : chunks) { if (manhattan_radius(cur_chunk.Position() - reference) > 1) { @@ -488,7 +499,6 @@ bool World::Intersection(const Entity &e, const EntityState &s, std::vector