+DirectInput::DirectInput(World &world, Player &player, WorldManipulator &manip)
+: world(world)
+, player(player)
+, manip(manip)
+, aim_world()
+, aim_entity()
+, move_dir(0.0f)
+, pitch(0.0f)
+, yaw(0.0f)
+, dirty(true)
+, active_slot(0)
+, place_timer(256)
+, remove_timer(256) {
+
+}
+
+void DirectInput::Update(int dt) {
+ dirty = true; // 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::SetMovement(const glm::vec3 &m) {
+ if (dot(m, m) > 1.0f) {
+ move_dir = normalize(m);
+ } else {
+ move_dir = m;
+ }
+ dirty = true;
+}
+
+void DirectInput::TurnHead(float dp, float dy) {
+ pitch += dp;
+ if (pitch > PI / 2) {
+ pitch = PI / 2;
+ } else if (pitch < -PI / 2) {
+ pitch = -PI / 2;
+ }
+ yaw += dy;
+ if (yaw > PI) {
+ yaw -= PI * 2;
+ } else if (yaw < -PI) {
+ yaw += PI * 2;
+ }
+ dirty = true;
+}
+
+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::SelectInventory(int) {
+}
+
+void DirectInput::UpdatePlayer() {
+ constexpr float max_vel = 0.005f;
+ if (dirty) {
+ player.GetEntity().Orientation(glm::quat(glm::vec3(pitch, yaw, 0.0f)));
+ player.GetEntity().Velocity(glm::rotateY(move_dir * max_vel, yaw));
+
+ Ray aim = player.Aim();
+ if (!world.Intersection(aim, glm::mat4(1.0f), player.GetEntity().ChunkCoords(), aim_world)) {
+ aim_world = WorldCollision();
+ }
+ if (!world.Intersection(aim, glm::mat4(1.0f), player.GetEntity(), 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();
+ }
+ }
+ // TODO: update outline if applicable
+ dirty = false;
+ }
+}
+
+void DirectInput::PickBlock() {
+ UpdatePlayer();
+ if (!aim_world) return;
+ player.SetInventorySlot(aim_world.GetBlock().type - 1);
+}
+
+void DirectInput::PlaceBlock() {
+ UpdatePlayer();
+ if (!aim_world) return;
+
+ BlockLookup next_block(aim_world.chunk, aim_world.BlockPos(), Block::NormalFace(aim_world.normal));
+ if (!next_block) {
+ return;
+ }
+ manip.SetBlock(next_block.GetChunk(), next_block.GetBlockIndex(), Block(player.GetInventorySlot() + 1));
+ dirty = true;
+}
+
+void DirectInput::RemoveBlock() {
+ UpdatePlayer();
+ if (!aim_world) return;
+ manip.SetBlock(aim_world.GetChunk(), aim_world.block, Block(0));
+ dirty = true;
+}
+
+
+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"