]> git.localhorst.tv Git - blank.git/commitdiff
make AI entities avoid world collisions
authorDaniel Karbach <daniel.karbach@localhorst.tv>
Thu, 29 Oct 2015 07:56:17 +0000 (08:56 +0100)
committerDaniel Karbach <daniel.karbach@localhorst.tv>
Thu, 29 Oct 2015 07:56:17 +0000 (08:56 +0100)
src/ai/AIController.hpp
src/ai/ai.cpp
src/model/geometry.cpp
src/model/geometry.hpp
src/world/Entity.hpp
src/world/World.hpp
src/world/world.cpp

index cda3e8a08086465f694b020f6615c4c58ebd1271..5cbfd0ebc88455a43c434619ac3bd1c52473d0d4 100644 (file)
@@ -2,6 +2,7 @@
 #define BLANK_AI_AICONTROLLER_HPP_
 
 #include "../app/IntervalTimer.hpp"
+#include "../model/geometry.hpp"
 #include "../world/EntityController.hpp"
 
 #include <glm/glm.hpp>
@@ -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;
index ff39127151332dc544f23ae6784f719ec7d6bd6c..fc6abfc4b9e59fa0133ad1bb66c4776e4595b049 100644 (file)
@@ -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<WorldCollision> 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<float>::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 {
index e0b55826de35ed58bb05ca441a260a5877f45e86..a4ac5006d55a019544f122a035af5d98d21cf308 100644 (file)
@@ -1,10 +1,41 @@
 #include "geometry.hpp"
 
 #include <limits>
+#include <glm/gtx/matrix_cross_product.hpp>
+#include <glm/gtx/optimum_pow.hpp>
+#include <glm/gtx/transform.hpp>
 
 
 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<float>::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,
index 248ced30e0b77941e60b77d7ee8138f8ddf1fb75..5065124dad69a9974738a12035b8d4e1c18f4182 100644 (file)
@@ -57,6 +57,9 @@ T manhattan_radius(const glm::tvec3<T> &v) noexcept {
 }
 
 
+glm::mat3 find_rotation(const glm::vec3 &a, const glm::vec3 &b) noexcept;
+
+
 struct AABB {
        glm::vec3 min;
        glm::vec3 max;
index efd9224a9f86f7f7e25fbc0dd42bb7d116ea56bf..69fb0eb8cf7e7154590ddf52f88146428de75bd0 100644 (file)
@@ -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<float>::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
index ab563a7fa9e1e964bb49b10dc16762f2b623a99e..e29474e99f47af5e164943a4965fd896cbc2ee17 100644 (file)
@@ -62,11 +62,16 @@ public:
                EntityCollision &);
 
        /// check if given entity intersects with the world
-//     bool Intersection(const Entity &e, std::vector<WorldCollision> &col) {
-//             return Intersection(e, e.GetState(), col);
-//     }
        bool Intersection(const Entity &e, const EntityState &, std::vector<WorldCollision> &);
 
+       /// 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<WorldCollision> &);
+
        const BlockTypeRegistry &BlockTypes() noexcept { return block_type; }
        ChunkStore &Chunks() noexcept { return chunks; }
 
index 4f73243c5d4ca61b87b0bd7ba92abca74c834287..ffd30c00e48d4393a9ded247cafb219194cf8050 100644 (file)
@@ -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<float>::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<Worl
        AABB box = e.Bounds();
        Chunk::Pos reference = s.chunk_pos;
        glm::mat4 M = s.Transform(reference);
+       return Intersection(box, M, reference, col);
+}
+
+bool World::Intersection(
+       const AABB &box,
+       const glm::mat4 &M,
+       const glm::ivec3 &reference,
+       std::vector<WorldCollision> &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<Worl
        return any;
 }
 
-
 void World::Update(int dt) {
        float fdt(dt * 0.001f);
        for (Entity &entity : entities) {