+, aim_world()
+, aim_entity() {
+ player.GetEntity().SetController(*this);
+}
+
+void PlayerController::SetMovement(const glm::vec3 &m) noexcept {
+ if (dot(m, m) > 1.0f) {
+ move_dir = normalize(m);
+ } else {
+ move_dir = m;
+ }
+ 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);
+}
+
+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();
+ 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();
+ }
+ }
+ dirty = false;
+ }
+}
+
+
+DirectInput::DirectInput(World &world, Player &player, WorldManipulator &manip)
+: PlayerController(world, player)
+, manip(manip)
+, place_timer(0.25f)
+, remove_timer(0.25f) {