1 #include "AIController.hpp"
2 #include "ChaseState.hpp"
3 #include "FleeState.hpp"
4 #include "IdleState.hpp"
5 #include "RoamState.hpp"
7 #include "../model/geometry.hpp"
8 #include "../rand/GaloisLFSR.hpp"
9 #include "../world/Entity.hpp"
10 #include "../world/World.hpp"
11 #include "../world/WorldCollision.hpp"
15 #include <glm/glm.hpp>
29 AIController::AIController(World &world, GaloisLFSR &rand)
36 , decision_timer(1.0f)
39 , avoid_obstacles(true)
40 , obstacle_box{ glm::vec3(0.0f), glm::vec3(0.0f) }
41 , obstacle_transform(1.0f)
43 , flee_target(nullptr)
46 , seek_target(nullptr)
49 , evade_target(nullptr)
52 , pursuit_target(nullptr)
55 , wander_pos(1.0f, 0.0f, 0.0f)
64 AIController::~AIController() {
68 void AIController::SetState(const AIState &s) {
74 void AIController::Update(Entity &e, float dt) {
75 think_timer.Update(dt);
76 decision_timer.Update(dt);
77 state->Update(*this, e, dt);
79 if (avoid_obstacles && e.Moving()) {
80 obstacle_box = e.Bounds();
81 obstacle_box.min.z = -e.Speed();
82 obstacle_box.max.x = 0.0f;
83 // our box is oriented for -Z velocity
84 obstacle_transform = glm::mat4(find_rotation(glm::vec3(0.0f, 0.0f, -1.0f), e.Heading()));
85 // and positioned relative to the entity's chunk
86 obstacle_transform[3] = glm::vec4(e.GetState().block_pos, 1.0f);
90 glm::vec3 displacement(
91 random.SNorm() * wander_disp,
92 random.SNorm() * wander_disp,
93 random.SNorm() * wander_disp
95 if (!iszero(displacement)) {
96 wander_pos = normalize(wander_pos + displacement * dt) * wander_radius;
101 // orient head towards heading
102 glm::vec3 heading(e.Heading());
103 float tgt_pitch = std::atan(heading.y / length(glm::vec2(heading.x, heading.z)));
104 float tgt_yaw = std::atan2(-heading.x, -heading.z);
105 e.SetHead(tgt_pitch, tgt_yaw);
109 glm::vec3 AIController::ControlForce(const Entity &entity, const EntityState &state) const {
111 return GetHaltForce(entity, state);
113 glm::vec3 force(0.0f);
114 if (IsAvoidingObstacles() && entity.Moving()) {
115 if (MaxOutForce(force, GetObstacleAvoidanceForce(entity, state), entity.MaxControlForce())) {
120 if (MaxOutForce(force, GetFleeForce(entity, state), entity.MaxControlForce())) {
125 if (MaxOutForce(force, GetSeekForce(entity, state), entity.MaxControlForce())) {
130 if (MaxOutForce(force, GetEvadeForce(entity, state), entity.MaxControlForce())) {
135 if (MaxOutForce(force, GetPursuitForce(entity, state), entity.MaxControlForce())) {
140 if (MaxOutForce(force, GetWanderForce(entity, state), entity.MaxControlForce())) {
147 Player *AIController::ClosestVisiblePlayer(const Entity &e) noexcept {
148 Player *target = nullptr;
149 float distance = sight_dist;
150 const glm::ivec3 &reference(e.ChunkCoords());
151 Ray aim(e.Aim(reference));
152 for (Player &p : world.Players()) {
153 const Entity &pe = p.GetEntity();
156 const glm::vec3 diff(pe.AbsoluteDifference(e));
157 float dist = length(diff);
158 if (dist > distance) continue;
160 // FOV test, 45° in each direction
161 if (dot(diff / dist, aim.dir) < sight_angle) {
165 // LOS test, assumes all entities are see-through
167 if (world.Intersection(aim, glm::mat4(1.0f), reference, col) && col.depth < dist) {
178 bool AIController::LineOfSight(const Entity &from, const Entity &to) const noexcept {
179 const glm::ivec3 &reference(from.ChunkCoords());
180 Ray aim(from.Aim(reference));
181 const glm::vec3 diff(to.AbsoluteDifference(from));
182 float dist = length(diff);
183 if (dist > sight_dist || dot(diff / dist, aim.dir) < sight_angle) {
187 if (world.Intersection(aim, glm::mat4(1.0f), reference, col) && col.depth < dist) {
195 bool AIController::MayThink() const noexcept {
196 return think_timer.Hit();
199 void AIController::SetThinkInterval(float i) noexcept {
200 think_timer = FineTimer(i);
206 void AIController::CueDecision(
210 decision_timer = FineTimer(minimum + variance * random.SNorm());
211 decision_timer.Start();
214 bool AIController::DecisionDue() const noexcept {
215 return decision_timer.HitOnce();
218 unsigned int AIController::Decide(unsigned int num_choices) noexcept {
219 return random.Next<unsigned int>() % num_choices;
224 void AIController::EnterHalt() noexcept {
228 void AIController::ExitHalt() noexcept {
232 bool AIController::IsHalted() const noexcept {
236 void AIController::SetHaltSpeed(float speed) noexcept {
240 glm::vec3 AIController::GetHaltForce(const Entity &, const EntityState &state) const noexcept {
241 return Halt(state, halt_speed);
244 // obstacle avoidance
246 void AIController::StartAvoidingObstacles() noexcept {
247 avoid_obstacles = true;
250 void AIController::StopAvoidingObstacles() noexcept {
251 avoid_obstacles = false;
254 bool AIController::IsAvoidingObstacles() const noexcept {
255 return avoid_obstacles;
260 std::vector<WorldCollision> col;
264 glm::vec3 AIController::GetObstacleAvoidanceForce(const Entity &e, const EntityState &state) const noexcept {
266 return glm::vec3(0.0f);
269 if (!world.Intersection(obstacle_box, obstacle_transform, e.ChunkCoords(), col)) {
270 return glm::vec3(0.0f);
272 // find the nearest block
273 WorldCollision *nearest = nullptr;
274 glm::vec3 difference(0.0f);
275 float distance = std::numeric_limits<float>::infinity();
276 for (WorldCollision &c : col) {
277 // diff points from block to state
278 glm::vec3 diff = state.RelativePosition(c.ChunkPos()) - c.BlockCoords();
279 float dist = length_squared(diff);
280 if (dist < distance) {
287 // intersection test lied to us
288 return glm::vec3(0.0f);
290 // and steer away from it
291 // to_go is the distance between our position and the
292 // point on the "velocity ray" closest to obstacle
293 float to_go = dot(difference, e.Heading());
294 // point is our future position if we keep going our way
295 glm::vec3 point(e.GetState().block_pos + e.Heading() * to_go);
296 // now steer away in the direction of (point - block)
297 // with a magniture proportional to speed/distance
298 return normalize(point - nearest->BlockCoords()) * (e.Speed() / std::sqrt(distance));
303 void AIController::StartFleeing() noexcept {
307 void AIController::StopFleeing() noexcept {
310 flee_target->UnRef();
311 flee_target = nullptr;
315 bool AIController::IsFleeing() const noexcept {
316 return fleeing && flee_target;
319 void AIController::SetFleeTarget(Entity &e) noexcept {
321 flee_target->UnRef();
327 void AIController::SetFleeSpeed(float speed) noexcept {
331 Entity &AIController::GetFleeTarget() noexcept {
335 const Entity &AIController::GetFleeTarget() const noexcept {
339 glm::vec3 AIController::GetFleeForce(const Entity &, const EntityState &state) const noexcept {
340 return Flee(state, GetFleeTarget().GetState(), flee_speed, 2.0f);
345 void AIController::StartSeeking() noexcept {
349 void AIController::StopSeeking() noexcept {
352 seek_target->UnRef();
353 seek_target = nullptr;
357 bool AIController::IsSeeking() const noexcept {
358 return seeking && seek_target;
361 void AIController::SetSeekTarget(Entity &e) noexcept {
363 seek_target->UnRef();
369 void AIController::SetSeekSpeed(float speed) noexcept {
373 Entity &AIController::GetSeekTarget() noexcept {
377 const Entity &AIController::GetSeekTarget() const noexcept {
381 glm::vec3 AIController::GetSeekForce(const Entity &, const EntityState &state) const noexcept {
382 return Seek(state, GetSeekTarget().GetState(), seek_speed, 2.0f);
387 void AIController::StartEvading() noexcept {
391 void AIController::StopEvading() noexcept {
394 evade_target->UnRef();
395 evade_target = nullptr;
399 bool AIController::IsEvading() const noexcept {
400 return evading && evade_target;
403 void AIController::SetEvadeTarget(Entity &e) noexcept {
405 evade_target->UnRef();
411 void AIController::SetEvadeSpeed(float speed) noexcept {
415 Entity &AIController::GetEvadeTarget() noexcept {
416 return *evade_target;
419 const Entity &AIController::GetEvadeTarget() const noexcept {
420 return *evade_target;
423 glm::vec3 AIController::GetEvadeForce(const Entity &, const EntityState &state) const noexcept{
424 glm::vec3 cur_diff(state.Diff(GetEvadeTarget().GetState()));
425 float time_estimate = length(cur_diff) / evade_speed;
426 EntityState pred_state(GetEvadeTarget().GetState());
427 pred_state.block_pos += pred_state.velocity * time_estimate;
428 return Flee(state, pred_state, evade_speed, 2.0f);
433 void AIController::StartPursuing() noexcept {
437 void AIController::StopPursuing() noexcept {
439 if (pursuit_target) {
440 pursuit_target->UnRef();
441 pursuit_target = nullptr;
445 bool AIController::IsPursuing() const noexcept {
446 return pursuing && pursuit_target;
449 void AIController::SetPursuitTarget(Entity &e) noexcept {
450 if (pursuit_target) {
451 pursuit_target->UnRef();
454 pursuit_target->Ref();
457 void AIController::SetPursuitSpeed(float speed) noexcept {
458 pursuit_speed = speed;
461 Entity &AIController::GetPursuitTarget() noexcept {
462 return *pursuit_target;
465 const Entity &AIController::GetPursuitTarget() const noexcept {
466 return *pursuit_target;
469 glm::vec3 AIController::GetPursuitForce(const Entity &, const EntityState &state) const noexcept {
470 glm::vec3 cur_diff(state.Diff(GetPursuitTarget().GetState()));
471 float time_estimate = length(cur_diff) / pursuit_speed;
472 EntityState pred_state(GetPursuitTarget().GetState());
473 pred_state.block_pos += pred_state.velocity * time_estimate;
474 return Seek(state, pred_state, pursuit_speed, 2.0f);
479 void AIController::StartWandering() noexcept {
483 void AIController::StopWandering() noexcept {
487 bool AIController::IsWandering() const noexcept {
491 void AIController::SetWanderParams(
497 wander_speed = speed;
498 wander_dist = distance;
499 wander_radius = radius;
500 wander_disp = displacement;
503 glm::vec3 AIController::GetWanderForce(const Entity &e, const EntityState &state) const noexcept {
504 glm::vec3 wander_target(normalize(e.Heading() * wander_dist + wander_pos) * wander_speed);
505 return TargetVelocity(wander_target, state, 0.5f);
511 void ChaseState::Enter(AIController &ctrl) const {
512 ctrl.SetHaltSpeed(2.0f);
513 ctrl.SetPursuitSpeed(4.0f);
514 ctrl.StartPursuing();
517 void ChaseState::Update(AIController &ctrl, Entity &e, float dt) const {
518 // check if target still alive and in sight
519 if (ctrl.GetPursuitTarget().Dead()) {
523 if (!ctrl.LineOfSight(e, ctrl.GetPursuitTarget())) {
527 // halt if we're close enough, flee if we're too close
528 float dist_sq = length_squared(e.AbsoluteDifference(ctrl.GetPursuitTarget()));
529 if (dist_sq < 8.0f) {
530 ctrl.SetFleeTarget(ctrl.GetPursuitTarget());
532 } else if (dist_sq < 25.0f) {
539 void ChaseState::Exit(AIController &ctrl) const {
546 void FleeState::Enter(AIController &ctrl) const {
547 ctrl.CueDecision(6.0f, 3.0f);
548 ctrl.SetFleeSpeed(4.0f);
552 void FleeState::Update(AIController &ctrl, Entity &e, float dt) const {
553 if (!ctrl.DecisionDue()) return;
557 void FleeState::Exit(AIController &ctrl) const {
563 void IdleState::Enter(AIController &ctrl) const {
564 ctrl.SetHaltSpeed(0.5f);
566 ctrl.SetWanderParams(0.001f, 1.1f);
567 ctrl.CueDecision(10.0f, 5.0f);
570 void IdleState::Update(AIController &ctrl, Entity &e, float dt) const {
571 if (ctrl.MayThink()) {
572 const Player *player = ctrl.ClosestVisiblePlayer(e);
574 ctrl.SetPursuitTarget(player->GetEntity());
575 ctrl.SetState(chase);
580 if (!ctrl.DecisionDue()) return;
582 unsigned int d = ctrl.Decide(10);
584 // .2 chance to start going
587 // .3 chance of looking around
589 ctrl.StartWandering();
591 // .5 chance of doing nothing
592 ctrl.StopWandering();
595 ctrl.CueDecision(10.0f, 5.0f);
598 void IdleState::Exit(AIController &ctrl) const {
600 ctrl.StopWandering();
605 void RoamState::Enter(AIController &ctrl) const {
606 ctrl.SetWanderParams(1.0f);
607 ctrl.StartWandering();
608 ctrl.CueDecision(10.0f, 5.0f);
611 void RoamState::Update(AIController &ctrl, Entity &e, float dt) const {
612 if (ctrl.MayThink()) {
613 const Player *player = ctrl.ClosestVisiblePlayer(e);
615 ctrl.SetPursuitTarget(player->GetEntity());
616 ctrl.SetState(chase);
621 if (!ctrl.DecisionDue()) return;
623 unsigned int d = ctrl.Decide(10);
625 // .1 chance of idling
628 ctrl.CueDecision(10.0f, 5.0f);
631 void RoamState::Exit(AIController &ctrl) const {
632 ctrl.StopWandering();