1 #include "AIController.hpp"
2 #include "ChaseState.hpp"
3 #include "FleeState.hpp"
4 #include "IdleState.hpp"
5 #include "RoamState.hpp"
7 #include "../geometry/distance.hpp"
8 #include "../geometry/rotation.hpp"
9 #include "../rand/GaloisLFSR.hpp"
10 #include "../world/Entity.hpp"
11 #include "../world/World.hpp"
12 #include "../world/WorldCollision.hpp"
16 #include <glm/glm.hpp>
30 AIController::AIController(World &world, GaloisLFSR &rand)
37 , decision_timer(1.0f)
40 , avoid_obstacles(true)
41 , obstacle_box{ glm::vec3(0.0f), glm::vec3(0.0f) }
42 , obstacle_transform(1.0f)
44 , flee_target(nullptr)
47 , seek_target(nullptr)
50 , evade_target(nullptr)
53 , pursuit_target(nullptr)
56 , wander_pos(1.0f, 0.0f, 0.0f)
65 AIController::~AIController() {
69 void AIController::SetState(const AIState &s) {
75 void AIController::Update(Entity &e, float dt) {
76 think_timer.Update(dt);
77 decision_timer.Update(dt);
78 state->Update(*this, e, dt);
80 if (avoid_obstacles && e.Moving()) {
81 obstacle_box = e.Bounds();
82 obstacle_box.min.z = -e.Speed();
83 obstacle_box.max.x = 0.0f;
84 // our box is oriented for -Z velocity
85 obstacle_transform = glm::mat4(find_rotation(glm::vec3(0.0f, 0.0f, -1.0f), e.Heading()));
86 // and positioned relative to the entity's chunk
87 obstacle_transform[3] = glm::vec4(e.GetState().block_pos, 1.0f);
91 glm::vec3 displacement(
92 random.SNorm() * wander_disp,
93 random.SNorm() * wander_disp,
94 random.SNorm() * wander_disp
96 if (!iszero(displacement)) {
97 wander_pos = normalize(wander_pos + displacement * dt) * wander_radius;
102 // orient head towards heading
103 glm::vec3 heading(e.Heading());
104 // only half pitch, so we don't crane our neck
105 float tgt_pitch = std::atan(heading.y / length(glm::vec2(heading.x, heading.z))) * 0.5f;
106 // always look straight ahead
107 // maybe look at the pursuit target if there is one
108 float tgt_yaw = 0.0f;
109 e.SetHead(tgt_pitch, tgt_yaw);
114 glm::vec3 AIController::ControlForce(const Entity &entity, const EntityState &state) const {
116 return GetHaltForce(entity, state);
118 glm::vec3 force(0.0f);
119 if (IsAvoidingObstacles() && entity.Moving()) {
120 if (MaxOutForce(force, GetObstacleAvoidanceForce(entity, state), entity.MaxControlForce())) {
125 if (MaxOutForce(force, GetFleeForce(entity, state), entity.MaxControlForce())) {
130 if (MaxOutForce(force, GetSeekForce(entity, state), entity.MaxControlForce())) {
135 if (MaxOutForce(force, GetEvadeForce(entity, state), entity.MaxControlForce())) {
140 if (MaxOutForce(force, GetPursuitForce(entity, state), entity.MaxControlForce())) {
145 if (MaxOutForce(force, GetWanderForce(entity, state), entity.MaxControlForce())) {
152 Player *AIController::ClosestVisiblePlayer(const Entity &e) noexcept {
153 Player *target = nullptr;
154 float distance = sight_dist;
155 const glm::ivec3 &reference(e.ChunkCoords());
156 Ray aim(e.Aim(reference));
157 for (Player &p : world.Players()) {
158 const Entity &pe = p.GetEntity();
161 const glm::vec3 diff(pe.AbsoluteDifference(e));
162 float dist = length(diff);
163 if (dist > distance) continue;
165 // FOV test, 45° in each direction
166 if (dot(diff / dist, aim.dir) < sight_angle) {
170 // LOS test, assumes all entities are see-through
172 if (world.Intersection(aim, glm::mat4(1.0f), reference, col) && col.depth < dist) {
183 bool AIController::LineOfSight(const Entity &from, const Entity &to) const noexcept {
184 const glm::ivec3 &reference(from.ChunkCoords());
185 Ray aim(from.Aim(reference));
186 const glm::vec3 diff(to.AbsoluteDifference(from));
187 float dist = length(diff);
188 if (dist > sight_dist || dot(diff / dist, aim.dir) < sight_angle) {
192 if (world.Intersection(aim, glm::mat4(1.0f), reference, col) && col.depth < dist) {
200 bool AIController::MayThink() const noexcept {
201 return think_timer.Hit();
204 void AIController::SetThinkInterval(float i) noexcept {
205 think_timer = FineTimer(i);
211 void AIController::CueDecision(
215 decision_timer = FineTimer(minimum + variance * random.SNorm());
216 decision_timer.Start();
219 bool AIController::DecisionDue() const noexcept {
220 return decision_timer.HitOnce();
223 unsigned int AIController::Decide(unsigned int num_choices) noexcept {
224 return random.Next<unsigned int>() % num_choices;
229 void AIController::EnterHalt() noexcept {
233 void AIController::ExitHalt() noexcept {
237 bool AIController::IsHalted() const noexcept {
241 void AIController::SetHaltSpeed(float speed) noexcept {
245 glm::vec3 AIController::GetHaltForce(const Entity &, const EntityState &state) const noexcept {
246 return Halt(state, halt_speed);
249 // obstacle avoidance
251 void AIController::StartAvoidingObstacles() noexcept {
252 avoid_obstacles = true;
255 void AIController::StopAvoidingObstacles() noexcept {
256 avoid_obstacles = false;
259 bool AIController::IsAvoidingObstacles() const noexcept {
260 return avoid_obstacles;
265 std::vector<WorldCollision> col;
269 glm::vec3 AIController::GetObstacleAvoidanceForce(const Entity &e, const EntityState &state) const noexcept {
271 return glm::vec3(0.0f);
274 if (!world.Intersection(obstacle_box, obstacle_transform, e.ChunkCoords(), col)) {
275 return glm::vec3(0.0f);
277 // find the nearest block
278 WorldCollision *nearest = nullptr;
279 glm::vec3 difference(0.0f);
280 float distance = std::numeric_limits<float>::infinity();
281 for (WorldCollision &c : col) {
282 // diff points from block to state
283 glm::vec3 diff = state.RelativePosition(c.ChunkPos()) - c.BlockCoords();
284 float dist = length_squared(diff);
285 if (dist < distance) {
292 // intersection test lied to us
293 return glm::vec3(0.0f);
295 // and steer away from it
296 // to_go is the distance between our position and the
297 // point on the "velocity ray" closest to obstacle
298 float to_go = dot(difference, e.Heading());
299 // point is our future position if we keep going our way
300 glm::vec3 point(e.GetState().block_pos + e.Heading() * to_go);
301 // now steer away in the direction of (point - block)
302 // with a magniture proportional to speed/distance
303 return normalize(point - nearest->BlockCoords()) * (e.Speed() / std::sqrt(distance));
308 void AIController::StartFleeing() noexcept {
312 void AIController::StopFleeing() noexcept {
315 flee_target->UnRef();
316 flee_target = nullptr;
320 bool AIController::IsFleeing() const noexcept {
321 return fleeing && flee_target;
324 void AIController::SetFleeTarget(Entity &e) noexcept {
326 flee_target->UnRef();
332 void AIController::SetFleeSpeed(float speed) noexcept {
336 Entity &AIController::GetFleeTarget() noexcept {
340 const Entity &AIController::GetFleeTarget() const noexcept {
344 glm::vec3 AIController::GetFleeForce(const Entity &, const EntityState &state) const noexcept {
345 return Flee(state, GetFleeTarget().GetState(), flee_speed, 2.0f);
350 void AIController::StartSeeking() noexcept {
354 void AIController::StopSeeking() noexcept {
357 seek_target->UnRef();
358 seek_target = nullptr;
362 bool AIController::IsSeeking() const noexcept {
363 return seeking && seek_target;
366 void AIController::SetSeekTarget(Entity &e) noexcept {
368 seek_target->UnRef();
374 void AIController::SetSeekSpeed(float speed) noexcept {
378 Entity &AIController::GetSeekTarget() noexcept {
382 const Entity &AIController::GetSeekTarget() const noexcept {
386 glm::vec3 AIController::GetSeekForce(const Entity &, const EntityState &state) const noexcept {
387 return Seek(state, GetSeekTarget().GetState(), seek_speed, 2.0f);
392 void AIController::StartEvading() noexcept {
396 void AIController::StopEvading() noexcept {
399 evade_target->UnRef();
400 evade_target = nullptr;
404 bool AIController::IsEvading() const noexcept {
405 return evading && evade_target;
408 void AIController::SetEvadeTarget(Entity &e) noexcept {
410 evade_target->UnRef();
416 void AIController::SetEvadeSpeed(float speed) noexcept {
420 Entity &AIController::GetEvadeTarget() noexcept {
421 return *evade_target;
424 const Entity &AIController::GetEvadeTarget() const noexcept {
425 return *evade_target;
428 glm::vec3 AIController::GetEvadeForce(const Entity &, const EntityState &state) const noexcept{
429 glm::vec3 cur_diff(state.Diff(GetEvadeTarget().GetState()));
430 float time_estimate = length(cur_diff) / evade_speed;
431 EntityState pred_state(GetEvadeTarget().GetState());
432 pred_state.block_pos += pred_state.velocity * time_estimate;
433 return Flee(state, pred_state, evade_speed, 2.0f);
438 void AIController::StartPursuing() noexcept {
442 void AIController::StopPursuing() noexcept {
444 if (pursuit_target) {
445 pursuit_target->UnRef();
446 pursuit_target = nullptr;
450 bool AIController::IsPursuing() const noexcept {
451 return pursuing && pursuit_target;
454 void AIController::SetPursuitTarget(Entity &e) noexcept {
455 if (pursuit_target) {
456 pursuit_target->UnRef();
459 pursuit_target->Ref();
462 void AIController::SetPursuitSpeed(float speed) noexcept {
463 pursuit_speed = speed;
466 Entity &AIController::GetPursuitTarget() noexcept {
467 return *pursuit_target;
470 const Entity &AIController::GetPursuitTarget() const noexcept {
471 return *pursuit_target;
474 glm::vec3 AIController::GetPursuitForce(const Entity &, const EntityState &state) const noexcept {
475 glm::vec3 cur_diff(state.Diff(GetPursuitTarget().GetState()));
476 float time_estimate = length(cur_diff) / pursuit_speed;
477 EntityState pred_state(GetPursuitTarget().GetState());
478 pred_state.block_pos += pred_state.velocity * time_estimate;
479 return Seek(state, pred_state, pursuit_speed, 2.0f);
484 void AIController::StartWandering() noexcept {
488 void AIController::StopWandering() noexcept {
492 bool AIController::IsWandering() const noexcept {
496 void AIController::SetWanderParams(
502 wander_speed = speed;
503 wander_dist = distance;
504 wander_radius = radius;
505 wander_disp = displacement;
508 glm::vec3 AIController::GetWanderForce(const Entity &e, const EntityState &state) const noexcept {
509 glm::vec3 wander_target(normalize(e.Heading() * wander_dist + wander_pos) * wander_speed);
510 return TargetVelocity(wander_target, state, 0.5f);
516 void ChaseState::Enter(AIController &ctrl) const {
517 ctrl.SetHaltSpeed(2.0f);
518 ctrl.SetPursuitSpeed(4.0f);
519 ctrl.StartPursuing();
522 void ChaseState::Update(AIController &ctrl, Entity &e, float dt) const {
523 // check if target still alive and in sight
524 if (ctrl.GetPursuitTarget().Dead()) {
528 if (!ctrl.LineOfSight(e, ctrl.GetPursuitTarget())) {
532 // halt if we're close enough, flee if we're too close
533 float dist_sq = length_squared(e.AbsoluteDifference(ctrl.GetPursuitTarget()));
534 if (dist_sq < 8.0f) {
535 ctrl.SetFleeTarget(ctrl.GetPursuitTarget());
537 } else if (dist_sq < 25.0f) {
544 void ChaseState::Exit(AIController &ctrl) const {
551 void FleeState::Enter(AIController &ctrl) const {
552 ctrl.CueDecision(6.0f, 3.0f);
553 ctrl.SetFleeSpeed(4.0f);
557 void FleeState::Update(AIController &ctrl, Entity &e, float dt) const {
558 if (!ctrl.DecisionDue()) return;
562 void FleeState::Exit(AIController &ctrl) const {
568 void IdleState::Enter(AIController &ctrl) const {
569 ctrl.SetHaltSpeed(0.5f);
571 ctrl.SetWanderParams(0.001f, 1.1f);
572 ctrl.CueDecision(10.0f, 5.0f);
575 void IdleState::Update(AIController &ctrl, Entity &e, float dt) const {
576 if (ctrl.MayThink()) {
577 const Player *player = ctrl.ClosestVisiblePlayer(e);
579 ctrl.SetPursuitTarget(player->GetEntity());
580 ctrl.SetState(chase);
585 if (!ctrl.DecisionDue()) return;
587 unsigned int d = ctrl.Decide(10);
589 // .2 chance to start going
592 // .3 chance of looking around
594 ctrl.StartWandering();
596 // .5 chance of doing nothing
597 ctrl.StopWandering();
600 ctrl.CueDecision(10.0f, 5.0f);
603 void IdleState::Exit(AIController &ctrl) const {
605 ctrl.StopWandering();
610 void RoamState::Enter(AIController &ctrl) const {
611 ctrl.SetWanderParams(1.0f);
612 ctrl.StartWandering();
613 ctrl.CueDecision(10.0f, 5.0f);
616 void RoamState::Update(AIController &ctrl, Entity &e, float dt) const {
617 if (ctrl.MayThink()) {
618 const Player *player = ctrl.ClosestVisiblePlayer(e);
620 ctrl.SetPursuitTarget(player->GetEntity());
621 ctrl.SetState(chase);
626 if (!ctrl.DecisionDue()) return;
628 unsigned int d = ctrl.Decide(10);
630 // .1 chance of idling
633 ctrl.CueDecision(10.0f, 5.0f);
636 void RoamState::Exit(AIController &ctrl) const {
637 ctrl.StopWandering();