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 // only half pitch, so we don't crane our neck
104 float tgt_pitch = std::atan(heading.y / length(glm::vec2(heading.x, heading.z))) * 0.5f;
105 // always look straight ahead
106 // maybe look at the pursuit target if there is one
107 float tgt_yaw = 0.0f;
108 e.SetHead(tgt_pitch, tgt_yaw);
113 glm::vec3 AIController::ControlForce(const Entity &entity, const EntityState &state) const {
115 return GetHaltForce(entity, state);
117 glm::vec3 force(0.0f);
118 if (IsAvoidingObstacles() && entity.Moving()) {
119 if (MaxOutForce(force, GetObstacleAvoidanceForce(entity, state), entity.MaxControlForce())) {
124 if (MaxOutForce(force, GetFleeForce(entity, state), entity.MaxControlForce())) {
129 if (MaxOutForce(force, GetSeekForce(entity, state), entity.MaxControlForce())) {
134 if (MaxOutForce(force, GetEvadeForce(entity, state), entity.MaxControlForce())) {
139 if (MaxOutForce(force, GetPursuitForce(entity, state), entity.MaxControlForce())) {
144 if (MaxOutForce(force, GetWanderForce(entity, state), entity.MaxControlForce())) {
151 Player *AIController::ClosestVisiblePlayer(const Entity &e) noexcept {
152 Player *target = nullptr;
153 float distance = sight_dist;
154 const glm::ivec3 &reference(e.ChunkCoords());
155 Ray aim(e.Aim(reference));
156 for (Player &p : world.Players()) {
157 const Entity &pe = p.GetEntity();
160 const glm::vec3 diff(pe.AbsoluteDifference(e));
161 float dist = length(diff);
162 if (dist > distance) continue;
164 // FOV test, 45° in each direction
165 if (dot(diff / dist, aim.dir) < sight_angle) {
169 // LOS test, assumes all entities are see-through
171 if (world.Intersection(aim, glm::mat4(1.0f), reference, col) && col.depth < dist) {
182 bool AIController::LineOfSight(const Entity &from, const Entity &to) const noexcept {
183 const glm::ivec3 &reference(from.ChunkCoords());
184 Ray aim(from.Aim(reference));
185 const glm::vec3 diff(to.AbsoluteDifference(from));
186 float dist = length(diff);
187 if (dist > sight_dist || dot(diff / dist, aim.dir) < sight_angle) {
191 if (world.Intersection(aim, glm::mat4(1.0f), reference, col) && col.depth < dist) {
199 bool AIController::MayThink() const noexcept {
200 return think_timer.Hit();
203 void AIController::SetThinkInterval(float i) noexcept {
204 think_timer = FineTimer(i);
210 void AIController::CueDecision(
214 decision_timer = FineTimer(minimum + variance * random.SNorm());
215 decision_timer.Start();
218 bool AIController::DecisionDue() const noexcept {
219 return decision_timer.HitOnce();
222 unsigned int AIController::Decide(unsigned int num_choices) noexcept {
223 return random.Next<unsigned int>() % num_choices;
228 void AIController::EnterHalt() noexcept {
232 void AIController::ExitHalt() noexcept {
236 bool AIController::IsHalted() const noexcept {
240 void AIController::SetHaltSpeed(float speed) noexcept {
244 glm::vec3 AIController::GetHaltForce(const Entity &, const EntityState &state) const noexcept {
245 return Halt(state, halt_speed);
248 // obstacle avoidance
250 void AIController::StartAvoidingObstacles() noexcept {
251 avoid_obstacles = true;
254 void AIController::StopAvoidingObstacles() noexcept {
255 avoid_obstacles = false;
258 bool AIController::IsAvoidingObstacles() const noexcept {
259 return avoid_obstacles;
264 std::vector<WorldCollision> col;
268 glm::vec3 AIController::GetObstacleAvoidanceForce(const Entity &e, const EntityState &state) const noexcept {
270 return glm::vec3(0.0f);
273 if (!world.Intersection(obstacle_box, obstacle_transform, e.ChunkCoords(), col)) {
274 return glm::vec3(0.0f);
276 // find the nearest block
277 WorldCollision *nearest = nullptr;
278 glm::vec3 difference(0.0f);
279 float distance = std::numeric_limits<float>::infinity();
280 for (WorldCollision &c : col) {
281 // diff points from block to state
282 glm::vec3 diff = state.RelativePosition(c.ChunkPos()) - c.BlockCoords();
283 float dist = length_squared(diff);
284 if (dist < distance) {
291 // intersection test lied to us
292 return glm::vec3(0.0f);
294 // and steer away from it
295 // to_go is the distance between our position and the
296 // point on the "velocity ray" closest to obstacle
297 float to_go = dot(difference, e.Heading());
298 // point is our future position if we keep going our way
299 glm::vec3 point(e.GetState().block_pos + e.Heading() * to_go);
300 // now steer away in the direction of (point - block)
301 // with a magniture proportional to speed/distance
302 return normalize(point - nearest->BlockCoords()) * (e.Speed() / std::sqrt(distance));
307 void AIController::StartFleeing() noexcept {
311 void AIController::StopFleeing() noexcept {
314 flee_target->UnRef();
315 flee_target = nullptr;
319 bool AIController::IsFleeing() const noexcept {
320 return fleeing && flee_target;
323 void AIController::SetFleeTarget(Entity &e) noexcept {
325 flee_target->UnRef();
331 void AIController::SetFleeSpeed(float speed) noexcept {
335 Entity &AIController::GetFleeTarget() noexcept {
339 const Entity &AIController::GetFleeTarget() const noexcept {
343 glm::vec3 AIController::GetFleeForce(const Entity &, const EntityState &state) const noexcept {
344 return Flee(state, GetFleeTarget().GetState(), flee_speed, 2.0f);
349 void AIController::StartSeeking() noexcept {
353 void AIController::StopSeeking() noexcept {
356 seek_target->UnRef();
357 seek_target = nullptr;
361 bool AIController::IsSeeking() const noexcept {
362 return seeking && seek_target;
365 void AIController::SetSeekTarget(Entity &e) noexcept {
367 seek_target->UnRef();
373 void AIController::SetSeekSpeed(float speed) noexcept {
377 Entity &AIController::GetSeekTarget() noexcept {
381 const Entity &AIController::GetSeekTarget() const noexcept {
385 glm::vec3 AIController::GetSeekForce(const Entity &, const EntityState &state) const noexcept {
386 return Seek(state, GetSeekTarget().GetState(), seek_speed, 2.0f);
391 void AIController::StartEvading() noexcept {
395 void AIController::StopEvading() noexcept {
398 evade_target->UnRef();
399 evade_target = nullptr;
403 bool AIController::IsEvading() const noexcept {
404 return evading && evade_target;
407 void AIController::SetEvadeTarget(Entity &e) noexcept {
409 evade_target->UnRef();
415 void AIController::SetEvadeSpeed(float speed) noexcept {
419 Entity &AIController::GetEvadeTarget() noexcept {
420 return *evade_target;
423 const Entity &AIController::GetEvadeTarget() const noexcept {
424 return *evade_target;
427 glm::vec3 AIController::GetEvadeForce(const Entity &, const EntityState &state) const noexcept{
428 glm::vec3 cur_diff(state.Diff(GetEvadeTarget().GetState()));
429 float time_estimate = length(cur_diff) / evade_speed;
430 EntityState pred_state(GetEvadeTarget().GetState());
431 pred_state.block_pos += pred_state.velocity * time_estimate;
432 return Flee(state, pred_state, evade_speed, 2.0f);
437 void AIController::StartPursuing() noexcept {
441 void AIController::StopPursuing() noexcept {
443 if (pursuit_target) {
444 pursuit_target->UnRef();
445 pursuit_target = nullptr;
449 bool AIController::IsPursuing() const noexcept {
450 return pursuing && pursuit_target;
453 void AIController::SetPursuitTarget(Entity &e) noexcept {
454 if (pursuit_target) {
455 pursuit_target->UnRef();
458 pursuit_target->Ref();
461 void AIController::SetPursuitSpeed(float speed) noexcept {
462 pursuit_speed = speed;
465 Entity &AIController::GetPursuitTarget() noexcept {
466 return *pursuit_target;
469 const Entity &AIController::GetPursuitTarget() const noexcept {
470 return *pursuit_target;
473 glm::vec3 AIController::GetPursuitForce(const Entity &, const EntityState &state) const noexcept {
474 glm::vec3 cur_diff(state.Diff(GetPursuitTarget().GetState()));
475 float time_estimate = length(cur_diff) / pursuit_speed;
476 EntityState pred_state(GetPursuitTarget().GetState());
477 pred_state.block_pos += pred_state.velocity * time_estimate;
478 return Seek(state, pred_state, pursuit_speed, 2.0f);
483 void AIController::StartWandering() noexcept {
487 void AIController::StopWandering() noexcept {
491 bool AIController::IsWandering() const noexcept {
495 void AIController::SetWanderParams(
501 wander_speed = speed;
502 wander_dist = distance;
503 wander_radius = radius;
504 wander_disp = displacement;
507 glm::vec3 AIController::GetWanderForce(const Entity &e, const EntityState &state) const noexcept {
508 glm::vec3 wander_target(normalize(e.Heading() * wander_dist + wander_pos) * wander_speed);
509 return TargetVelocity(wander_target, state, 0.5f);
515 void ChaseState::Enter(AIController &ctrl) const {
516 ctrl.SetHaltSpeed(2.0f);
517 ctrl.SetPursuitSpeed(4.0f);
518 ctrl.StartPursuing();
521 void ChaseState::Update(AIController &ctrl, Entity &e, float dt) const {
522 // check if target still alive and in sight
523 if (ctrl.GetPursuitTarget().Dead()) {
527 if (!ctrl.LineOfSight(e, ctrl.GetPursuitTarget())) {
531 // halt if we're close enough, flee if we're too close
532 float dist_sq = length_squared(e.AbsoluteDifference(ctrl.GetPursuitTarget()));
533 if (dist_sq < 8.0f) {
534 ctrl.SetFleeTarget(ctrl.GetPursuitTarget());
536 } else if (dist_sq < 25.0f) {
543 void ChaseState::Exit(AIController &ctrl) const {
550 void FleeState::Enter(AIController &ctrl) const {
551 ctrl.CueDecision(6.0f, 3.0f);
552 ctrl.SetFleeSpeed(4.0f);
556 void FleeState::Update(AIController &ctrl, Entity &e, float dt) const {
557 if (!ctrl.DecisionDue()) return;
561 void FleeState::Exit(AIController &ctrl) const {
567 void IdleState::Enter(AIController &ctrl) const {
568 ctrl.SetHaltSpeed(0.5f);
570 ctrl.SetWanderParams(0.001f, 1.1f);
571 ctrl.CueDecision(10.0f, 5.0f);
574 void IdleState::Update(AIController &ctrl, Entity &e, float dt) const {
575 if (ctrl.MayThink()) {
576 const Player *player = ctrl.ClosestVisiblePlayer(e);
578 ctrl.SetPursuitTarget(player->GetEntity());
579 ctrl.SetState(chase);
584 if (!ctrl.DecisionDue()) return;
586 unsigned int d = ctrl.Decide(10);
588 // .2 chance to start going
591 // .3 chance of looking around
593 ctrl.StartWandering();
595 // .5 chance of doing nothing
596 ctrl.StopWandering();
599 ctrl.CueDecision(10.0f, 5.0f);
602 void IdleState::Exit(AIController &ctrl) const {
604 ctrl.StopWandering();
609 void RoamState::Enter(AIController &ctrl) const {
610 ctrl.SetWanderParams(1.0f);
611 ctrl.StartWandering();
612 ctrl.CueDecision(10.0f, 5.0f);
615 void RoamState::Update(AIController &ctrl, Entity &e, float dt) const {
616 if (ctrl.MayThink()) {
617 const Player *player = ctrl.ClosestVisiblePlayer(e);
619 ctrl.SetPursuitTarget(player->GetEntity());
620 ctrl.SetState(chase);
625 if (!ctrl.DecisionDue()) return;
627 unsigned int d = ctrl.Decide(10);
629 // .1 chance of idling
632 ctrl.CueDecision(10.0f, 5.0f);
635 void RoamState::Exit(AIController &ctrl) const {
636 ctrl.StopWandering();