]> git.localhorst.tv Git - blank.git/blobdiff - src/ai/ai.cpp
geometry stuff
[blank.git] / src / ai / ai.cpp
index 11add89bb049f079fad1bc62f01c612ce03e685e..4b76856ea9664db0bb5dc17681155bfe501090be 100644 (file)
@@ -4,7 +4,8 @@
 #include "IdleState.hpp"
 #include "RoamState.hpp"
 
-#include "../model/geometry.hpp"
+#include "../geometry/distance.hpp"
+#include "../geometry/rotation.hpp"
 #include "../rand/GaloisLFSR.hpp"
 #include "../world/Entity.hpp"
 #include "../world/World.hpp"
@@ -36,6 +37,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 +77,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().pos.block, 1.0f);
+       }
+
        if (wandering) {
                glm::vec3 displacement(
                        random.SNorm() * wander_disp,
@@ -86,55 +100,55 @@ void AIController::Update(Entity &e, float dt) {
 
        if (e.Moving()) {
                // orient head towards heading
-               glm::vec3 heading(Heading(e.GetState()));
-               float tgt_pitch = std::atan(heading.y / length(glm::vec2(heading.x, heading.z)));
-               float tgt_yaw = std::atan2(-heading.x, -heading.z);
+               glm::vec3 heading(e.Heading());
+               // only half pitch, so we don't crane our neck
+               float tgt_pitch = std::atan(heading.y / length(glm::vec2(heading.x, heading.z))) * 0.5f;
+               // always look straight ahead
+               // maybe look at the pursuit target if there is one
+               float tgt_yaw = 0.0f;
                e.SetHead(tgt_pitch, tgt_yaw);
+               e.OrientBody(dt);
        }
 }
 
 glm::vec3 AIController::ControlForce(const Entity &entity, const EntityState &state) const {
        if (IsHalted()) {
-               return GetHaltForce(state);
+               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(state), entity.MaxControlForce())) {
+               if (MaxOutForce(force, GetFleeForce(entity, state), entity.MaxControlForce())) {
                        return force;
                }
        }
        if (IsSeeking()) {
-               if (MaxOutForce(force, GetSeekForce(state), entity.MaxControlForce())) {
+               if (MaxOutForce(force, GetSeekForce(entity, state), entity.MaxControlForce())) {
                        return force;
                }
        }
        if (IsEvading()) {
-               if (MaxOutForce(force, GetEvadeForce(state), entity.MaxControlForce())) {
+               if (MaxOutForce(force, GetEvadeForce(entity, state), entity.MaxControlForce())) {
                        return force;
                }
        }
        if (IsPursuing()) {
-               if (MaxOutForce(force, GetPursuitForce(state), entity.MaxControlForce())) {
+               if (MaxOutForce(force, GetPursuitForce(entity, state), entity.MaxControlForce())) {
                        return force;
                }
        }
        if (IsWandering()) {
-               if (MaxOutForce(force, GetWanderForce(state), entity.MaxControlForce())) {
+               if (MaxOutForce(force, GetWanderForce(entity, state), entity.MaxControlForce())) {
                        return force;
                }
        }
        return force;
 }
 
-glm::vec3 AIController::Heading(const EntityState &state) noexcept {
-       if (dot(state.velocity, state.velocity) > std::numeric_limits<float>::epsilon()) {
-               return normalize(state.velocity);
-       } else {
-               float cp = std::cos(state.pitch);
-               return glm::vec3(std::cos(state.yaw) * cp, std::sin(state.yaw) * cp, std::sin(state.pitch));
-       }
-}
-
 Player *AIController::ClosestVisiblePlayer(const Entity &e) noexcept {
        Player *target = nullptr;
        float distance = sight_dist;
@@ -145,17 +159,17 @@ Player *AIController::ClosestVisiblePlayer(const Entity &e) noexcept {
 
                // distance test
                const glm::vec3 diff(pe.AbsoluteDifference(e));
-               float dist = length_squared(diff);
+               float dist = length(diff);
                if (dist > distance) continue;
 
                // FOV test, 45° in each direction
-               if (dot(normalize(diff), aim.dir) < sight_angle) {
+               if (dot(diff / dist, aim.dir) < sight_angle) {
                        continue;
                }
 
                // LOS test, assumes all entities are see-through
                WorldCollision col;
-               if (world.Intersection(aim, glm::mat4(1.0f), reference, col) && col.depth * col.depth < dist) {
+               if (world.Intersection(aim, reference, col) && col.depth < dist) {
                        continue;
                }
 
@@ -175,7 +189,7 @@ bool AIController::LineOfSight(const Entity &from, const Entity &to) const noexc
                return false;
        }
        WorldCollision col;
-       if (world.Intersection(aim, glm::mat4(1.0f), reference, col) && col.depth < dist) {
+       if (world.Intersection(aim, reference, col) && col.depth < dist) {
                return false;
        }
        return true;
@@ -228,10 +242,67 @@ void AIController::SetHaltSpeed(float speed) noexcept {
        halt_speed = speed;
 }
 
-glm::vec3 AIController::GetHaltForce(const EntityState &state) const noexcept {
+glm::vec3 AIController::GetHaltForce(const Entity &, const EntityState &state) const noexcept {
        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 = length2(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().pos.block + 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 {
@@ -270,7 +341,7 @@ const Entity &AIController::GetFleeTarget() const noexcept {
        return *flee_target;
 }
 
-glm::vec3 AIController::GetFleeForce(const EntityState &state) const noexcept {
+glm::vec3 AIController::GetFleeForce(const Entity &, const EntityState &state) const noexcept {
        return Flee(state, GetFleeTarget().GetState(), flee_speed, 2.0f);
 }
 
@@ -312,7 +383,7 @@ const Entity &AIController::GetSeekTarget() const noexcept {
        return *seek_target;
 }
 
-glm::vec3 AIController::GetSeekForce(const EntityState &state) const noexcept {
+glm::vec3 AIController::GetSeekForce(const Entity &, const EntityState &state) const noexcept {
        return Seek(state, GetSeekTarget().GetState(), seek_speed, 2.0f);
 }
 
@@ -354,11 +425,11 @@ const Entity &AIController::GetEvadeTarget() const noexcept {
        return *evade_target;
 }
 
-glm::vec3 AIController::GetEvadeForce(const EntityState &state) const noexcept{
+glm::vec3 AIController::GetEvadeForce(const Entity &, const EntityState &state) const noexcept{
        glm::vec3 cur_diff(state.Diff(GetEvadeTarget().GetState()));
        float time_estimate = length(cur_diff) / evade_speed;
        EntityState pred_state(GetEvadeTarget().GetState());
-       pred_state.block_pos += pred_state.velocity * time_estimate;
+       pred_state.pos.block += pred_state.velocity * time_estimate;
        return Flee(state, pred_state, evade_speed, 2.0f);
 }
 
@@ -400,11 +471,11 @@ const Entity &AIController::GetPursuitTarget() const noexcept {
        return *pursuit_target;
 }
 
-glm::vec3 AIController::GetPursuitForce(const EntityState &state) const noexcept {
+glm::vec3 AIController::GetPursuitForce(const Entity &, const EntityState &state) const noexcept {
        glm::vec3 cur_diff(state.Diff(GetPursuitTarget().GetState()));
        float time_estimate = length(cur_diff) / pursuit_speed;
        EntityState pred_state(GetPursuitTarget().GetState());
-       pred_state.block_pos += pred_state.velocity * time_estimate;
+       pred_state.pos.block += pred_state.velocity * time_estimate;
        return Seek(state, pred_state, pursuit_speed, 2.0f);
 }
 
@@ -434,8 +505,8 @@ void AIController::SetWanderParams(
        wander_disp = displacement;
 }
 
-glm::vec3 AIController::GetWanderForce(const EntityState &state) const noexcept {
-       glm::vec3 wander_target(normalize(Heading(state) * wander_dist + wander_pos) * wander_speed);
+glm::vec3 AIController::GetWanderForce(const Entity &e, const EntityState &state) const noexcept {
+       glm::vec3 wander_target(normalize(e.Heading() * wander_dist + wander_pos) * wander_speed);
        return TargetVelocity(wander_target, state, 0.5f);
 }
 
@@ -459,7 +530,7 @@ void ChaseState::Update(AIController &ctrl, Entity &e, float dt) const {
                return;
        }
        // halt if we're close enough, flee if we're too close
-       float dist_sq = length_squared(e.AbsoluteDifference(ctrl.GetPursuitTarget()));
+       float dist_sq = length2(e.AbsoluteDifference(ctrl.GetPursuitTarget()));
        if (dist_sq < 8.0f) {
                ctrl.SetFleeTarget(ctrl.GetPursuitTarget());
                ctrl.SetState(flee);