]> git.localhorst.tv Git - blank.git/blob - src/ai/ai.cpp
experiments with ai states and steering
[blank.git] / src / ai / ai.cpp
1 #include "AIController.hpp"
2 #include "IdleState.hpp"
3 #include "RoamState.hpp"
4
5 #include "../model/geometry.hpp"
6 #include "../rand/GaloisLFSR.hpp"
7 #include "../world/Entity.hpp"
8 #include "../world/World.hpp"
9 #include "../world/WorldCollision.hpp"
10
11 #include <cmath>
12 #include <limits>
13 #include <glm/glm.hpp>
14
15
16 namespace blank {
17
18 namespace {
19
20 IdleState idle;
21 RoamState roam;
22
23 }
24
25 AIController::AIController(GaloisLFSR &rand)
26 : random(rand)
27 , state(&idle)
28 , decision_timer(1.0f)
29 , halted(false)
30 , halt_speed(1.0f)
31 , flee_target(nullptr)
32 , flee_speed(5.0f)
33 , seek_target(nullptr)
34 , seek_speed(5.0f)
35 , wandering(false)
36 , wander_pos(1.0f, 0.0f, 0.0f)
37 , wander_speed(1.0f)
38 , wander_dist(2.0f)
39 , wander_radius(1.5f)
40 , wander_disp(1.0f) {
41         state->Enter(*this);
42 }
43
44 AIController::~AIController() {
45         state->Exit(*this);
46 }
47
48 void AIController::SetState(const AIState &s) {
49         state->Exit(*this);
50         state = &s;
51         state->Enter(*this);
52 }
53
54 void AIController::Update(Entity &e, float dt) {
55         decision_timer.Update(dt);
56         state->Update(*this, e, dt);
57
58         if (wandering) {
59                 glm::vec3 displacement(
60                         random.SNorm() * wander_disp,
61                         random.SNorm() * wander_disp,
62                         random.SNorm() * wander_disp
63                 );
64                 if (!iszero(displacement)) {
65                         wander_pos = normalize(wander_pos + displacement * dt) * wander_radius;
66                 }
67         }
68
69         if (e.Moving()) {
70                 // orient head towards heading
71                 glm::vec3 heading(Heading(e.GetState()));
72                 float tgt_pitch = std::atan(heading.y / length(glm::vec2(heading.x, heading.z)));
73                 float tgt_yaw = std::atan2(-heading.x, -heading.z);
74                 e.SetHead(tgt_pitch, tgt_yaw);
75         }
76 }
77
78 glm::vec3 AIController::ControlForce(const Entity &entity, const EntityState &state) const {
79         if (IsHalted()) {
80                 return Halt(state, halt_speed);
81         }
82         glm::vec3 force(0.0f);
83         if (IsFleeing()) {
84                 glm::vec3 diff(GetFleeTarget().GetState().Diff(state));
85                 if (MaxOutForce(force, TargetVelocity(normalize(diff) * flee_speed, state, 0.5f), entity.MaxControlForce())) {
86                         return force;
87                 }
88         }
89         if (IsSeeking()) {
90                 glm::vec3 diff(state.Diff(GetSeekTarget().GetState()));
91                 if (MaxOutForce(force, TargetVelocity(normalize(diff) * seek_speed, state, 0.5f), entity.MaxControlForce())) {
92                         return force;
93                 }
94         }
95         if (IsWandering()) {
96                 glm::vec3 wander_target(normalize(Heading(state) * wander_dist + wander_pos) * wander_speed);
97                 if (MaxOutForce(force, TargetVelocity(wander_target, state, 2.0f), entity.MaxControlForce())) {
98                         return force;
99                 }
100         }
101         return force;
102 }
103
104 glm::vec3 AIController::Heading(const EntityState &state) noexcept {
105         if (dot(state.velocity, state.velocity) > std::numeric_limits<float>::epsilon()) {
106                 return normalize(state.velocity);
107         } else {
108                 float cp = std::cos(state.pitch);
109                 return glm::vec3(std::cos(state.yaw) * cp, std::sin(state.yaw) * cp, std::sin(state.pitch));
110         }
111 }
112
113 void AIController::CueDecision(
114         float minimum,
115         float variance
116 ) noexcept {
117         decision_timer = FineTimer(minimum + variance * random.SNorm());
118         decision_timer.Start();
119 }
120
121 bool AIController::DecisionDue() const noexcept {
122         return decision_timer.HitOnce();
123 }
124
125 unsigned int AIController::Decide(unsigned int num_choices) noexcept {
126         return random.Next<unsigned int>() % num_choices;
127 }
128
129 void AIController::EnterHalt(float speed) noexcept {
130         halted = true;
131         halt_speed = speed;
132 }
133
134 void AIController::ExitHalt() noexcept {
135         halted = false;
136 }
137
138 bool AIController::IsHalted() const noexcept {
139         return halted;
140 }
141
142 void AIController::StartFleeing(const Entity &e, float speed) noexcept {
143         flee_target = &e;
144         flee_speed = speed;
145 }
146
147 void AIController::StopFleeing() noexcept {
148         flee_target = nullptr;
149 }
150
151 bool AIController::IsFleeing() const noexcept {
152         return flee_target;
153 }
154
155 const Entity &AIController::GetFleeTarget() const noexcept {
156         return *flee_target;
157 }
158
159 void AIController::StartSeeking(const Entity &e, float speed) noexcept {
160         seek_target = &e;
161         seek_speed = speed;
162 }
163
164 void AIController::StopSeeking() noexcept {
165         seek_target = nullptr;
166 }
167
168 bool AIController::IsSeeking() const noexcept {
169         return seek_target;
170 }
171
172 const Entity &AIController::GetSeekTarget() const noexcept {
173         return *seek_target;
174 }
175
176 void AIController::StartWandering(
177         float speed,
178         float distance,
179         float radius,
180         float displacement
181 ) noexcept {
182         wandering = true;
183         wander_speed = speed;
184         wander_dist = distance;
185         wander_radius = radius;
186         wander_disp = displacement;
187 }
188
189 void AIController::StopWandering() noexcept {
190         wandering = false;
191 }
192
193 bool AIController::IsWandering() const noexcept {
194         return wandering;
195 }
196
197
198 void IdleState::Enter(AIController &ctrl) const {
199         ctrl.EnterHalt(2.0f);
200         ctrl.StartWandering(0.001f, 1.1f);
201         ctrl.CueDecision(10.0f, 5.0f);
202 }
203
204 void IdleState::Update(AIController &ctrl, Entity &e, float dt) const {
205         // TODO: check for players
206         if (!ctrl.DecisionDue()) return;
207
208         unsigned int d = ctrl.Decide(10);
209         if (d == 0) {
210                 // .1 chance to start going
211                 ctrl.SetState(roam);
212         } else if (d < 3) {
213                 // .2 chance of looking around
214                 ctrl.ExitHalt();
215         } else {
216                 ctrl.EnterHalt(2.0f);
217         }
218         ctrl.CueDecision(10.0f, 5.0f);
219 }
220
221 void IdleState::Exit(AIController &ctrl) const {
222         ctrl.ExitHalt();
223         ctrl.StopWandering();
224 }
225
226 void RoamState::Enter(AIController &ctrl) const {
227         ctrl.StartWandering(1.0f);
228         ctrl.CueDecision(10.0f, 5.0f);
229 }
230
231 void RoamState::Update(AIController &ctrl, Entity &e, float dt) const {
232         // TODO: check for players
233         if (!ctrl.DecisionDue()) return;
234
235         unsigned int d = ctrl.Decide(10);
236         if (d == 0) {
237                 // .1 chance of idling
238                 ctrl.SetState(idle);
239         }
240         ctrl.CueDecision(10.0f, 5.0f);
241 }
242
243 void RoamState::Exit(AIController &ctrl) const {
244         ctrl.StopWandering();
245 }
246
247 }