--- /dev/null
+#ifndef BLANK_AI_AICONTROLLER_HPP_
+#define BLANK_AI_AICONTROLLER_HPP_
+
+#include "../world/EntityController.hpp"
+
+#include <glm/glm.hpp>
+
+
+namespace blank {
+
+class GaloisLFSR;
+
+class AIController
+: public EntityController {
+
+public:
+ explicit AIController(GaloisLFSR &);
+ ~AIController();
+
+ void Update(Entity &, float dt) override;
+
+ glm::vec3 ControlForce(const EntityState &) const override;
+
+ static glm::vec3 Heading(const EntityState &) noexcept;
+
+private:
+ GaloisLFSR &random;
+
+ float chase_speed;
+ float flee_speed;
+ float stop_dist;
+ float flee_dist;
+
+ glm::vec3 wander_pos;
+ float wander_dist;
+ float wander_radius;
+ float wander_disp;
+ float wander_speed;
+
+};
+
+}
+
+#endif
+++ /dev/null
-#ifndef BLANK_AI_CHASER_HPP_
-#define BLANK_AI_CHASER_HPP_
-
-#include "Controller.hpp"
-
-
-namespace blank {
-
-class World;
-
-class Chaser
-: public Controller {
-
-public:
- Chaser(World &, Entity &ctrl, Entity &tgt) noexcept;
- ~Chaser();
-
- Entity &Target() noexcept { return tgt; }
- const Entity &Target() const noexcept { return tgt; }
-
- void Update(int dt) override;
-
-private:
- World &world;
- Entity &tgt;
- float chase_speed;
- float flee_speed;
- float stop_dist;
- float flee_dist;
-
-};
-
-}
-
-#endif
+++ /dev/null
-#ifndef BLANK_AI_CONTROLLER_HPP_
-#define BLANK_AI_CONTROLLER_HPP_
-
-
-namespace blank {
-
-class Entity;
-
-class Controller {
-
-public:
- explicit Controller(Entity &e) noexcept;
- virtual ~Controller();
-
- Entity &Controlled() noexcept { return entity; }
- const Entity &Controlled() const noexcept { return entity; }
-
- virtual void Update(int dt) = 0;
-
-private:
- Entity &entity;
-
-};
-
-}
-
-#endif
+++ /dev/null
-#ifndef BLANK_AI_RANDOMWALK_HPP_
-#define BLANK_AI_RANDOMWALK_HPP_
-
-#include "Controller.hpp"
-
-#include "../rand/GaloisLFSR.hpp"
-
-#include <glm/glm.hpp>
-
-
-namespace blank {
-
-/// Randomly start or stop moving in axis directions every now and then.
-class RandomWalk
-: public Controller {
-
-public:
- RandomWalk(Entity &, std::uint64_t seed) noexcept;
- ~RandomWalk();
-
- void Update(int dt) override;
-
-private:
- void Change() noexcept;
-
-private:
- GaloisLFSR random;
-
- glm::vec3 start_vel;
- glm::vec3 target_vel;
-
- int switch_time;
- float lerp_max;
- float lerp_time;
-
-};
-
-}
-
-#endif
#include "Spawner.hpp"
-#include "Chaser.hpp"
-#include "RandomWalk.hpp"
+#include "AIController.hpp"
+
#include "../model/Model.hpp"
#include "../model/ModelRegistry.hpp"
#include "../rand/GaloisLFSR.hpp"
Spawner::Spawner(World &world, ModelRegistry &models, GaloisLFSR &rand)
: world(world)
, models(models)
-, controllers()
+, entities()
, random(rand)
, timer(64)
, despawn_range(128 * 128)
}
Spawner::~Spawner() {
- for (auto &ctrl : controllers) {
- delete ctrl;
+ for (Entity *e : entities) {
+ e->UnRef();
}
}
if (timer.Hit()) {
TrySpawn();
}
- for (auto &ctrl : controllers) {
- ctrl->Update(dt);
- }
}
void Spawner::CheckDespawn() noexcept {
const auto &refs = world.Players();
- for (auto iter = controllers.begin(), end = controllers.end(); iter != end;) {
- Entity &e = (*iter)->Controlled();
+ for (auto iter = entities.begin(), end = entities.end(); iter != end;) {
+ Entity &e = (**iter);
if (e.Dead()) {
- delete *iter;
- iter = controllers.erase(iter);
- end = controllers.end();
+ e.UnRef();
+ iter = entities.erase(iter);
+ end = entities.end();
continue;
}
bool safe = false;
}
if (!safe) {
e.Kill();
- delete *iter;
- iter = controllers.erase(iter);
- end = controllers.end();
+ e.UnRef();
+ iter = entities.erase(iter);
+ end = entities.end();
} else {
++iter;
}
}
void Spawner::TrySpawn() {
- if (controllers.size() >= max_entities || model_length == 0) return;
+ if (entities.size() >= max_entities || model_length == 0) return;
// select random player to punish
auto &players = world.Players();
e.Bounds({ { -0.5f, -0.5f, -0.5f }, { 0.5f, 0.5f, 0.5f } });
e.WorldCollidable(true);
RandomModel().Instantiate(e.GetModel());
- Controller *ctrl;
- if (random()) {
- ctrl = new RandomWalk(e, random.Next<std::uint64_t>());
- e.Name("walker");
- } else {
- ctrl = new Chaser(world, e, reference);
- e.Name("chaser");
- }
- controllers.emplace_back(ctrl);
+ e.SetController(new AIController(random));
+ e.Name("spawned");
+ e.Ref();
+ entities.emplace_back(&e);
}
Model &Spawner::RandomModel() noexcept {
namespace blank {
-class Controller;
class Entity;
class GaloisLFSR;
class Model;
private:
World &world;
ModelRegistry ⊧
- std::vector<Controller *> controllers;
+ std::vector<Entity *> entities;
GaloisLFSR &random;
- IntervalTimer timer;
+ CoarseTimer timer;
float despawn_range;
float spawn_distance;
unsigned int max_entities;
-#include "Chaser.hpp"
-#include "Controller.hpp"
-#include "RandomWalk.hpp"
+#include "AIController.hpp"
#include "../model/geometry.hpp"
+#include "../rand/GaloisLFSR.hpp"
#include "../world/Entity.hpp"
#include "../world/World.hpp"
#include "../world/WorldCollision.hpp"
+#include <cmath>
+#include <limits>
#include <glm/glm.hpp>
namespace blank {
-Chaser::Chaser(World &world, Entity &ctrl, Entity &tgt) noexcept
-: Controller(ctrl)
-, world(world)
-, tgt(tgt)
+AIController::AIController(GaloisLFSR &rand)
+: random(rand)
, chase_speed(2.0f)
, flee_speed(-5.0f)
-, stop_dist(10)
-, flee_dist(5) {
- tgt.Ref();
-}
+, stop_dist(10.0f)
+, flee_dist(5.0f)
+, wander_pos(1.0f, 0.0f, 0.0f)
+, wander_dist(2.0f)
+, wander_radius(1.0f)
+, wander_disp(1.0f)
+, wander_speed(1.0f) {
-Chaser::~Chaser() {
- tgt.UnRef();
}
-void Chaser::Update(int dt) {
- if (Target().Dead()) {
- Controlled().Kill();
- return;
- }
+AIController::~AIController() {
- glm::vec3 diff(Target().AbsoluteDifference(Controlled()));
- float dist = length(diff);
- if (dist < std::numeric_limits<float>::epsilon()) {
- Controlled().TargetVelocity(glm::vec3(0.0f));
- return;
- }
- glm::vec3 norm_diff(diff / dist);
+}
- bool line_of_sight = true;
- Ray aim{Target().Position() - diff, norm_diff};
- WorldCollision coll;
- if (world.Intersection(aim, glm::mat4(1.0f), Target().ChunkCoords(), coll)) {
- line_of_sight = coll.depth > dist;
+void AIController::Update(Entity &e, float dt) {
+ // movement: for now, wander only
+ glm::vec3 displacement(
+ random.SNorm() * wander_disp,
+ random.SNorm() * wander_disp,
+ random.SNorm() * wander_disp
+ );
+ if (dot(displacement, displacement) > std::numeric_limits<float>::epsilon()) {
+ wander_pos = normalize(wander_pos + displacement * dt) * wander_radius;
}
- if (!line_of_sight) {
- Controlled().TargetVelocity(glm::vec3(0.0f));
- } else if (dist > stop_dist) {
- Controlled().TargetVelocity(norm_diff * chase_speed);
- } else if (dist < flee_dist) {
- Controlled().TargetVelocity(norm_diff * flee_speed);
- } else {
- Controlled().TargetVelocity(glm::vec3(0.0f));
+ if (e.Moving()) {
+ // orient head towards heading
+ glm::vec3 heading(Heading(e.GetState()));
+ float tgt_pitch = std::atan(heading.y / length(glm::vec2(heading.x, heading.z)));
+ float tgt_yaw = std::atan2(-heading.x, -heading.z);
+ e.SetHead(tgt_pitch, tgt_yaw);
}
}
-
-Controller::Controller(Entity &e) noexcept
-: entity(e) {
- entity.Ref();
-}
-
-Controller::~Controller() {
- entity.UnRef();
+glm::vec3 AIController::ControlForce(const EntityState &state) const {
+ return (Heading(state) * wander_dist + wander_pos) * wander_speed;
}
-
-RandomWalk::RandomWalk(Entity &e, std::uint64_t seed) noexcept
-: Controller(e)
-, random(seed)
-, start_vel(e.Velocity())
-, target_vel(start_vel)
-, switch_time(0)
-, lerp_max(1.0f)
-, lerp_time(0.0f) {
-
-}
-
-RandomWalk::~RandomWalk() {
-
-}
-
-void RandomWalk::Update(int dt) {
- switch_time -= dt;
- lerp_time -= dt;
- if (switch_time < 0) {
- switch_time += 2500 + (random.Next<unsigned short>() % 5000);
- lerp_max = 1500 + (random.Next<unsigned short>() % 1000);
- lerp_time = lerp_max;
- Change();
- } else if (lerp_time > 0) {
- float a = std::min(lerp_time / lerp_max, 1.0f);
- Controlled().TargetVelocity(mix(target_vel, start_vel, a));
+glm::vec3 AIController::Heading(const EntityState &state) noexcept {
+ if (dot(state.velocity, state.velocity) > std::numeric_limits<float>::epsilon()) {
+ return normalize(state.velocity);
} else {
- Controlled().TargetVelocity(target_vel);
+ float cp = std::cos(state.pitch);
+ return glm::vec3(std::cos(state.yaw) * cp, std::sin(state.yaw) * cp, std::sin(state.pitch));
}
}
-void RandomWalk::Change() noexcept {
- start_vel = target_vel;
-
- constexpr float base = 0.001f;
-
- target_vel.x = base * (random.Next<short>() % 1024);
- target_vel.y = base * (random.Next<short>() % 1024);
- target_vel.z = base * (random.Next<short>() % 1024);
-}
-
}
#ifndef BLANK_APP_INTERVALTIMER_HPP
#define BLANK_APP_INTERVALTIMER_HPP
+#include <cmath>
+
namespace blank {
-/// Timer that hits every n milliseconds. Resolution is that of the
-/// delta values passed to Update(), minimum 1ms.
-/// Also tracks the number of iterations as well as milliseconds
+/// Timer that hits every n Time units. Resolution is that of the
+/// delta values passed to Update().
+/// Also tracks the number of iterations as well as Time units
/// passed.
+template<class Time = int>
class IntervalTimer {
public:
- /// Create a timer that hits every interval_ms milliseconds.
+ /// Create a timer that hits every interval Time units.
/// Initial state is stopped.
- explicit IntervalTimer(int interval_ms = 0) noexcept
+ explicit IntervalTimer(Time interval_ms = Time(0)) noexcept
: intv(interval_ms) { }
void Start() noexcept {
- speed = 1;
+ speed = Time(1);
}
void Stop() noexcept {
- value = 0;
- speed = 0;
+ value = Time(0);
+ speed = Time(0);
}
void Reset() noexcept {
- value = 0;
+ value = Time(0);
}
bool Running() const noexcept {
- return speed != 0;
+ return speed != Time(0);
}
/// true if an interval boundary was passed by the last call to Update()
bool Hit() const noexcept {
- return Running() && value % intv < last_dt;
+ return Running() && mod(value, intv) < last_dt;
}
bool HitOnce() const noexcept {
return Running() && value >= intv;
}
- int Elapsed() const noexcept {
+ Time Elapsed() const noexcept {
return value;
}
- int Interval() const noexcept {
+ Time Interval() const noexcept {
return intv;
}
int Iteration() const noexcept {
value -= intv;
}
- void Update(int dt) noexcept {
+ void Update(Time dt) noexcept {
value += dt * speed;
last_dt = dt;
}
+ static Time mod(Time val, Time m) noexcept {
+ return val % m;
+ }
+
private:
- int intv;
- int value = 0;
- int speed = 0;
- int last_dt = 0;
+ Time intv;
+ Time value = Time(0);
+ Time speed = Time(0);
+ Time last_dt = Time(0);
};
+using CoarseTimer = IntervalTimer<int>;
+using FineTimer = IntervalTimer<float>;
+
+template<>
+inline float IntervalTimer<float>::mod(float val, float m) noexcept {
+ return std::fmod(val, m);
+}
+
+template<>
+inline int IntervalTimer<float>::Iteration() const noexcept {
+ return std::floor(value / intv);
+}
+
}
#endif
private:
static constexpr std::size_t NUM_SRC = 16;
ALuint source[NUM_SRC];
- IntervalTimer timer[NUM_SRC];
+ CoarseTimer timer[NUM_SRC];
int last_free;
};
}
ALuint src = source[i];
- IntervalTimer &t = timer[i];
+ CoarseTimer &t = timer[i];
sound.Bind(src);
alSourcefv(src, AL_POSITION, glm::value_ptr(pos));
alSourcefv(src, AL_DIRECTION, glm::value_ptr(dir));
alSourcePlay(src);
- t = IntervalTimer(sound.Duration());
+ t = CoarseTimer(sound.Duration());
t.Start();
}
ChunkStore &store;
const WorldSave &save;
std::list<ChunkTransmission> transmissions;
- IntervalTimer timer;
+ CoarseTimer timer;
};
Interface interface;
ChunkReceiver chunk_receiver;
ChunkRenderer chunk_renderer;
- IntervalTimer loop_timer;
+ CoarseTimer loop_timer;
SkyBox sky;
public:
explicit NetworkedInput(World &, Player &, Client &);
- void Update(int dt);
+ void Update(Entity &, float dt) override;
void PushPlayerUpdate(int dt);
void MergePlayerCorrection(std::uint16_t, const EntityState &);
struct PlayerHistory {
EntityState state;
- glm::vec3 tgt_vel;
+ glm::vec3 movement;
float delta_t;
std::uint16_t packet;
- PlayerHistory(EntityState s, const glm::vec3 &tv, float dt, std::uint16_t p)
- : state(s), tgt_vel(tv), delta_t(dt), packet(p) { }
+ PlayerHistory(EntityState s, const glm::vec3 &mv, float dt, std::uint16_t p)
+ : state(s), movement(mv), delta_t(dt), packet(p) { }
};
std::list<PlayerHistory> player_hist;
}
void InteractiveState::Update(int dt) {
- input.Update(dt);
- if (input.BlockFocus()) {
- hud.FocusBlock(input.BlockFocus().GetChunk(), input.BlockFocus().block);
- } else if (input.EntityFocus()) {
- hud.FocusEntity(*input.EntityFocus().entity);
- } else {
- hud.FocusNone();
- }
- hud.Display(res.block_types[player.GetInventorySlot() + 1]);
loop_timer.Update(dt);
master.Update(dt);
chunk_receiver.Update(dt);
- hud.Update(dt);
int world_dt = 0;
while (loop_timer.HitOnce()) {
world.Update(loop_timer.Interval());
}
chunk_renderer.Update(dt);
+ if (input.BlockFocus()) {
+ hud.FocusBlock(input.BlockFocus().GetChunk(), input.BlockFocus().block);
+ } else if (input.EntityFocus()) {
+ hud.FocusEntity(*input.EntityFocus().entity);
+ } else {
+ hud.FocusNone();
+ }
if (world_dt > 0) {
input.PushPlayerUpdate(world_dt);
}
+ hud.Display(res.block_types[player.GetInventorySlot() + 1]);
+ hud.Update(dt);
glm::mat4 trans = player.GetEntity().Transform(player.GetEntity().ChunkCoords());
glm::vec3 dir(trans * glm::vec4(0.0f, 0.0f, -1.0f, 0.0f));
}
-void NetworkedInput::Update(int dt) {
+void NetworkedInput::Update(Entity &, float dt) {
Invalidate();
UpdatePlayer();
}
InventorySlot()
);
if (player_hist.size() < 16) {
- player_hist.emplace_back(state, GetPlayer().GetEntity().TargetVelocity(), dt * 0.001f, packet);
+ player_hist.emplace_back(state, GetMovement(), dt * 0.001f, packet);
} else {
auto entry = player_hist.begin();
entry->state = state;
- entry->tgt_vel = GetPlayer().GetEntity().TargetVelocity();
+ entry->movement = GetMovement();
entry->delta_t = dt * 0.001f;
entry->packet = packet;
player_hist.splice(player_hist.end(), player_hist, entry);
}
}
+ glm::vec3 restore_movement(GetMovement());
+
EntityState player_state = GetPlayer().GetEntity().GetState();
Entity replay(GetPlayer().GetEntity());
replay.SetState(corrected_state);
vector<WorldCollision> col;
while (entry != end) {
replay.Velocity(entry->state.velocity);
- replay.TargetVelocity(entry->tgt_vel);
+ SetMovement(entry->movement);
GetWorld().Update(replay, entry->delta_t);
entry->state.chunk_pos = replay.GetState().chunk_pos;
entry->state.block_pos = replay.GetState().block_pos;
const float disp_squared = dot(displacement, displacement);
if (disp_squared < 16.0f * numeric_limits<float>::epsilon()) {
+ SetMovement(restore_movement);
return;
}
player_state.block_pos += displacement;
}
GetPlayer().GetEntity().SetState(player_state);
+ SetMovement(restore_movement);
}
void NetworkedInput::StartPrimaryAction() {
private:
ConnectionHandler *handler;
IPaddress addr;
- IntervalTimer send_timer;
- IntervalTimer recv_timer;
+ CoarseTimer send_timer;
+ CoarseTimer recv_timer;
Packet::TControl ctrl_out;
Packet::TControl ctrl_in;
return (*this)(next);
}
+ float SNorm() noexcept {
+ return float(Next<std::uint32_t>()) * (1.0f / 2147483647.5f) - 1.0f;
+ }
+
+ float UNorm() noexcept {
+ return float(Next<std::uint32_t>()) * (1.0f / 4294967295.0f);
+ }
+
template<class Container>
typename Container::reference From(Container &c) {
return c[Next<typename Container::size_type>() % c.size()];
EntityState player_update_state;
std::uint16_t player_update_pack;
- IntervalTimer player_update_timer;
+ CoarseTimer player_update_timer;
std::uint8_t old_actions;
ChunkTransmitter transmitter;
ChunkLoader chunk_loader;
Spawner spawner;
Server server;
- IntervalTimer loop_timer;
+ CoarseTimer loop_timer;
};
}
SendUpdates();
- input->Update(dt);
CheckPlayerFix();
CheckChunkQueue();
}
}
void MasterState::Update(int dt) {
- input.Update(dt);
+ spawner.Update(dt);
+ world.Update(dt);
if (input.BlockFocus()) {
hud.FocusBlock(input.BlockFocus().GetChunk(), input.BlockFocus().block);
} else if (input.EntityFocus()) {
}
hud.Display(res.block_types[player.GetInventorySlot() + 1]);
hud.Update(dt);
- spawner.Update(dt);
- world.Update(dt);
chunk_loader.Update(dt);
chunk_renderer.Update(dt);
public:
DirectInput(World &, Player &, WorldManipulator &);
- void Update(int dt);
+ void Update(Entity &, float dt) override;
void StartPrimaryAction() override;
void StopPrimaryAction() override;
private:
WorldManipulator &manip;
- IntervalTimer place_timer;
- IntervalTimer remove_timer;
+ FineTimer place_timer;
+ FineTimer remove_timer;
};
// message box
MessageBox messages;
- IntervalTimer msg_timer;
+ CoarseTimer msg_timer;
bool msg_keep;
// crosshair
#include <glm/glm.hpp>
#include "../world/EntityCollision.hpp"
+#include "../world/EntityController.hpp"
#include "../world/WorldCollision.hpp"
class Player;
class World;
-class PlayerController {
+class PlayerController
+: public EntityController {
public:
PlayerController(World &, Player &);
/// the magnitude (clamped to [0..1]) can be used to attenuate target velocity
void SetMovement(const glm::vec3 &) noexcept;
const glm::vec3 &GetMovement() const noexcept { return move_dir; }
+
+ glm::vec3 ControlForce(const EntityState &) const override;
+
/// turn the controlled entity's head by given pitch and yaw deltas
void TurnHead(float pitch, float yaw) noexcept;
, dirty(true)
, aim_world()
, aim_entity() {
-
+ player.GetEntity().SetController(*this);
}
void PlayerController::SetMovement(const glm::vec3 &m) noexcept {
Invalidate();
}
+glm::vec3 PlayerController::ControlForce(const EntityState &s) const {
+ glm::vec3 target(rotateY(move_dir * player.GetEntity().MaxVelocity(), s.yaw) - s.velocity);
+ return target * player.GetEntity().MaxControlForce();
+}
+
void PlayerController::TurnHead(float dp, float dy) noexcept {
player.GetEntity().TurnHead(dp, dy);
}
}
void PlayerController::UpdatePlayer() noexcept {
- constexpr float max_vel = 5.0f; // in m/s
if (dirty) {
- player.GetEntity().TargetVelocity(glm::rotateY(move_dir * max_vel, player.GetEntity().Yaw()));
-
Ray aim = player.Aim();
if (!world.Intersection(aim, glm::mat4(1.0f), player.GetEntity().ChunkCoords(), aim_world)) {
aim_world = WorldCollision();
DirectInput::DirectInput(World &world, Player &player, WorldManipulator &manip)
: PlayerController(world, player)
, manip(manip)
-, place_timer(256)
-, remove_timer(256) {
+, place_timer(0.25f)
+, remove_timer(0.25f) {
}
-void DirectInput::Update(int dt) {
+void DirectInput::Update(Entity &, float dt) {
Invalidate(); // world has changed in the meantime
UpdatePlayer();
namespace blank {
class DirectionalLighting;
+class EntityController;
class Shape;
class Entity {
public:
Entity() noexcept;
+ ~Entity() noexcept;
+
+ // note that when copying an entity which owns its controller, the
+ // original must outlive the copy, otherwise the copy ends up with
+ // an invalid controller pointer
+ Entity(const Entity &) noexcept;
+ Entity &operator =(const Entity &) = delete;
+
+ bool HasController() const noexcept { return ctrl; }
+ // entity takes over ownership of controller
+ void SetController(EntityController *c) noexcept;
+ // entity uses shared controller
+ void SetController(EntityController &c) noexcept;
+ void UnsetController() noexcept;
+ EntityController &GetController() noexcept { return *ctrl; }
+ const EntityController &GetController() const noexcept { return *ctrl; }
Instance &GetModel() noexcept { return model; }
const Instance &GetModel() const noexcept { return model; }
bool WorldCollidable() const noexcept { return world_collision; }
void WorldCollidable(bool b) noexcept { world_collision = b; }
- /// desired velocity in local coordinate system
- const glm::vec3 &TargetVelocity() const noexcept { return tgt_vel; }
- void TargetVelocity(const glm::vec3 &v) noexcept { tgt_vel = v; }
+ float MaxVelocity() const noexcept { return max_vel; }
+ void MaxVelocity(float v) noexcept { max_vel = v; }
+ float MaxControlForce() const noexcept { return max_force; }
+ void MaxControlForce(float f) noexcept { max_force = f; }
+
+ glm::vec3 ControlForce(const EntityState &) const noexcept;
const glm::vec3 &Velocity() const noexcept { return state.velocity; }
void Velocity(const glm::vec3 &v) noexcept { state.velocity = v; }
+ bool Moving() const noexcept {
+ return dot(Velocity(), Velocity()) > std::numeric_limits<float>::epsilon();
+ }
+
const glm::vec3 &Position() const noexcept { return state.block_pos; }
void Position(const glm::ivec3 &, const glm::vec3 &) noexcept;
void Position(const glm::vec3 &) noexcept;
bool Dead() const noexcept { return dead; }
bool CanRemove() const noexcept { return dead && ref_count <= 0; }
+ void Update(float dt);
+
void Render(const glm::mat4 &M, DirectionalLighting &prog) noexcept {
if (model) model.Render(M, prog);
}
void UpdateModel() noexcept;
private:
+ EntityController *ctrl;
Instance model;
std::uint32_t id;
AABB bounds;
EntityState state;
- glm::vec3 tgt_vel;
+
+ // TODO: I'd prefer a drag solution
+ float max_vel;
+ float max_force;
int ref_count;
bool world_collision;
bool dead;
+ bool owns_controller;
+
};
}
--- /dev/null
+#ifndef BLANK_WORLD_ENTITYCONTROLLER_HPP_
+#define BLANK_WORLD_ENTITYCONTROLLER_HPP_
+
+#include <glm/glm.hpp>
+
+
+namespace blank {
+
+class Entity;
+class EntityState;
+
+struct EntityController {
+
+ virtual ~EntityController();
+
+ virtual void Update(Entity &, float dt) = 0;
+
+ virtual glm::vec3 ControlForce(const EntityState &) const = 0;
+
+};
+
+}
+
+#endif
#include "Entity.hpp"
+#include "EntityController.hpp"
#include "EntityDerivative.hpp"
#include "EntityState.hpp"
#include "Player.hpp"
namespace blank {
Entity::Entity() noexcept
-: model()
+: ctrl(nullptr)
+, model()
, id(-1)
, name("anonymous")
, bounds()
, state()
-, tgt_vel(0.0f)
+, max_vel(5.0f)
+, max_force(25.0f)
, ref_count(0)
, world_collision(false)
-, dead(false) {
+, dead(false)
+, owns_controller(false) {
}
+Entity::~Entity() noexcept {
+ UnsetController();
+}
+
+Entity::Entity(const Entity &other) noexcept
+: ctrl(other.ctrl)
+, model(other.model)
+, id(-1)
+, name(other.name)
+, bounds(other.bounds)
+, state(other.state)
+, max_vel(other.max_vel)
+, max_force(other.max_force)
+, ref_count(0)
+, world_collision(other.world_collision)
+, dead(other.dead)
+, owns_controller(false) {
+
+}
+
+void Entity::SetController(EntityController *c) noexcept {
+ UnsetController();
+ ctrl = c;
+ owns_controller = true;
+}
+
+void Entity::SetController(EntityController &c) noexcept {
+ UnsetController();
+ ctrl = &c;
+ owns_controller = false;
+}
+
+void Entity::UnsetController() noexcept {
+ if (ctrl && owns_controller) {
+ delete ctrl;
+ }
+ ctrl = nullptr;
+}
+
+glm::vec3 Entity::ControlForce(const EntityState &s) const noexcept {
+ if (HasController()) {
+ return GetController().ControlForce(s);
+ } else {
+ return -s.velocity;
+ }
+}
+
void Entity::Position(const glm::ivec3 &c, const glm::vec3 &b) noexcept {
state.chunk_pos = c;
state.block_pos = b;
}
}
+void Entity::Update(float dt) {
+ if (HasController()) {
+ GetController().Update(*this, dt);
+ }
+}
+
+
+EntityController::~EntityController() {
+
+}
+
EntityState::EntityState()
: chunk_pos(0)
void World::Update(int dt) {
float fdt(dt * 0.001f);
+ for (Entity &entity : entities) {
+ entity.Update(fdt);
+ }
for (Entity &entity : entities) {
Update(entity, fdt);
}
const Entity &entity,
const EntityState &state
) {
- constexpr float k = 10.0f; // spring constant
- constexpr float b = 10.0f; // damper constant
- const glm::vec3 x(-entity.TargetVelocity()); // endpoint displacement from equilibrium, by 1s, in m
- const glm::vec3 v(state.velocity); // relative velocity between endpoints in m/s
- return ((-k) * x) - (b * v); // times 1kg/s, in kg*m/s²
+ return entity.ControlForce(state);
}
namespace {
#include "app/IntervalTimer.hpp"
+#include <limits>
+
CPPUNIT_TEST_SUITE_REGISTRATION(blank::test::TimerTest);
}
-void TimerTest::testIntervalTimer() {
- IntervalTimer timer(50);
+void TimerTest::testCoarseTimer() {
+ CoarseTimer timer(50);
CPPUNIT_ASSERT_MESSAGE(
- "fresh timer is running",
+ "fresh coarse timer is running",
!timer.Running()
);
CPPUNIT_ASSERT_MESSAGE(
- "fresh timer hit",
+ "fresh coarse timer hit",
!timer.Hit()
);
CPPUNIT_ASSERT_EQUAL_MESSAGE(
- "fresh timer with non-zero elapsed time",
+ "fresh coarse timer with non-zero elapsed time",
0, timer.Elapsed()
);
CPPUNIT_ASSERT_EQUAL_MESSAGE(
- "fresh timer at non-zero iteration",
+ "fresh coarse timer at non-zero iteration",
0, timer.Iteration()
);
timer.Start();
CPPUNIT_ASSERT_MESSAGE(
- "startet timer is not running",
+ "startet coarse timer is not running",
timer.Running()
);
CPPUNIT_ASSERT_MESSAGE(
- "started timer hit without update",
+ "started coarse timer hit without update",
!timer.Hit()
);
CPPUNIT_ASSERT_EQUAL_MESSAGE(
- "started, but not updated timer with non-zero elapsed time",
+ "started, but not updated coarse timer with non-zero elapsed time",
0, timer.Elapsed()
);
CPPUNIT_ASSERT_EQUAL_MESSAGE(
- "started, but not updated timer at non-zero iteration",
+ "started, but not updated coarse timer at non-zero iteration",
0, timer.Iteration()
);
timer.Update(25);
CPPUNIT_ASSERT_MESSAGE(
- "updated timer is not running",
+ "updated coarse timer is not running",
timer.Running()
);
CPPUNIT_ASSERT_MESSAGE(
- "timer hit after update, but before it should",
+ "coarse timer hit after update, but before it should",
!timer.Hit()
);
CPPUNIT_ASSERT_EQUAL_MESSAGE(
- "wrong elapsed time on updated timer",
+ "wrong elapsed time on updated coarse timer",
25, timer.Elapsed()
);
CPPUNIT_ASSERT_EQUAL_MESSAGE(
- "wrong iteration on updated timer",
+ "wrong iteration on updated coarse timer",
0, timer.Iteration()
);
timer.Update(25);
CPPUNIT_ASSERT_MESSAGE(
- "timer not hit after updating to its exact interval time",
+ "coarse timer not hit after updating to its exact interval time",
timer.Hit()
);
CPPUNIT_ASSERT_EQUAL_MESSAGE(
- "wrong elapsed time on updated timer",
+ "wrong elapsed time on updated coarse timer",
50, timer.Elapsed()
);
CPPUNIT_ASSERT_EQUAL_MESSAGE(
- "wrong iteration on updated timer at exact interval time",
+ "wrong iteration on updated coarse timer at exact interval time",
1, timer.Iteration()
);
timer.Update(49);
CPPUNIT_ASSERT_MESSAGE(
- "timer hit after updating from exact interval time to just before the next",
+ "coarse timer hit after updating from exact interval time to just before the next",
!timer.Hit()
);
CPPUNIT_ASSERT_EQUAL_MESSAGE(
- "wrong elapsed time on updated timer",
+ "wrong elapsed time on updated coarse timer",
99, timer.Elapsed()
);
CPPUNIT_ASSERT_EQUAL_MESSAGE(
- "wrong iteration after updating timer from exact interval time to just before the next",
+ "wrong iteration after updating coarse timer from exact interval time to just before the next",
1, timer.Iteration()
);
timer.Update(2);
CPPUNIT_ASSERT_MESSAGE(
- "timer not hit after updating across interval time boundary",
+ "coarse timer not hit after updating across interval time boundary",
timer.Hit()
);
CPPUNIT_ASSERT_EQUAL_MESSAGE(
- "wrong elapsed time on updated timer",
+ "wrong elapsed time on updated coarse timer",
101, timer.Elapsed()
);
CPPUNIT_ASSERT_EQUAL_MESSAGE(
timer.Stop();
CPPUNIT_ASSERT_MESSAGE(
- "stopped timer is running",
+ "stopped coarse timer is running",
!timer.Running()
);
CPPUNIT_ASSERT_MESSAGE(
- "stopped timer hit",
+ "stopped coarse timer hit",
!timer.Hit()
);
CPPUNIT_ASSERT_EQUAL_MESSAGE(
- "stopped timer has non-zero elapsed time",
+ "stopped coarse timer has non-zero elapsed time",
0, timer.Elapsed()
);
CPPUNIT_ASSERT_EQUAL_MESSAGE(
- "stopped timer at non-zero iteration",
+ "stopped coarse timer at non-zero iteration",
+ 0, timer.Iteration()
+ );
+}
+
+void TimerTest::testFineTimer() {
+ FineTimer timer(0.5f);
+ CPPUNIT_ASSERT_MESSAGE(
+ "fresh fine timer is running",
+ !timer.Running()
+ );
+ CPPUNIT_ASSERT_MESSAGE(
+ "fresh fine timer hit",
+ !timer.Hit()
+ );
+ CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE(
+ "fresh fine timer with non-zero elapsed time",
+ 0.0f, timer.Elapsed(), std::numeric_limits<float>::epsilon()
+ );
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(
+ "fresh fine timer at non-zero iteration",
+ 0, timer.Iteration()
+ );
+
+ timer.Start();
+ CPPUNIT_ASSERT_MESSAGE(
+ "startet fine timer is not running",
+ timer.Running()
+ );
+ CPPUNIT_ASSERT_MESSAGE(
+ "started fine timer hit without update",
+ !timer.Hit()
+ );
+ CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE(
+ "started, but not updated fine timer with non-zero elapsed time",
+ 0.0f, timer.Elapsed(), std::numeric_limits<float>::epsilon()
+ );
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(
+ "started, but not updated fine timer at non-zero iteration",
+ 0, timer.Iteration()
+ );
+
+ timer.Update(0.25f);
+ CPPUNIT_ASSERT_MESSAGE(
+ "updated fine timer is not running",
+ timer.Running()
+ );
+ CPPUNIT_ASSERT_MESSAGE(
+ "fine timer hit after update, but before it should",
+ !timer.Hit()
+ );
+ CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE(
+ "wrong elapsed time on updated fine timer",
+ 0.25f, timer.Elapsed(), std::numeric_limits<float>::epsilon()
+ );
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(
+ "wrong iteration on updated fine timer",
+ 0, timer.Iteration()
+ );
+
+ timer.Update(0.25f);
+ CPPUNIT_ASSERT_MESSAGE(
+ "fine timer not hit after updating to its exact interval time",
+ timer.Hit()
+ );
+ CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE(
+ "wrong elapsed time on updated fine timer",
+ 0.5f, timer.Elapsed(), std::numeric_limits<float>::epsilon()
+ );
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(
+ "wrong iteration on updated fine timer at exact interval time",
+ 1, timer.Iteration()
+ );
+
+ timer.Update(0.49f);
+ CPPUNIT_ASSERT_MESSAGE(
+ "fine timer hit after updating from exact interval time to just before the next",
+ !timer.Hit()
+ );
+ CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE(
+ "wrong elapsed time on updated fine timer",
+ 0.99f, timer.Elapsed(), std::numeric_limits<float>::epsilon()
+ );
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(
+ "wrong iteration after updating fine timer from exact interval time to just before the next",
+ 1, timer.Iteration()
+ );
+
+ timer.Update(0.02f);
+ CPPUNIT_ASSERT_MESSAGE(
+ "fine timer not hit after updating across interval time boundary",
+ timer.Hit()
+ );
+ CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE(
+ "wrong elapsed time on updated fine timer",
+ 1.01f, timer.Elapsed(), std::numeric_limits<float>::epsilon()
+ );
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(
+ "wrong iteration after updating across interval time boundary",
+ 2, timer.Iteration()
+ );
+
+ timer.Stop();
+ CPPUNIT_ASSERT_MESSAGE(
+ "stopped fine timer is running",
+ !timer.Running()
+ );
+ CPPUNIT_ASSERT_MESSAGE(
+ "stopped fine timer hit",
+ !timer.Hit()
+ );
+ CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE(
+ "stopped fine timer has non-zero elapsed time",
+ 0.0f, timer.Elapsed(), std::numeric_limits<float>::epsilon()
+ );
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(
+ "stopped fine timer at non-zero iteration",
0, timer.Iteration()
);
}
CPPUNIT_TEST_SUITE(TimerTest);
-CPPUNIT_TEST(testIntervalTimer);
+CPPUNIT_TEST(testCoarseTimer);
+CPPUNIT_TEST(testFineTimer);
CPPUNIT_TEST_SUITE_END();
void setUp();
void tearDown();
- void testIntervalTimer();
+ void testCoarseTimer();
+ void testFineTimer();
};