+PlayerController::PlayerController(World &world, Player &player)
+: world(world)
+, player(player)
+, move_dir(0.0f)
+, dirty(true)
+, aim_world()
+, aim_entity() {
+ player.GetEntity().SetController(*this);
+ player.GetEntity().GetSteering().SetAcceleration(5.0f);
+}
+
+PlayerController::~PlayerController() {
+ if (&player.GetEntity().GetController() == this) {
+ player.GetEntity().UnsetController();
+ }
+}
+
+void PlayerController::SetMovement(const glm::vec3 &m) noexcept {
+ if (glm::dot(m, m) > 1.0f) {
+ move_dir = glm::normalize(m);
+ } else {
+ move_dir = m;
+ }
+ Invalidate();
+}
+
+void PlayerController::TurnHead(float dp, float dy) noexcept {
+ player.GetEntity().TurnHead(dp, dy);
+}
+
+float PlayerController::GetPitch() const noexcept {
+ return player.GetEntity().Pitch();
+}
+
+float PlayerController::GetYaw() const noexcept {
+ return player.GetEntity().Yaw();
+}
+
+void PlayerController::SelectInventory(int i) noexcept {
+ player.SetInventorySlot(i);
+}
+
+int PlayerController::InventorySlot() const noexcept {
+ return player.GetInventorySlot();
+}
+
+void PlayerController::Invalidate() noexcept {
+ dirty = true;
+}
+
+void PlayerController::UpdatePlayer() noexcept {
+ if (dirty) {
+ Ray aim = player.Aim();
+ Entity &entity = player.GetEntity();
+ if (!world.Intersection(aim, entity.ChunkCoords(), aim_world)) {
+ aim_world = WorldCollision();
+ }
+ 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) {
+ 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(glm::rotateY(move_dir * entity.MaxVelocity(), entity.Yaw()), 0.0f)
+ * glm::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;
+ }
+}
+
+
+DirectInput::DirectInput(World &world, Player &player, WorldManipulator &manip)
+: PlayerController(world, player)
+, manip(manip)
+, place_timer(0.25f)
+, remove_timer(0.25f) {
+
+}
+
+void DirectInput::Update(Entity &, float dt) {
+ Invalidate(); // world has changed in the meantime
+ UpdatePlayer();
+
+ remove_timer.Update(dt);
+ if (remove_timer.Hit()) {
+ RemoveBlock();
+ }
+
+ place_timer.Update(dt);
+ if (place_timer.Hit()) {
+ PlaceBlock();
+ }
+}
+
+void DirectInput::StartPrimaryAction() {
+ if (!remove_timer.Running()) {
+ RemoveBlock();
+ remove_timer.Start();
+ }
+}
+
+void DirectInput::StopPrimaryAction() {
+ remove_timer.Stop();
+}
+
+void DirectInput::StartSecondaryAction() {
+ if (!place_timer.Running()) {
+ PlaceBlock();
+ place_timer.Start();
+ }
+}
+
+void DirectInput::StopSecondaryAction() {
+ place_timer.Stop();
+}
+
+void DirectInput::StartTertiaryAction() {
+ PickBlock();
+}
+
+void DirectInput::StopTertiaryAction() {
+ // nothing
+}
+
+void DirectInput::PickBlock() {
+ UpdatePlayer();
+ if (!BlockFocus()) return;
+ SelectInventory(BlockFocus().GetBlock().type - 1);
+}
+
+void DirectInput::PlaceBlock() {
+ // update block focus
+ UpdatePlayer();
+ // do nothing if not looking at any block
+ if (!BlockFocus()) return;
+
+ // determine block adjacent to the face the player is looking at
+ BlockLookup next_block(BlockFocus().chunk, BlockFocus().BlockPos(), Block::NormalFace(BlockFocus().normal));
+ // abort if it's unavailable
+ if (!next_block) {
+ return;
+ }
+
+ // "can replace" check
+ // this prevents players from replacing solid blocks e.g. by looking through slabs
+ // simple for now, should be expanded to include things like
+ // entities in the way or replacable blocks like water and stuff
+ if (next_block.GetBlock().type != 0) {
+ return;
+ }
+
+ Block new_block(InventorySlot() + 1);
+
+ // block's up vector
+ // align with player's up
+ const glm::vec3 player_up(GetPlayer().GetEntity().Up());
+ new_block.SetFace(Block::NormalFace(player_up));
+ // to align with player's local up/down look (like stairs in minecraft), just invert
+ // it if pitch is positive
+ // or, align with focus normal (like logs in minecraft)
+
+ // determine block's turn (local rotation about up axis)
+ // when aligned with player's up (first mode, and currently the only one implemented)
+ // project the player's view forward onto his entity's XZ plane and
+ // use the closest cardinal direction it's pointing in
+ const glm::vec3 view_forward(-GetPlayer().GetEntity().ViewTransform(GetPlayer().GetEntity().ChunkCoords())[2]);
+ // if view is straight up or down, this will be a null vector (NaN after normalization)
+ // in that case maybe the model forward should be used?
+ // the current implementation implicitly falls back to TURN_NONE which is -Z
+ const glm::vec3 local_forward(glm::normalize(view_forward - glm::proj(view_forward, player_up)));
+ // FIXME: I suspect this only works when player_up is positive Y
+ if (local_forward.x > 0.707f) {
+ new_block.SetTurn(Block::TURN_RIGHT);
+ } else if (local_forward.z > 0.707f) {
+ new_block.SetTurn(Block::TURN_AROUND);
+ } else if (local_forward.x < -0.707f) {
+ new_block.SetTurn(Block::TURN_LEFT);
+ }
+ // for mode two ("minecraft stairs") it should work the same, but I haven't properly
+ // thought that through (well, that's also true about the whole face/turn thing, but oh well)
+ // mode three I have absoloutely no clue. that placement would be appropriate for pipe-like
+ // blocks, where turn shouldn't make a difference, but what if it does?
+
+ manip.SetBlock(next_block.GetChunk(), next_block.GetBlockIndex(), new_block);
+ Invalidate();
+}
+
+void DirectInput::RemoveBlock() {
+ UpdatePlayer();
+ if (!BlockFocus()) return;
+ manip.SetBlock(BlockFocus().GetChunk(), BlockFocus().block, Block(0));
+ Invalidate();
+}
+
+
+HUD::HUD(Environment &env, Config &config, const Player &player)
+: env(env)
+, config(config)
+, player(player)
+// block focus
+, outline()
+, outline_transform(1.0f)
+, outline_visible(false)
+// "inventory"