]> git.localhorst.tv Git - blank.git/blob - src/ai/ai.cpp
make AI entities avoid world collisions
[blank.git] / src / ai / ai.cpp
1 #include "AIController.hpp"
2 #include "ChaseState.hpp"
3 #include "FleeState.hpp"
4 #include "IdleState.hpp"
5 #include "RoamState.hpp"
6
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"
12
13 #include <cmath>
14 #include <limits>
15 #include <glm/glm.hpp>
16
17
18 namespace blank {
19
20 namespace {
21
22 ChaseState chase;
23 FleeState flee;
24 IdleState idle;
25 RoamState roam;
26
27 }
28
29 AIController::AIController(World &world, GaloisLFSR &rand)
30 : world(world)
31 , random(rand)
32 , state(&idle)
33 , sight_dist(64.0f)
34 , sight_angle(0.707f)
35 , think_timer(0.5f)
36 , decision_timer(1.0f)
37 , halted(false)
38 , halt_speed(1.0f)
39 , avoid_obstacles(true)
40 , obstacle_box{ glm::vec3(0.0f), glm::vec3(0.0f) }
41 , obstacle_transform(1.0f)
42 , fleeing(false)
43 , flee_target(nullptr)
44 , flee_speed(5.0f)
45 , seeking(false)
46 , seek_target(nullptr)
47 , seek_speed(5.0f)
48 , evading(false)
49 , evade_target(nullptr)
50 , evade_speed(5.0f)
51 , pursuing(false)
52 , pursuit_target(nullptr)
53 , pursuit_speed(5.0f)
54 , wandering(false)
55 , wander_pos(1.0f, 0.0f, 0.0f)
56 , wander_speed(1.0f)
57 , wander_dist(2.0f)
58 , wander_radius(1.5f)
59 , wander_disp(1.0f) {
60         think_timer.Start();
61         state->Enter(*this);
62 }
63
64 AIController::~AIController() {
65         state->Exit(*this);
66 }
67
68 void AIController::SetState(const AIState &s) {
69         state->Exit(*this);
70         state = &s;
71         state->Enter(*this);
72 }
73
74 void AIController::Update(Entity &e, float dt) {
75         think_timer.Update(dt);
76         decision_timer.Update(dt);
77         state->Update(*this, e, dt);
78
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);
87         }
88
89         if (wandering) {
90                 glm::vec3 displacement(
91                         random.SNorm() * wander_disp,
92                         random.SNorm() * wander_disp,
93                         random.SNorm() * wander_disp
94                 );
95                 if (!iszero(displacement)) {
96                         wander_pos = normalize(wander_pos + displacement * dt) * wander_radius;
97                 }
98         }
99
100         if (e.Moving()) {
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);
106         }
107 }
108
109 glm::vec3 AIController::ControlForce(const Entity &entity, const EntityState &state) const {
110         if (IsHalted()) {
111                 return GetHaltForce(entity, state);
112         }
113         glm::vec3 force(0.0f);
114         if (IsAvoidingObstacles() && entity.Moving()) {
115                 if (MaxOutForce(force, GetObstacleAvoidanceForce(entity, state), entity.MaxControlForce())) {
116                         return force;
117                 }
118         }
119         if (IsFleeing()) {
120                 if (MaxOutForce(force, GetFleeForce(entity, state), entity.MaxControlForce())) {
121                         return force;
122                 }
123         }
124         if (IsSeeking()) {
125                 if (MaxOutForce(force, GetSeekForce(entity, state), entity.MaxControlForce())) {
126                         return force;
127                 }
128         }
129         if (IsEvading()) {
130                 if (MaxOutForce(force, GetEvadeForce(entity, state), entity.MaxControlForce())) {
131                         return force;
132                 }
133         }
134         if (IsPursuing()) {
135                 if (MaxOutForce(force, GetPursuitForce(entity, state), entity.MaxControlForce())) {
136                         return force;
137                 }
138         }
139         if (IsWandering()) {
140                 if (MaxOutForce(force, GetWanderForce(entity, state), entity.MaxControlForce())) {
141                         return force;
142                 }
143         }
144         return force;
145 }
146
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();
154
155                 // distance test
156                 const glm::vec3 diff(pe.AbsoluteDifference(e));
157                 float dist = length(diff);
158                 if (dist > distance) continue;
159
160                 // FOV test, 45° in each direction
161                 if (dot(diff / dist, aim.dir) < sight_angle) {
162                         continue;
163                 }
164
165                 // LOS test, assumes all entities are see-through
166                 WorldCollision col;
167                 if (world.Intersection(aim, glm::mat4(1.0f), reference, col) && col.depth < dist) {
168                         continue;
169                 }
170
171                 // we got a match
172                 target = &p;
173                 distance = dist;
174         }
175         return target;
176 }
177
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) {
184                 return false;
185         }
186         WorldCollision col;
187         if (world.Intersection(aim, glm::mat4(1.0f), reference, col) && col.depth < dist) {
188                 return false;
189         }
190         return true;
191 }
192
193 // think
194
195 bool AIController::MayThink() const noexcept {
196         return think_timer.Hit();
197 }
198
199 void AIController::SetThinkInterval(float i) noexcept {
200         think_timer = FineTimer(i);
201         think_timer.Start();
202 }
203
204 // decide
205
206 void AIController::CueDecision(
207         float minimum,
208         float variance
209 ) noexcept {
210         decision_timer = FineTimer(minimum + variance * random.SNorm());
211         decision_timer.Start();
212 }
213
214 bool AIController::DecisionDue() const noexcept {
215         return decision_timer.HitOnce();
216 }
217
218 unsigned int AIController::Decide(unsigned int num_choices) noexcept {
219         return random.Next<unsigned int>() % num_choices;
220 }
221
222 // halt
223
224 void AIController::EnterHalt() noexcept {
225         halted = true;
226 }
227
228 void AIController::ExitHalt() noexcept {
229         halted = false;
230 }
231
232 bool AIController::IsHalted() const noexcept {
233         return halted;
234 }
235
236 void AIController::SetHaltSpeed(float speed) noexcept {
237         halt_speed = speed;
238 }
239
240 glm::vec3 AIController::GetHaltForce(const Entity &, const EntityState &state) const noexcept {
241         return Halt(state, halt_speed);
242 }
243
244 // obstacle avoidance
245
246 void AIController::StartAvoidingObstacles() noexcept {
247         avoid_obstacles = true;
248 }
249
250 void AIController::StopAvoidingObstacles() noexcept {
251         avoid_obstacles = false;
252 }
253
254 bool AIController::IsAvoidingObstacles() const noexcept {
255         return avoid_obstacles;
256 }
257
258 namespace {
259
260 std::vector<WorldCollision> col;
261
262 }
263
264 glm::vec3 AIController::GetObstacleAvoidanceForce(const Entity &e, const EntityState &state) const noexcept {
265         if (!e.Moving()) {
266                 return glm::vec3(0.0f);
267         }
268         col.clear();
269         if (!world.Intersection(obstacle_box, obstacle_transform, e.ChunkCoords(), col)) {
270                 return glm::vec3(0.0f);
271         }
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) {
281                         nearest = &c;
282                         difference = diff;
283                         distance = dist;
284                 }
285         }
286         if (!nearest) {
287                 // intersection test lied to us
288                 return glm::vec3(0.0f);
289         }
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));
299 }
300
301 // flee
302
303 void AIController::StartFleeing() noexcept {
304         fleeing = true;
305 }
306
307 void AIController::StopFleeing() noexcept {
308         fleeing = false;
309         if (flee_target) {
310                 flee_target->UnRef();
311                 flee_target = nullptr;
312         }
313 }
314
315 bool AIController::IsFleeing() const noexcept {
316         return fleeing && flee_target;
317 }
318
319 void AIController::SetFleeTarget(Entity &e) noexcept {
320         if (flee_target) {
321                 flee_target->UnRef();
322         }
323         flee_target = &e;
324         flee_target->Ref();
325 }
326
327 void AIController::SetFleeSpeed(float speed) noexcept {
328         flee_speed = speed;
329 }
330
331 Entity &AIController::GetFleeTarget() noexcept {
332         return *flee_target;
333 }
334
335 const Entity &AIController::GetFleeTarget() const noexcept {
336         return *flee_target;
337 }
338
339 glm::vec3 AIController::GetFleeForce(const Entity &, const EntityState &state) const noexcept {
340         return Flee(state, GetFleeTarget().GetState(), flee_speed, 2.0f);
341 }
342
343 // seek
344
345 void AIController::StartSeeking() noexcept {
346         seeking = true;
347 }
348
349 void AIController::StopSeeking() noexcept {
350         seeking = false;
351         if (seek_target) {
352                 seek_target->UnRef();
353                 seek_target = nullptr;
354         }
355 }
356
357 bool AIController::IsSeeking() const noexcept {
358         return seeking && seek_target;
359 }
360
361 void AIController::SetSeekTarget(Entity &e) noexcept {
362         if (seek_target) {
363                 seek_target->UnRef();
364         }
365         seek_target = &e;
366         seek_target->Ref();
367 }
368
369 void AIController::SetSeekSpeed(float speed) noexcept {
370         seek_speed = speed;
371 }
372
373 Entity &AIController::GetSeekTarget() noexcept {
374         return *seek_target;
375 }
376
377 const Entity &AIController::GetSeekTarget() const noexcept {
378         return *seek_target;
379 }
380
381 glm::vec3 AIController::GetSeekForce(const Entity &, const EntityState &state) const noexcept {
382         return Seek(state, GetSeekTarget().GetState(), seek_speed, 2.0f);
383 }
384
385 // evade
386
387 void AIController::StartEvading() noexcept {
388         evading = true;
389 }
390
391 void AIController::StopEvading() noexcept {
392         evading = false;
393         if (evade_target) {
394                 evade_target->UnRef();
395                 evade_target = nullptr;
396         }
397 }
398
399 bool AIController::IsEvading() const noexcept {
400         return evading && evade_target;
401 }
402
403 void AIController::SetEvadeTarget(Entity &e) noexcept {
404         if (evade_target) {
405                 evade_target->UnRef();
406         }
407         evade_target = &e;
408         evade_target->Ref();
409 }
410
411 void AIController::SetEvadeSpeed(float speed) noexcept {
412         evade_speed = speed;
413 }
414
415 Entity &AIController::GetEvadeTarget() noexcept {
416         return *evade_target;
417 }
418
419 const Entity &AIController::GetEvadeTarget() const noexcept {
420         return *evade_target;
421 }
422
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);
429 }
430
431 // pursuit
432
433 void AIController::StartPursuing() noexcept {
434         pursuing = true;
435 }
436
437 void AIController::StopPursuing() noexcept {
438         pursuing = false;
439         if (pursuit_target) {
440                 pursuit_target->UnRef();
441                 pursuit_target = nullptr;
442         }
443 }
444
445 bool AIController::IsPursuing() const noexcept {
446         return pursuing && pursuit_target;
447 }
448
449 void AIController::SetPursuitTarget(Entity &e) noexcept {
450         if (pursuit_target) {
451                 pursuit_target->UnRef();
452         }
453         pursuit_target = &e;
454         pursuit_target->Ref();
455 }
456
457 void AIController::SetPursuitSpeed(float speed) noexcept {
458         pursuit_speed = speed;
459 }
460
461 Entity &AIController::GetPursuitTarget() noexcept {
462         return *pursuit_target;
463 }
464
465 const Entity &AIController::GetPursuitTarget() const noexcept {
466         return *pursuit_target;
467 }
468
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);
475 }
476
477 // wander
478
479 void AIController::StartWandering() noexcept {
480         wandering = true;
481 }
482
483 void AIController::StopWandering() noexcept {
484         wandering = false;
485 }
486
487 bool AIController::IsWandering() const noexcept {
488         return wandering;
489 }
490
491 void AIController::SetWanderParams(
492         float speed,
493         float distance,
494         float radius,
495         float displacement
496 ) noexcept {
497         wander_speed = speed;
498         wander_dist = distance;
499         wander_radius = radius;
500         wander_disp = displacement;
501 }
502
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);
506 }
507
508
509 // chase
510
511 void ChaseState::Enter(AIController &ctrl) const {
512         ctrl.SetHaltSpeed(2.0f);
513         ctrl.SetPursuitSpeed(4.0f);
514         ctrl.StartPursuing();
515 }
516
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()) {
520                 ctrl.SetState(idle);
521                 return;
522         }
523         if (!ctrl.LineOfSight(e, ctrl.GetPursuitTarget())) {
524                 ctrl.SetState(idle);
525                 return;
526         }
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());
531                 ctrl.SetState(flee);
532         } else if (dist_sq < 25.0f) {
533                 ctrl.EnterHalt();
534         } else {
535                 ctrl.ExitHalt();
536         }
537 }
538
539 void ChaseState::Exit(AIController &ctrl) const {
540         ctrl.StopPursuing();
541         ctrl.ExitHalt();
542 }
543
544 // flee
545
546 void FleeState::Enter(AIController &ctrl) const {
547         ctrl.CueDecision(6.0f, 3.0f);
548         ctrl.SetFleeSpeed(4.0f);
549         ctrl.StartFleeing();
550 }
551
552 void FleeState::Update(AIController &ctrl, Entity &e, float dt) const {
553         if (!ctrl.DecisionDue()) return;
554         ctrl.SetState(idle);
555 }
556
557 void FleeState::Exit(AIController &ctrl) const {
558         ctrl.StopFleeing();
559 }
560
561 // idle
562
563 void IdleState::Enter(AIController &ctrl) const {
564         ctrl.SetHaltSpeed(0.5f);
565         ctrl.EnterHalt();
566         ctrl.SetWanderParams(0.001f, 1.1f);
567         ctrl.CueDecision(10.0f, 5.0f);
568 }
569
570 void IdleState::Update(AIController &ctrl, Entity &e, float dt) const {
571         if (ctrl.MayThink()) {
572                 const Player *player = ctrl.ClosestVisiblePlayer(e);
573                 if (player) {
574                         ctrl.SetPursuitTarget(player->GetEntity());
575                         ctrl.SetState(chase);
576                         return;
577                 }
578         }
579
580         if (!ctrl.DecisionDue()) return;
581
582         unsigned int d = ctrl.Decide(10);
583         if (d < 2) {
584                 // .2 chance to start going
585                 ctrl.SetState(roam);
586         } else if (d < 5) {
587                 // .3 chance of looking around
588                 ctrl.ExitHalt();
589                 ctrl.StartWandering();
590         } else {
591                 // .5 chance of doing nothing
592                 ctrl.StopWandering();
593                 ctrl.EnterHalt();
594         }
595         ctrl.CueDecision(10.0f, 5.0f);
596 }
597
598 void IdleState::Exit(AIController &ctrl) const {
599         ctrl.ExitHalt();
600         ctrl.StopWandering();
601 }
602
603 // roam
604
605 void RoamState::Enter(AIController &ctrl) const {
606         ctrl.SetWanderParams(1.0f);
607         ctrl.StartWandering();
608         ctrl.CueDecision(10.0f, 5.0f);
609 }
610
611 void RoamState::Update(AIController &ctrl, Entity &e, float dt) const {
612         if (ctrl.MayThink()) {
613                 const Player *player = ctrl.ClosestVisiblePlayer(e);
614                 if (player) {
615                         ctrl.SetPursuitTarget(player->GetEntity());
616                         ctrl.SetState(chase);
617                         return;
618                 }
619         }
620
621         if (!ctrl.DecisionDue()) return;
622
623         unsigned int d = ctrl.Decide(10);
624         if (d == 0) {
625                 // .1 chance of idling
626                 ctrl.SetState(idle);
627         }
628         ctrl.CueDecision(10.0f, 5.0f);
629 }
630
631 void RoamState::Exit(AIController &ctrl) const {
632         ctrl.StopWandering();
633 }
634
635 }