From 6a3227ca747d0598711f7354cd39897184e9fe6a Mon Sep 17 00:00:00 2001 From: Daniel Karbach Date: Fri, 27 Nov 2015 16:43:02 +0100 Subject: [PATCH] move steering behaviours into entity --- src/ai/AIController.hpp | 105 +------ src/ai/AIState.hpp | 4 +- src/ai/ChaseState.hpp | 4 +- src/ai/FleeState.hpp | 4 +- src/ai/IdleState.hpp | 4 +- src/ai/RoamState.hpp | 4 +- src/ai/Spawner.cpp | 11 +- src/ai/Spawner.hpp | 5 +- src/ai/ai.cpp | 489 +++++---------------------------- src/app/Environment.hpp | 3 - src/app/runtime.cpp | 13 +- src/server/ServerState.cpp | 2 +- src/standalone/standalone.cpp | 2 +- src/ui/PlayerController.hpp | 2 - src/ui/ui.cpp | 31 ++- src/world/Entity.hpp | 5 + src/world/EntityController.hpp | 51 ---- src/world/Steering.hpp | 120 ++++++++ src/world/World.hpp | 6 + src/world/world.cpp | 293 +++++++++++++++++--- 20 files changed, 494 insertions(+), 664 deletions(-) create mode 100644 src/world/Steering.hpp diff --git a/src/ai/AIController.hpp b/src/ai/AIController.hpp index 15645a2..cfd49d9 100644 --- a/src/ai/AIController.hpp +++ b/src/ai/AIController.hpp @@ -12,23 +12,24 @@ namespace blank { class AIState; class Entity; -class GaloisLFSR; class Player; class World; +// TODO: AI and entities are tightly coupled, maybe AIcontroller should +// be part of Entity. In that case, players could either be separated +// from other entities use function as a degenerate AI which blindly +// executes whatever its human tell it to. class AIController : public EntityController { public: - AIController(World &, GaloisLFSR &); + AIController(World &, Entity &); ~AIController(); - void SetState(const AIState &); + void SetState(const AIState &, Entity &); void Update(Entity &, float dt) override; - glm::vec3 ControlForce(const Entity &, const EntityState &) const override; - /// get the closest player that given entity can see /// returns nullptr if none are in sight Player *ClosestVisiblePlayer(const Entity &) noexcept; @@ -49,72 +50,8 @@ public: /// random choice of 0 to num_choices - 1 unsigned int Decide(unsigned int num_choices) noexcept; - void EnterHalt() noexcept; - void ExitHalt() noexcept; - bool IsHalted() const noexcept; - void SetHaltSpeed(float) noexcept; - glm::vec3 GetHaltForce(const Entity &, const EntityState &) const noexcept; - - void StartAvoidingObstacles() noexcept; - void StopAvoidingObstacles() noexcept; - bool IsAvoidingObstacles() const noexcept; - glm::vec3 GetObstacleAvoidanceForce(const Entity &, const EntityState &) const noexcept; - - void StartFleeing() noexcept; - void StopFleeing() noexcept; - bool IsFleeing() const noexcept; - void SetFleeTarget(Entity &) noexcept; - void SetFleeSpeed(float) noexcept; - Entity &GetFleeTarget() noexcept; - const Entity &GetFleeTarget() const noexcept; - glm::vec3 GetFleeForce(const Entity &, const EntityState &) const noexcept; - - void StartSeeking() noexcept; - void StopSeeking() noexcept; - bool IsSeeking() const noexcept; - void SetSeekTarget(Entity &) noexcept; - void SetSeekSpeed(float) noexcept; - Entity &GetSeekTarget() noexcept; - const Entity &GetSeekTarget() const noexcept; - glm::vec3 GetSeekForce(const Entity &, const EntityState &) const noexcept; - - void StartEvading() noexcept; - void StopEvading() noexcept; - bool IsEvading() const noexcept; - void SetEvadeTarget(Entity &) noexcept; - void SetEvadeSpeed(float) noexcept; - Entity &GetEvadeTarget() noexcept; - const Entity &GetEvadeTarget() const noexcept; - glm::vec3 GetEvadeForce(const Entity &, const EntityState &) const noexcept; - - void StartPursuing() noexcept; - void StopPursuing() noexcept; - bool IsPursuing() const noexcept; - void SetPursuitTarget(Entity &) noexcept; - void SetPursuitSpeed(float) noexcept; - Entity &GetPursuitTarget() noexcept; - const Entity &GetPursuitTarget() const noexcept; - glm::vec3 GetPursuitForce(const Entity &, const EntityState &) const noexcept; - - /// start wandering randomly - void StartWandering() noexcept; - void StopWandering() noexcept; - bool IsWandering() const noexcept; - /// change how wandering is performed - /// the trajectory is modified by targetting a blip on a sphere - /// in front of the entity which moves randomly - /// the displacement is given (roughly) in units per second - void SetWanderParams( - float speed, - float distance = 2.0f, - float radius = 1.0f, - float displacement = 1.0f - ) noexcept; - glm::vec3 GetWanderForce(const Entity &, const EntityState &) const noexcept; - private: World &world; - GaloisLFSR &random; const AIState *state; /// how far controlled entities can see @@ -125,36 +62,6 @@ private: FineTimer think_timer; FineTimer decision_timer; - bool halted; - float halt_speed; - - bool avoid_obstacles; - AABB obstacle_box; - glm::mat4 obstacle_transform; - - bool fleeing; - Entity *flee_target; - float flee_speed; - - bool seeking; - Entity *seek_target; - float seek_speed; - - bool evading; - Entity *evade_target; - float evade_speed; - - bool pursuing; - Entity *pursuit_target; - float pursuit_speed; - - bool wandering; - glm::vec3 wander_pos; - float wander_speed; - float wander_dist; - float wander_radius; - float wander_disp; - }; } diff --git a/src/ai/AIState.hpp b/src/ai/AIState.hpp index ed82cc0..7642ebd 100644 --- a/src/ai/AIState.hpp +++ b/src/ai/AIState.hpp @@ -6,9 +6,9 @@ namespace blank { struct AIState { - virtual void Enter(AIController &) const = 0; + virtual void Enter(AIController &, Entity &) const = 0; virtual void Update(AIController &, Entity &, float dt) const = 0; - virtual void Exit(AIController &) const = 0; + virtual void Exit(AIController &, Entity &) const = 0; }; diff --git a/src/ai/ChaseState.hpp b/src/ai/ChaseState.hpp index 97e6895..3b707de 100644 --- a/src/ai/ChaseState.hpp +++ b/src/ai/ChaseState.hpp @@ -14,9 +14,9 @@ namespace blank { class ChaseState : public AIState { - void Enter(AIController &) const override; + void Enter(AIController &, Entity &) const override; void Update(AIController &, Entity &, float dt) const override; - void Exit(AIController &) const override; + void Exit(AIController &, Entity &) const override; }; diff --git a/src/ai/FleeState.hpp b/src/ai/FleeState.hpp index 8664e58..75a5d4b 100644 --- a/src/ai/FleeState.hpp +++ b/src/ai/FleeState.hpp @@ -14,9 +14,9 @@ namespace blank { class FleeState : public AIState { - void Enter(AIController &) const override; + void Enter(AIController &, Entity &) const override; void Update(AIController &, Entity &, float dt) const override; - void Exit(AIController &) const override; + void Exit(AIController &, Entity &) const override; }; diff --git a/src/ai/IdleState.hpp b/src/ai/IdleState.hpp index b663d91..ec2592a 100644 --- a/src/ai/IdleState.hpp +++ b/src/ai/IdleState.hpp @@ -14,9 +14,9 @@ namespace blank { class IdleState : public AIState { - void Enter(AIController &) const override; + void Enter(AIController &, Entity &) const override; void Update(AIController &, Entity &, float dt) const override; - void Exit(AIController &) const override; + void Exit(AIController &, Entity &) const override; }; diff --git a/src/ai/RoamState.hpp b/src/ai/RoamState.hpp index 3a560e0..321e1f4 100644 --- a/src/ai/RoamState.hpp +++ b/src/ai/RoamState.hpp @@ -13,9 +13,9 @@ namespace blank { class RoamState : public AIState { - void Enter(AIController &) const override; + void Enter(AIController &, Entity &) const override; void Update(AIController &, Entity &, float dt) const override; - void Exit(AIController &) const override; + void Exit(AIController &, Entity &) const override; }; diff --git a/src/ai/Spawner.cpp b/src/ai/Spawner.cpp index fabc6dd..1f9573b 100644 --- a/src/ai/Spawner.cpp +++ b/src/ai/Spawner.cpp @@ -18,11 +18,10 @@ using namespace std; namespace blank { -Spawner::Spawner(World &world, ModelRegistry &models, GaloisLFSR &rand) +Spawner::Spawner(World &world, ModelRegistry &models) : world(world) , models(models) , entities() -, random(rand) , timer(64) , despawn_range(128 * 128) , spawn_distance(16 * 16) @@ -93,13 +92,13 @@ void Spawner::TrySpawn() { // select random player to punish auto &players = world.Players(); if (players.size() == 0) return; - size_t player_num = random.Next() % players.size(); + size_t player_num = world.Random().Next() % players.size(); auto i = players.begin(), end = players.end(); for (; player_num > 0 && i != end; ++i, --player_num) { } const Player &player = *i; - BlockLookup spawn_block(player.GetChunks().RandomBlock(random)); + BlockLookup spawn_block(player.GetChunks().RandomBlock(world.Random())); // distance check //glm::vec3 diff(glm::vec3(chunk * Chunk::Extent() - pos) + player.entity->Position()); @@ -127,14 +126,14 @@ void Spawner::Spawn(Entity &reference, const glm::ivec3 &chunk, const glm::vec3 e.Bounds({ { -0.5f, -0.5f, -0.5f }, { 0.5f, 0.5f, 0.5f } }); e.WorldCollidable(true); RandomModel().Instantiate(e.GetModel()); - e.SetController(new AIController(world, random)); + e.SetController(new AIController(world, e)); e.Name("spawned"); e.Ref(); entities.emplace_back(&e); } Model &Spawner::RandomModel() noexcept { - std::size_t offset = (random.Next() % model_length) + model_offset; + std::size_t offset = (world.Random().Next() % model_length) + model_offset; return models[offset]; } diff --git a/src/ai/Spawner.hpp b/src/ai/Spawner.hpp index f99029e..72d2ac1 100644 --- a/src/ai/Spawner.hpp +++ b/src/ai/Spawner.hpp @@ -10,7 +10,6 @@ namespace blank { class Entity; -class GaloisLFSR; class Model; class ModelRegistry; class World; @@ -18,7 +17,7 @@ class World; class Spawner { public: - Spawner(World &, ModelRegistry &, GaloisLFSR &); + Spawner(World &, ModelRegistry &); ~Spawner(); void LimitModels(std::size_t begin, std::size_t end); @@ -37,8 +36,6 @@ private: ModelRegistry ⊧ std::vector entities; - GaloisLFSR &random; - CoarseTimer timer; float despawn_range; float spawn_distance; diff --git a/src/ai/ai.cpp b/src/ai/ai.cpp index 4b76856..820eb4d 100644 --- a/src/ai/ai.cpp +++ b/src/ai/ai.cpp @@ -27,49 +27,26 @@ RoamState roam; } -AIController::AIController(World &world, GaloisLFSR &rand) +AIController::AIController(World &world, Entity &entity) : world(world) -, random(rand) , state(&idle) , sight_dist(64.0f) , sight_angle(0.707f) , think_timer(0.5f) -, decision_timer(1.0f) -, halted(false) -, halt_speed(1.0f) -, avoid_obstacles(true) -, obstacle_box{ glm::vec3(0.0f), glm::vec3(0.0f) } -, obstacle_transform(1.0f) -, fleeing(false) -, flee_target(nullptr) -, flee_speed(5.0f) -, seeking(false) -, seek_target(nullptr) -, seek_speed(5.0f) -, evading(false) -, evade_target(nullptr) -, evade_speed(5.0f) -, pursuing(false) -, pursuit_target(nullptr) -, pursuit_speed(5.0f) -, wandering(false) -, wander_pos(1.0f, 0.0f, 0.0f) -, wander_speed(1.0f) -, wander_dist(2.0f) -, wander_radius(1.5f) -, wander_disp(1.0f) { +, decision_timer(1.0f) { think_timer.Start(); - state->Enter(*this); + state->Enter(*this, entity); } AIController::~AIController() { - state->Exit(*this); + // ignore this for now + // state->Exit(*this, entity); } -void AIController::SetState(const AIState &s) { - state->Exit(*this); +void AIController::SetState(const AIState &s, Entity &entity) { + state->Exit(*this, entity); state = &s; - state->Enter(*this); + state->Enter(*this, entity); } void AIController::Update(Entity &e, float dt) { @@ -77,27 +54,6 @@ void AIController::Update(Entity &e, float dt) { decision_timer.Update(dt); state->Update(*this, e, dt); - if (avoid_obstacles && e.Moving()) { - obstacle_box = e.Bounds(); - obstacle_box.min.z = -e.Speed(); - obstacle_box.max.x = 0.0f; - // our box is oriented for -Z velocity - obstacle_transform = glm::mat4(find_rotation(glm::vec3(0.0f, 0.0f, -1.0f), e.Heading())); - // and positioned relative to the entity's chunk - obstacle_transform[3] = glm::vec4(e.GetState().pos.block, 1.0f); - } - - if (wandering) { - glm::vec3 displacement( - random.SNorm() * wander_disp, - random.SNorm() * wander_disp, - random.SNorm() * wander_disp - ); - if (!iszero(displacement)) { - wander_pos = normalize(wander_pos + displacement * dt) * wander_radius; - } - } - if (e.Moving()) { // orient head towards heading glm::vec3 heading(e.Heading()); @@ -111,44 +67,6 @@ void AIController::Update(Entity &e, float dt) { } } -glm::vec3 AIController::ControlForce(const Entity &entity, const EntityState &state) const { - if (IsHalted()) { - return GetHaltForce(entity, state); - } - glm::vec3 force(0.0f); - if (IsAvoidingObstacles() && entity.Moving()) { - if (MaxOutForce(force, GetObstacleAvoidanceForce(entity, state), entity.MaxControlForce())) { - return force; - } - } - if (IsFleeing()) { - if (MaxOutForce(force, GetFleeForce(entity, state), entity.MaxControlForce())) { - return force; - } - } - if (IsSeeking()) { - if (MaxOutForce(force, GetSeekForce(entity, state), entity.MaxControlForce())) { - return force; - } - } - if (IsEvading()) { - if (MaxOutForce(force, GetEvadeForce(entity, state), entity.MaxControlForce())) { - return force; - } - } - if (IsPursuing()) { - if (MaxOutForce(force, GetPursuitForce(entity, state), entity.MaxControlForce())) { - return force; - } - } - if (IsWandering()) { - if (MaxOutForce(force, GetWanderForce(entity, state), entity.MaxControlForce())) { - return force; - } - } - return force; -} - Player *AIController::ClosestVisiblePlayer(const Entity &e) noexcept { Player *target = nullptr; float distance = sight_dist; @@ -212,7 +130,7 @@ void AIController::CueDecision( float minimum, float variance ) noexcept { - decision_timer = FineTimer(minimum + variance * random.SNorm()); + decision_timer = FineTimer(minimum + variance * world.Random().SNorm()); decision_timer.Start(); } @@ -221,354 +139,76 @@ bool AIController::DecisionDue() const noexcept { } unsigned int AIController::Decide(unsigned int num_choices) noexcept { - return random.Next() % num_choices; -} - -// halt - -void AIController::EnterHalt() noexcept { - halted = true; -} - -void AIController::ExitHalt() noexcept { - halted = false; -} - -bool AIController::IsHalted() const noexcept { - return halted; -} - -void AIController::SetHaltSpeed(float speed) noexcept { - halt_speed = speed; -} - -glm::vec3 AIController::GetHaltForce(const Entity &, const EntityState &state) const noexcept { - return Halt(state, halt_speed); -} - -// obstacle avoidance - -void AIController::StartAvoidingObstacles() noexcept { - avoid_obstacles = true; -} - -void AIController::StopAvoidingObstacles() noexcept { - avoid_obstacles = false; -} - -bool AIController::IsAvoidingObstacles() const noexcept { - return avoid_obstacles; -} - -namespace { - -std::vector col; - -} - -glm::vec3 AIController::GetObstacleAvoidanceForce(const Entity &e, const EntityState &state) const noexcept { - if (!e.Moving()) { - return glm::vec3(0.0f); - } - col.clear(); - if (!world.Intersection(obstacle_box, obstacle_transform, e.ChunkCoords(), col)) { - return glm::vec3(0.0f); - } - // find the nearest block - WorldCollision *nearest = nullptr; - glm::vec3 difference(0.0f); - float distance = std::numeric_limits::infinity(); - for (WorldCollision &c : col) { - // diff points from block to state - glm::vec3 diff = state.RelativePosition(c.ChunkPos()) - c.BlockCoords(); - float dist = length2(diff); - if (dist < distance) { - nearest = &c; - difference = diff; - distance = dist; - } - } - if (!nearest) { - // intersection test lied to us - return glm::vec3(0.0f); - } - // and steer away from it - // to_go is the distance between our position and the - // point on the "velocity ray" closest to obstacle - float to_go = dot(difference, e.Heading()); - // point is our future position if we keep going our way - glm::vec3 point(e.GetState().pos.block + e.Heading() * to_go); - // now steer away in the direction of (point - block) - // with a magniture proportional to speed/distance - return normalize(point - nearest->BlockCoords()) * (e.Speed() / std::sqrt(distance)); -} - -// flee - -void AIController::StartFleeing() noexcept { - fleeing = true; -} - -void AIController::StopFleeing() noexcept { - fleeing = false; - if (flee_target) { - flee_target->UnRef(); - flee_target = nullptr; - } -} - -bool AIController::IsFleeing() const noexcept { - return fleeing && flee_target; -} - -void AIController::SetFleeTarget(Entity &e) noexcept { - if (flee_target) { - flee_target->UnRef(); - } - flee_target = &e; - flee_target->Ref(); -} - -void AIController::SetFleeSpeed(float speed) noexcept { - flee_speed = speed; -} - -Entity &AIController::GetFleeTarget() noexcept { - return *flee_target; -} - -const Entity &AIController::GetFleeTarget() const noexcept { - return *flee_target; -} - -glm::vec3 AIController::GetFleeForce(const Entity &, const EntityState &state) const noexcept { - return Flee(state, GetFleeTarget().GetState(), flee_speed, 2.0f); -} - -// seek - -void AIController::StartSeeking() noexcept { - seeking = true; -} - -void AIController::StopSeeking() noexcept { - seeking = false; - if (seek_target) { - seek_target->UnRef(); - seek_target = nullptr; - } -} - -bool AIController::IsSeeking() const noexcept { - return seeking && seek_target; -} - -void AIController::SetSeekTarget(Entity &e) noexcept { - if (seek_target) { - seek_target->UnRef(); - } - seek_target = &e; - seek_target->Ref(); -} - -void AIController::SetSeekSpeed(float speed) noexcept { - seek_speed = speed; -} - -Entity &AIController::GetSeekTarget() noexcept { - return *seek_target; -} - -const Entity &AIController::GetSeekTarget() const noexcept { - return *seek_target; -} - -glm::vec3 AIController::GetSeekForce(const Entity &, const EntityState &state) const noexcept { - return Seek(state, GetSeekTarget().GetState(), seek_speed, 2.0f); -} - -// evade - -void AIController::StartEvading() noexcept { - evading = true; -} - -void AIController::StopEvading() noexcept { - evading = false; - if (evade_target) { - evade_target->UnRef(); - evade_target = nullptr; - } -} - -bool AIController::IsEvading() const noexcept { - return evading && evade_target; -} - -void AIController::SetEvadeTarget(Entity &e) noexcept { - if (evade_target) { - evade_target->UnRef(); - } - evade_target = &e; - evade_target->Ref(); -} - -void AIController::SetEvadeSpeed(float speed) noexcept { - evade_speed = speed; -} - -Entity &AIController::GetEvadeTarget() noexcept { - return *evade_target; -} - -const Entity &AIController::GetEvadeTarget() const noexcept { - return *evade_target; -} - -glm::vec3 AIController::GetEvadeForce(const Entity &, const EntityState &state) const noexcept{ - glm::vec3 cur_diff(state.Diff(GetEvadeTarget().GetState())); - float time_estimate = length(cur_diff) / evade_speed; - EntityState pred_state(GetEvadeTarget().GetState()); - pred_state.pos.block += pred_state.velocity * time_estimate; - return Flee(state, pred_state, evade_speed, 2.0f); -} - -// pursuit - -void AIController::StartPursuing() noexcept { - pursuing = true; -} - -void AIController::StopPursuing() noexcept { - pursuing = false; - if (pursuit_target) { - pursuit_target->UnRef(); - pursuit_target = nullptr; - } -} - -bool AIController::IsPursuing() const noexcept { - return pursuing && pursuit_target; -} - -void AIController::SetPursuitTarget(Entity &e) noexcept { - if (pursuit_target) { - pursuit_target->UnRef(); - } - pursuit_target = &e; - pursuit_target->Ref(); -} - -void AIController::SetPursuitSpeed(float speed) noexcept { - pursuit_speed = speed; -} - -Entity &AIController::GetPursuitTarget() noexcept { - return *pursuit_target; -} - -const Entity &AIController::GetPursuitTarget() const noexcept { - return *pursuit_target; -} - -glm::vec3 AIController::GetPursuitForce(const Entity &, const EntityState &state) const noexcept { - glm::vec3 cur_diff(state.Diff(GetPursuitTarget().GetState())); - float time_estimate = length(cur_diff) / pursuit_speed; - EntityState pred_state(GetPursuitTarget().GetState()); - pred_state.pos.block += pred_state.velocity * time_estimate; - return Seek(state, pred_state, pursuit_speed, 2.0f); -} - -// wander - -void AIController::StartWandering() noexcept { - wandering = true; -} - -void AIController::StopWandering() noexcept { - wandering = false; -} - -bool AIController::IsWandering() const noexcept { - return wandering; -} - -void AIController::SetWanderParams( - float speed, - float distance, - float radius, - float displacement -) noexcept { - wander_speed = speed; - wander_dist = distance; - wander_radius = radius; - wander_disp = displacement; -} - -glm::vec3 AIController::GetWanderForce(const Entity &e, const EntityState &state) const noexcept { - glm::vec3 wander_target(normalize(e.Heading() * wander_dist + wander_pos) * wander_speed); - return TargetVelocity(wander_target, state, 0.5f); + return world.Random().Next() % num_choices; } // chase -void ChaseState::Enter(AIController &ctrl) const { - ctrl.SetHaltSpeed(2.0f); - ctrl.SetPursuitSpeed(4.0f); - ctrl.StartPursuing(); +void ChaseState::Enter(AIController &ctrl, Entity &e) const { + e.GetSteering() + .SetAcceleration(1.0f) + .SetSpeed(4.0f) + .Enable(Steering::PURSUE_TARGET) + ; } void ChaseState::Update(AIController &ctrl, Entity &e, float dt) const { + Steering &steering = e.GetSteering(); // check if target still alive and in sight - if (ctrl.GetPursuitTarget().Dead()) { - ctrl.SetState(idle); - return; - } - if (!ctrl.LineOfSight(e, ctrl.GetPursuitTarget())) { - ctrl.SetState(idle); + if ( + !steering.HasTargetEntity() || // lost + steering.GetTargetEntity().Dead() || // dead + !ctrl.LineOfSight(e, steering.GetTargetEntity()) // escaped + ) { + steering.ClearTargetEntity(); + ctrl.SetState(idle, e); return; } // halt if we're close enough, flee if we're too close - float dist_sq = length2(e.AbsoluteDifference(ctrl.GetPursuitTarget())); + float dist_sq = length2(e.AbsoluteDifference(steering.GetTargetEntity())); if (dist_sq < 8.0f) { - ctrl.SetFleeTarget(ctrl.GetPursuitTarget()); - ctrl.SetState(flee); + ctrl.SetState(flee, e); } else if (dist_sq < 25.0f) { - ctrl.EnterHalt(); + steering.Enable(Steering::HALT).Disable(Steering::PURSUE_TARGET); } else { - ctrl.ExitHalt(); + steering.Enable(Steering::PURSUE_TARGET).Disable(Steering::HALT); } } -void ChaseState::Exit(AIController &ctrl) const { - ctrl.StopPursuing(); - ctrl.ExitHalt(); +void ChaseState::Exit(AIController &ctrl, Entity &e) const { + e.GetSteering().Disable(Steering::HALT | Steering::PURSUE_TARGET); } // flee -void FleeState::Enter(AIController &ctrl) const { +void FleeState::Enter(AIController &ctrl, Entity &e) const { + e.GetSteering() + .SetAcceleration(1.0f) + .SetSpeed(4.0f) + .Enable(Steering::EVADE_TARGET) + ; ctrl.CueDecision(6.0f, 3.0f); - ctrl.SetFleeSpeed(4.0f); - ctrl.StartFleeing(); } void FleeState::Update(AIController &ctrl, Entity &e, float dt) const { if (!ctrl.DecisionDue()) return; - ctrl.SetState(idle); + ctrl.SetState(idle, e); } -void FleeState::Exit(AIController &ctrl) const { - ctrl.StopFleeing(); +void FleeState::Exit(AIController &ctrl, Entity &e) const { + e.GetSteering().Disable(Steering::EVADE_TARGET); } // idle -void IdleState::Enter(AIController &ctrl) const { - ctrl.SetHaltSpeed(0.5f); - ctrl.EnterHalt(); - ctrl.SetWanderParams(0.001f, 1.1f); +void IdleState::Enter(AIController &ctrl, Entity &e) const { + e.GetSteering() + .SetAcceleration(0.5f) + .SetSpeed(0.01f) + .Enable(Steering::HALT) + .SetWanderParams(1.0f, 1.1f, 1.0f) + ; ctrl.CueDecision(10.0f, 5.0f); } @@ -576,8 +216,8 @@ void IdleState::Update(AIController &ctrl, Entity &e, float dt) const { if (ctrl.MayThink()) { const Player *player = ctrl.ClosestVisiblePlayer(e); if (player) { - ctrl.SetPursuitTarget(player->GetEntity()); - ctrl.SetState(chase); + e.GetSteering().SetTargetEntity(player->GetEntity()); + ctrl.SetState(chase, e); return; } } @@ -587,29 +227,30 @@ void IdleState::Update(AIController &ctrl, Entity &e, float dt) const { unsigned int d = ctrl.Decide(10); if (d < 2) { // .2 chance to start going - ctrl.SetState(roam); + ctrl.SetState(roam, e); } else if (d < 5) { // .3 chance of looking around - ctrl.ExitHalt(); - ctrl.StartWandering(); + e.GetSteering().Disable(Steering::HALT).Enable(Steering::WANDER); } else { // .5 chance of doing nothing - ctrl.StopWandering(); - ctrl.EnterHalt(); + e.GetSteering().Disable(Steering::WANDER).Enable(Steering::HALT); } ctrl.CueDecision(10.0f, 5.0f); } -void IdleState::Exit(AIController &ctrl) const { - ctrl.ExitHalt(); - ctrl.StopWandering(); +void IdleState::Exit(AIController &ctrl, Entity &e) const { + e.GetSteering().Disable(Steering::HALT | Steering::WANDER); } // roam -void RoamState::Enter(AIController &ctrl) const { - ctrl.SetWanderParams(1.0f); - ctrl.StartWandering(); +void RoamState::Enter(AIController &ctrl, Entity &e) const { + e.GetSteering() + .SetAcceleration(0.5f) + .SetSpeed(1.0f) + .SetWanderParams(1.0f, 2.0f, 1.0f) + .Enable(Steering::WANDER) + ; ctrl.CueDecision(10.0f, 5.0f); } @@ -617,8 +258,8 @@ void RoamState::Update(AIController &ctrl, Entity &e, float dt) const { if (ctrl.MayThink()) { const Player *player = ctrl.ClosestVisiblePlayer(e); if (player) { - ctrl.SetPursuitTarget(player->GetEntity()); - ctrl.SetState(chase); + e.GetSteering().SetTargetEntity(player->GetEntity()); + ctrl.SetState(chase, e); return; } } @@ -628,13 +269,13 @@ void RoamState::Update(AIController &ctrl, Entity &e, float dt) const { unsigned int d = ctrl.Decide(10); if (d == 0) { // .1 chance of idling - ctrl.SetState(idle); + ctrl.SetState(idle, e); } ctrl.CueDecision(10.0f, 5.0f); } -void RoamState::Exit(AIController &ctrl) const { - ctrl.StopWandering(); +void RoamState::Exit(AIController &ctrl, Entity &e) const { + e.GetSteering().Disable(Steering::WANDER); } } diff --git a/src/app/Environment.hpp b/src/app/Environment.hpp index 7061000..2cab670 100644 --- a/src/app/Environment.hpp +++ b/src/app/Environment.hpp @@ -6,7 +6,6 @@ #include "StateControl.hpp" #include "../audio/Audio.hpp" #include "../graphics/Viewport.hpp" -#include "../rand/GaloisLFSR.hpp" #include "../shared/MessageState.hpp" #include "../ui/Keymap.hpp" @@ -38,8 +37,6 @@ struct HeadlessEnvironment { StateControl state; - GaloisLFSR rng; - explicit HeadlessEnvironment(const Config &); diff --git a/src/app/runtime.cpp b/src/app/runtime.cpp index c80fa76..2481f93 100644 --- a/src/app/runtime.cpp +++ b/src/app/runtime.cpp @@ -118,17 +118,8 @@ HeadlessEnvironment::HeadlessEnvironment(const Config &config) : config(config) , loader(config.asset_path) , counter() -, state() -, rng( -#ifdef BLANK_PROFILING -0 -#else -std::time(nullptr) -#endif -){ - for (int i = 0; i < 4; ++i) { - rng.Next(); - } +, state() { + } string HeadlessEnvironment::Config::GetWorldPath(const string &world_name) const { diff --git a/src/server/ServerState.cpp b/src/server/ServerState.cpp index bb6a8a9..860b71f 100644 --- a/src/server/ServerState.cpp +++ b/src/server/ServerState.cpp @@ -22,7 +22,7 @@ ServerState::ServerState( , world(res.block_types, wc) , generator(gc) , chunk_loader(world.Chunks(), generator, ws) -, spawner(world, res.models, env.rng) +, spawner(world, res.models) , server(config.net, world, wc, ws) , loop_timer(16) { res.Load(env.loader, "default"); diff --git a/src/standalone/standalone.cpp b/src/standalone/standalone.cpp index 2ffe6bc..3c3487c 100644 --- a/src/standalone/standalone.cpp +++ b/src/standalone/standalone.cpp @@ -59,7 +59,7 @@ MasterState::MasterState( , generator(gc) , chunk_loader(world.Chunks(), generator, save) , chunk_renderer(player.GetChunks()) -, spawner(world, res.models, env.rng) +, spawner(world, res.models) , sky(env.loader.LoadCubeMap("skybox")) , cli(world) , cli_ctx(player, hud) diff --git a/src/ui/PlayerController.hpp b/src/ui/PlayerController.hpp index 9cf0bca..48c3bdc 100644 --- a/src/ui/PlayerController.hpp +++ b/src/ui/PlayerController.hpp @@ -35,8 +35,6 @@ public: void SetMovement(const glm::vec3 &) noexcept; const glm::vec3 &GetMovement() const noexcept { return move_dir; } - glm::vec3 ControlForce(const Entity &, const EntityState &) const override; - /// turn the controlled entity's head by given pitch and yaw deltas void TurnHead(float pitch, float yaw) noexcept; diff --git a/src/ui/ui.cpp b/src/ui/ui.cpp index 838b1ee..bd529d5 100644 --- a/src/ui/ui.cpp +++ b/src/ui/ui.cpp @@ -43,6 +43,7 @@ PlayerController::PlayerController(World &world, Player &player) , aim_world() , aim_entity() { player.GetEntity().SetController(*this); + player.GetEntity().GetSteering().SetAcceleration(5.0f); } PlayerController::~PlayerController() { @@ -60,16 +61,6 @@ void PlayerController::SetMovement(const glm::vec3 &m) noexcept { Invalidate(); } -glm::vec3 PlayerController::ControlForce(const Entity &e, const EntityState &s) const { - if (!iszero(move_dir)) { - // scale input by max velocity, apply yaw, and transform to world space - return TargetVelocity(glm::vec3(glm::vec4(rotateY(move_dir * e.MaxVelocity(), s.yaw), 0.0f) * transpose(e.Transform())), s, 5.0f); - } else { - // target velocity of 0 is the same as halt - return Halt(s, 5.0f); - } -} - void PlayerController::TurnHead(float dp, float dy) noexcept { player.GetEntity().TurnHead(dp, dy); } @@ -97,20 +88,36 @@ void PlayerController::Invalidate() noexcept { void PlayerController::UpdatePlayer() noexcept { if (dirty) { Ray aim = player.Aim(); - if (!world.Intersection(aim, player.GetEntity().ChunkCoords(), aim_world)) { + Entity &entity = player.GetEntity(); + if (!world.Intersection(aim, entity.ChunkCoords(), aim_world)) { aim_world = WorldCollision(); } - if (!world.Intersection(aim, player.GetEntity(), aim_entity)) { + if (!world.Intersection(aim, entity, aim_entity)) { aim_entity = EntityCollision(); } if (aim_world && aim_entity) { // got both, pick the closest one if (aim_world.depth < aim_entity.depth) { + // FIXME: somehow this can get stuck on an entity? aim_entity = EntityCollision(); } else { aim_world = WorldCollision(); } } + Steering &steering = entity.GetSteering(); + if (!iszero(move_dir)) { + // scale input by max velocity, apply yaw, and transform to world space + steering.SetTargetVelocity(glm::vec3( + glm::vec4(rotateY(move_dir * entity.MaxVelocity(), entity.Yaw()), 0.0f) + * transpose(entity.Transform()) + )); + steering.Enable(Steering::TARGET_VELOCITY); + steering.Disable(Steering::HALT); + } else { + // target velocity of 0 is the same as halt + steering.Enable(Steering::HALT); + steering.Disable(Steering::TARGET_VELOCITY); + } dirty = false; } } diff --git a/src/world/Entity.hpp b/src/world/Entity.hpp index 0117fa9..36f6868 100644 --- a/src/world/Entity.hpp +++ b/src/world/Entity.hpp @@ -4,6 +4,7 @@ #include "Chunk.hpp" #include "EntityDerivative.hpp" #include "EntityState.hpp" +#include "Steering.hpp" #include "../geometry/primitive.hpp" #include "../model/Instance.hpp" @@ -32,6 +33,9 @@ public: Entity(const Entity &) noexcept; Entity &operator =(const Entity &) = delete; + Steering &GetSteering() noexcept { return steering; } + const Steering &GetSteering() const noexcept { return steering; } + bool HasController() const noexcept { return ctrl; } // entity takes over ownership of controller void SetController(EntityController *c) noexcept; @@ -142,6 +146,7 @@ private: private: + Steering steering; EntityController *ctrl; Instance model; diff --git a/src/world/EntityController.hpp b/src/world/EntityController.hpp index ce3d0cf..2724c5e 100644 --- a/src/world/EntityController.hpp +++ b/src/world/EntityController.hpp @@ -1,11 +1,6 @@ #ifndef BLANK_WORLD_ENTITYCONTROLLER_HPP_ #define BLANK_WORLD_ENTITYCONTROLLER_HPP_ -#include "EntityState.hpp" - -#include - - namespace blank { class Entity; @@ -16,52 +11,6 @@ struct EntityController { virtual void Update(Entity &, float dt) = 0; - virtual glm::vec3 ControlForce(const Entity &, const EntityState &) const = 0; - - - /// try to add as much of add to out so it doesn't exceed max - /// returns true if it's maxed out - static bool MaxOutForce( - glm::vec3 &out, - const glm::vec3 &add, - float max - ) noexcept; - /// give a force that makes state come to a halt over 1/n seconds - static inline glm::vec3 Halt( - const EntityState &state, - float n - ) noexcept { - return state.velocity * -n; - } - /// give a force that makes state's velocity converge with given - /// target velocity over 1/n seconds - static inline glm::vec3 TargetVelocity( - const glm::vec3 &target, - const EntityState &state, - float n - ) noexcept { - return (target - state.velocity) * n; - } - /// give a force that makes state go after target with an attempted - /// acceleration over 1/n seconds and a speed of s - static inline glm::vec3 Seek( - const EntityState &state, - const EntityState &target, - float s, - float n - ) noexcept { - return TargetVelocity(normalize(target.Diff(state)) * s, state, n); - } - /// opposite of seek - static inline glm::vec3 Flee( - const EntityState &state, - const EntityState &target, - float s, - float n - ) noexcept { - return TargetVelocity(normalize(state.Diff(target)) * s, state, n); - } - }; } diff --git a/src/world/Steering.hpp b/src/world/Steering.hpp new file mode 100644 index 0000000..d6ec57f --- /dev/null +++ b/src/world/Steering.hpp @@ -0,0 +1,120 @@ +#ifndef BLANK_WORLD_STEERING_HPP +#define BLANK_WORLD_STEERING_HPP + +#include "../geometry/Location.hpp" +#include "../geometry/primitive.hpp" + +#include + + +namespace blank { + +class Entity; +class EntityState; +class World; + +class Steering { + +public: + enum Behaviour { + // standalone behaviours + HALT = 0x0001, + WANDER = 0x0002, + OBSTACLE_AVOIDANCE = 0x0004, + + // requires target velocity + TARGET_VELOCITY = 0x0008, + + // behaviours requiring a target entity + EVADE_TARGET = 0x0010, + PURSUE_TARGET = 0x0020, + }; + + explicit Steering(const Entity &); + ~Steering(); + + Steering &Enable(unsigned int b) noexcept { enabled |= b; return *this; } + Steering &Disable(unsigned int b) noexcept { enabled &= ~b; return *this; } + bool AnyEnabled(unsigned int b) const noexcept { return enabled & b; } + bool AllEnabled(unsigned int b) const noexcept { return (enabled & b) == b; } + + Steering &SetTargetEntity(Entity &) noexcept; + Steering &ClearTargetEntity() noexcept; + bool HasTargetEntity() const noexcept { return target_entity; } + const Entity &GetTargetEntity() const noexcept { return *target_entity; } + + Steering &SetTargetVelocity(const glm::vec3 &v) noexcept { target_velocity = v; return *this; } + const glm::vec3 &GetTargetVelocity() const noexcept { return target_velocity; } + + /// time in seconds in which steering tried to arrive at target velocity + Steering &SetAcceleration(float a) noexcept { accel = a; return *this; } + /// maximum magnitude of velocity that behaviours generate + Steering &SetSpeed(float s) noexcept { speed = s; return *this; } + + /// configure wandering + /// r is the radius of the sphere + /// dist is the distance between the entity and the sphere's center + /// disp is the maximum variance of the point on the sphere in units per second + Steering &SetWanderParams(float r, float dist, float disp) noexcept { + wander_radius = r; + wander_dist = dist; + wander_disp = disp; + return *this; + } + + void Update(World &, float dt); + + glm::vec3 Force(const EntityState &) const noexcept; + +private: + void UpdateWander(World &, float dt); + void UpdateObstacle(World &); + + /// try to add as much of in to out so it doesn't exceed max + /// returns true if it's maxed out + static bool SumForce(glm::vec3 &out, const glm::vec3 &in, float max) noexcept; + + /// slow down to a halt + glm::vec3 Halt(const EntityState &) const noexcept; + /// accelerate to match given velocity + glm::vec3 TargetVelocity(const EntityState &, const glm::vec3 &) const noexcept; + /// move towards given location + glm::vec3 Seek(const EntityState &, const ExactLocation &) const noexcept; + /// move away from given location + glm::vec3 Flee(const EntityState &, const ExactLocation &) const noexcept; + /// try to halt at given location + glm::vec3 Arrive(const EntityState &, const ExactLocation &) const noexcept; + /// seek given entity's predicted position + glm::vec3 Pursuit(const EntityState &, const Entity &) const noexcept; + /// flee given entity's predicted position + glm::vec3 Evade(const EntityState &, const Entity &) const noexcept; + /// move around for no reason + glm::vec3 Wander(const EntityState &) const noexcept; + /// try not to crash into blocks + glm::vec3 ObstacleAvoidance(const EntityState &) const noexcept; + +private: + const Entity &entity; + + Entity *target_entity; + glm::vec3 target_velocity; + + float accel; + float speed; + + float wander_radius; + float wander_dist; + float wander_disp; + glm::vec3 wander_pos; + + glm::vec3 obstacle_dir; + + unsigned int enabled; + + + +}; + +} + +#endif diff --git a/src/world/World.hpp b/src/world/World.hpp index 7936f98..40136c8 100644 --- a/src/world/World.hpp +++ b/src/world/World.hpp @@ -5,6 +5,7 @@ #include "Entity.hpp" #include "Generator.hpp" #include "Player.hpp" +#include "../rand/GaloisLFSR.hpp" #include #include @@ -41,6 +42,9 @@ public: const std::string &Name() const noexcept { return config.name; } + /// get the shared random source for this world + GaloisLFSR &Random() noexcept { return rng; } + /// check if this ray hits a block /// depth in the collision is the distance between the ray's /// origin and the intersection point @@ -130,6 +134,8 @@ private: std::list players; std::list entities; + GaloisLFSR rng; + glm::vec3 light_direction; float fog_density; diff --git a/src/world/world.cpp b/src/world/world.cpp index 4f6d908..d933dc4 100644 --- a/src/world/world.cpp +++ b/src/world/world.cpp @@ -12,6 +12,7 @@ #include "../app/Assets.hpp" #include "../geometry/const.hpp" #include "../geometry/distance.hpp" +#include "../geometry/rotation.hpp" #include "../graphics/Format.hpp" #include "../graphics/Viewport.hpp" @@ -29,8 +30,16 @@ namespace blank { +namespace { + +/// used as a buffer for merging collisions +std::vector col; + +} + Entity::Entity() noexcept -: ctrl(nullptr) +: steering(*this) +, ctrl(nullptr) , model() , id(-1) , name("anonymous") @@ -52,7 +61,8 @@ Entity::~Entity() noexcept { } Entity::Entity(const Entity &other) noexcept -: ctrl(other.ctrl) +: steering(*this) +, ctrl(other.ctrl) , model(other.model) , id(-1) , name(other.name) @@ -91,14 +101,7 @@ void Entity::UnsetController() noexcept { } glm::vec3 Entity::ControlForce(const EntityState &s) const noexcept { - glm::vec3 force; - if (HasController()) { - force = GetController().ControlForce(*this, s); - } else { - force = -s.velocity; - } - limit(force, max_force); - return force; + return steering.Force(s); } void Entity::Position(const glm::ivec3 &c, const glm::vec3 &b) noexcept { @@ -137,6 +140,7 @@ void Entity::Update(World &world, float dt) { if (HasController()) { GetController().Update(*this, dt); } + steering.Update(world, dt); UpdatePhysics(world, dt); UpdateTransforms(); UpdateHeading(); @@ -330,29 +334,6 @@ EntityController::~EntityController() { } -bool EntityController::MaxOutForce( - glm::vec3 &out, - const glm::vec3 &add, - float max -) noexcept { - if (iszero(add) || any(isnan(add))) { - return false; - } - float current = iszero(out) ? 0.0f : length(out); - float remain = max - current; - if (remain <= 0.0f) { - return true; - } - float additional = length(add); - if (additional > remain) { - out += normalize(add) * remain; - return true; - } else { - out += add; - return false; - } -} - EntityState::EntityState() : pos() @@ -414,15 +395,253 @@ void Player::Update(int dt) { } +Steering::Steering(const Entity &e) +: entity(e) +, target_entity(nullptr) +, target_velocity(0.0f) +, accel(1.0f) +, speed(entity.MaxVelocity()) +, wander_radius(1.0f) +, wander_dist(2.0f) +, wander_disp(1.0f) +, wander_pos(1.0f, 0.0f, 0.0f) +, obstacle_dir(0.0f) +, enabled(0) { + +} + +Steering::~Steering() { + ClearTargetEntity(); +} + +Steering &Steering::SetTargetEntity(Entity &e) noexcept { + ClearTargetEntity(); + target_entity = &e; + e.Ref(); + return *this; +} + +Steering &Steering::ClearTargetEntity() noexcept { + if (target_entity) { + target_entity->UnRef(); + target_entity = nullptr; + } + return *this; +} + +void Steering::Update(World &world, float dt) { + if (AnyEnabled(WANDER)) { + UpdateWander(world, dt); + } + if (AnyEnabled(OBSTACLE_AVOIDANCE)) { + UpdateObstacle(world); + } +} + +void Steering::UpdateWander(World &world, float dt) { + glm::vec3 displacement( + world.Random().SNorm() * wander_disp, + world.Random().SNorm() * wander_disp, + world.Random().SNorm() * wander_disp + ); + if (!iszero(displacement)) { + wander_pos = normalize(wander_pos + displacement * dt) * wander_radius; + } +} + +void Steering::UpdateObstacle(World &world) { + if (!entity.Moving()) { + obstacle_dir = glm::vec3(0.0f); + return; + } + AABB box(entity.Bounds()); + box.min.z = -entity.Speed(); + box.max.z = 0.0f; + glm::mat4 transform(find_rotation(glm::vec3(0.0f, 0.0f, -1.0f), entity.Heading())); + transform[3] = glm::vec4(entity.Position(), 1.0f); + // check if that box intersects with any blocks + col.clear(); + if (!world.Intersection(box, transform, entity.ChunkCoords(), col)) { + obstacle_dir = glm::vec3(0.0f); + return; + } + // if so, pick the nearest collision + const WorldCollision *nearest = nullptr; + glm::vec3 difference(0.0f); + float distance = std::numeric_limits::infinity(); + for (const WorldCollision &c : col) { + // diff points from block to state + glm::vec3 diff = entity.GetState().RelativePosition(c.ChunkPos()) - c.BlockCoords(); + float dist = length2(diff); + if (dist < distance) { + nearest = &c; + difference = diff; + distance = dist; + } + } + if (!nearest) { + // intersection test lied to us + obstacle_dir = glm::vec3(0.0f); + return; + } + // and try to avoid it + float to_go = dot(difference, entity.Heading()); + glm::vec3 point(entity.Position() + entity.Heading() * to_go); + obstacle_dir = normalize(point - nearest->BlockCoords()) * (entity.Speed() / std::sqrt(distance)); +} + +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) || any(isnan(in))) { + return false; + } + float current = iszero(out) ? 0.0f : length(out); + float remain = max - current; + if (remain <= 0.0f) { + return true; + } + float additional = length(in); + if (additional > remain) { + out += 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, 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, 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 = length(diff); + if (dist < std::numeric_limits::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 = 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 = 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, normalize(entity.Heading() * wander_dist + wander_pos) * speed); +} + +glm::vec3 Steering::ObstacleAvoidance(const EntityState &state) const noexcept { + return obstacle_dir; +} + + World::World(const BlockTypeRegistry &types, const Config &config) : config(config) , block_type(types) , chunks(types) , players() , entities() +, rng( +#ifdef BLANK_PROFILING +0 +#else +std::time(nullptr) +#endif +) , light_direction(config.light_direction) , fog_density(config.fog_density) { - + for (int i = 0; i < 4; ++i) { + rng.Next(); + } } World::~World() { @@ -673,12 +892,6 @@ void World::Update(int dt) { } } -namespace { - -std::vector col; - -} - void World::ResolveWorldCollision( const Entity &entity, EntityState &state -- 2.39.2