+glm::vec3 Steering::Force(const EntityState &state) const noexcept {
+ glm::vec3 force(0.0f);
+ if (!enabled) {
+ return force;
+ }
+ const float max = entity.MaxControlForce();
+ if (AnyEnabled(HALT)) {
+ if (SumForce(force, Halt(state), max)) {
+ return force;
+ }
+ }
+ if (AnyEnabled(TARGET_VELOCITY)) {
+ if (SumForce(force, TargetVelocity(state, target_velocity), max)) {
+ return force;
+ }
+ }
+ if (AnyEnabled(OBSTACLE_AVOIDANCE)) {
+ if (SumForce(force, ObstacleAvoidance(state), max)) {
+ return force;
+ }
+ }
+ if (AnyEnabled(EVADE_TARGET)) {
+ if (HasTargetEntity()) {
+ if (SumForce(force, Evade(state, GetTargetEntity()), max)) {
+ return force;
+ }
+ } else {
+ std::cout << "Steering: evade enabled, but target entity not set" << std::endl;
+ }
+ }
+ if (AnyEnabled(PURSUE_TARGET)) {
+ if (HasTargetEntity()) {
+ if (SumForce(force, Pursuit(state, GetTargetEntity()), max)) {
+ return force;
+ }
+ } else {
+ std::cout << "Steering: pursuit enabled, but target entity not set" << std::endl;
+ }
+ }
+ if (AnyEnabled(WANDER)) {
+ if (SumForce(force, Wander(state), max)) {
+ return force;
+ }
+ }
+ return force;
+}
+
+bool Steering::SumForce(glm::vec3 &out, const glm::vec3 &in, float max) noexcept {
+ if (iszero(in) || glm::any(glm::isnan(in))) {
+ return false;
+ }
+ float current = iszero(out) ? 0.0f : glm::length(out);
+ float remain = max - current;
+ if (remain <= 0.0f) {
+ return true;
+ }
+ float additional = glm::length(in);
+ if (additional > remain) {
+ out += glm::normalize(in) * remain;
+ return true;
+ } else {
+ out += in;
+ return false;
+ }
+}
+
+glm::vec3 Steering::Halt(const EntityState &state) const noexcept {
+ return state.velocity * -accel;
+}
+
+glm::vec3 Steering::TargetVelocity(const EntityState &state, const glm::vec3 &vel) const noexcept {
+ return (vel - state.velocity) * accel;
+}
+
+glm::vec3 Steering::Seek(const EntityState &state, const ExactLocation &loc) const noexcept {
+ const glm::vec3 diff(loc.Difference(state.pos).Absolute());
+ if (iszero(diff)) {
+ return glm::vec3(0.0f);
+ } else {
+ return TargetVelocity(state, glm::normalize(diff) * speed);
+ }
+}
+
+glm::vec3 Steering::Flee(const EntityState &state, const ExactLocation &loc) const noexcept {
+ const glm::vec3 diff(state.pos.Difference(loc).Absolute());
+ if (iszero(diff)) {
+ return glm::vec3(0.0f);
+ } else {
+ return TargetVelocity(state, glm::normalize(diff) * speed);
+ }
+}
+
+glm::vec3 Steering::Arrive(const EntityState &state, const ExactLocation &loc) const noexcept {
+ const glm::vec3 diff(loc.Difference(state.pos).Absolute());
+ const float dist = glm::length(diff);
+ if (dist < std::numeric_limits<float>::epsilon()) {
+ return glm::vec3(0.0f);
+ } else {
+ const float att_speed = std::min(dist * accel, speed);
+ return TargetVelocity(state, diff * att_speed / dist);
+ }
+}
+
+glm::vec3 Steering::Pursuit(const EntityState &state, const Entity &other) const noexcept {
+ const glm::vec3 diff(state.Diff(other.GetState()));
+ if (iszero(diff)) {
+ return TargetVelocity(state, other.Velocity());
+ } else {
+ const float time_estimate = glm::length(diff) / speed;
+ ExactLocation prediction(other.ChunkCoords(), other.Position() + (other.Velocity() * time_estimate));
+ return Seek(state, prediction);
+ }
+}
+
+glm::vec3 Steering::Evade(const EntityState &state, const Entity &other) const noexcept {
+ const glm::vec3 diff(state.Diff(other.GetState()));
+ if (iszero(diff)) {
+ return TargetVelocity(state, -other.Velocity());
+ } else {
+ const float time_estimate = glm::length(diff) / speed;
+ ExactLocation prediction(other.ChunkCoords(), other.Position() + (other.Velocity() * time_estimate));
+ return Flee(state, prediction);
+ }
+}
+
+glm::vec3 Steering::Wander(const EntityState &state) const noexcept {
+ return TargetVelocity(state, glm::normalize(entity.Heading() * wander_dist + wander_pos) * speed);
+}
+
+glm::vec3 Steering::ObstacleAvoidance(const EntityState &) const noexcept {
+ return obstacle_dir;