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;
/// 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
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;
-
};
}
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;
};
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;
};
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;
};
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;
};
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;
};
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)
// select random player to punish
auto &players = world.Players();
if (players.size() == 0) return;
- size_t player_num = random.Next<unsigned short>() % players.size();
+ size_t player_num = world.Random().Next<unsigned short>() % 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());
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<std::size_t>() % model_length) + model_offset;
+ std::size_t offset = (world.Random().Next<std::size_t>() % model_length) + model_offset;
return models[offset];
}
namespace blank {
class Entity;
-class GaloisLFSR;
class Model;
class ModelRegistry;
class World;
class Spawner {
public:
- Spawner(World &, ModelRegistry &, GaloisLFSR &);
+ Spawner(World &, ModelRegistry &);
~Spawner();
void LimitModels(std::size_t begin, std::size_t end);
ModelRegistry ⊧
std::vector<Entity *> entities;
- GaloisLFSR &random;
-
CoarseTimer timer;
float despawn_range;
float spawn_distance;
}
-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) {
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());
}
}
-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;
float minimum,
float variance
) noexcept {
- decision_timer = FineTimer(minimum + variance * random.SNorm());
+ decision_timer = FineTimer(minimum + variance * world.Random().SNorm());
decision_timer.Start();
}
}
unsigned int AIController::Decide(unsigned int num_choices) noexcept {
- return random.Next<unsigned int>() % 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<WorldCollision> 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<float>::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<unsigned int>() % 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);
}
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;
}
}
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);
}
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;
}
}
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);
}
}
#include "StateControl.hpp"
#include "../audio/Audio.hpp"
#include "../graphics/Viewport.hpp"
-#include "../rand/GaloisLFSR.hpp"
#include "../shared/MessageState.hpp"
#include "../ui/Keymap.hpp"
StateControl state;
- GaloisLFSR rng;
-
explicit HeadlessEnvironment(const 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<int>();
- }
+, state() {
+
}
string HeadlessEnvironment::Config::GetWorldPath(const string &world_name) const {
, 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");
, 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)
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;
, aim_world()
, aim_entity() {
player.GetEntity().SetController(*this);
+ player.GetEntity().GetSteering().SetAcceleration(5.0f);
}
PlayerController::~PlayerController() {
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);
}
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;
}
}
#include "Chunk.hpp"
#include "EntityDerivative.hpp"
#include "EntityState.hpp"
+#include "Steering.hpp"
#include "../geometry/primitive.hpp"
#include "../model/Instance.hpp"
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;
private:
+ Steering steering;
EntityController *ctrl;
Instance model;
#ifndef BLANK_WORLD_ENTITYCONTROLLER_HPP_
#define BLANK_WORLD_ENTITYCONTROLLER_HPP_
-#include "EntityState.hpp"
-
-#include <glm/glm.hpp>
-
-
namespace blank {
class Entity;
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);
- }
-
};
}
--- /dev/null
+#ifndef BLANK_WORLD_STEERING_HPP
+#define BLANK_WORLD_STEERING_HPP
+
+#include "../geometry/Location.hpp"
+#include "../geometry/primitive.hpp"
+
+#include <glm/glm.hpp>
+
+
+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
#include "Entity.hpp"
#include "Generator.hpp"
#include "Player.hpp"
+#include "../rand/GaloisLFSR.hpp"
#include <cstdint>
#include <list>
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
std::list<Player> players;
std::list<Entity> entities;
+ GaloisLFSR rng;
+
glm::vec3 light_direction;
float fog_density;
#include "../app/Assets.hpp"
#include "../geometry/const.hpp"
#include "../geometry/distance.hpp"
+#include "../geometry/rotation.hpp"
#include "../graphics/Format.hpp"
#include "../graphics/Viewport.hpp"
namespace blank {
+namespace {
+
+/// used as a buffer for merging collisions
+std::vector<WorldCollision> col;
+
+}
+
Entity::Entity() noexcept
-: ctrl(nullptr)
+: steering(*this)
+, ctrl(nullptr)
, model()
, id(-1)
, name("anonymous")
}
Entity::Entity(const Entity &other) noexcept
-: ctrl(other.ctrl)
+: steering(*this)
+, ctrl(other.ctrl)
, model(other.model)
, id(-1)
, name(other.name)
}
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 {
if (HasController()) {
GetController().Update(*this, dt);
}
+ steering.Update(world, dt);
UpdatePhysics(world, dt);
UpdateTransforms();
UpdateHeading();
}
-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()
}
+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<float>::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<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 = 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<int>();
+ }
}
World::~World() {
}
}
-namespace {
-
-std::vector<WorldCollision> col;
-
-}
-
void World::ResolveWorldCollision(
const Entity &entity,
EntityState &state