]> git.localhorst.tv Git - blank.git/blobdiff - src/ui/ui.cpp
move common exceptions to app/error
[blank.git] / src / ui / ui.cpp
index 43cdb53613ec2660603204128f9fffc91cf24839..2fffcbdec19d1a9b8a40639255d31921823c1066 100644 (file)
 #include "../app/Config.hpp"
 #include "../app/Environment.hpp"
 #include "../app/FrameCounter.hpp"
-#include "../app/init.hpp"
 #include "../audio/Audio.hpp"
 #include "../audio/SoundBank.hpp"
+#include "../geometry/distance.hpp"
 #include "../graphics/Font.hpp"
 #include "../graphics/Viewport.hpp"
 #include "../io/TokenStreamReader.hpp"
 #include "../model/bounds.hpp"
+#include "../net/CongestionControl.hpp"
 #include "../world/BlockLookup.hpp"
 #include "../world/World.hpp"
 #include "../world/WorldManipulator.hpp"
@@ -27,6 +28,7 @@
 #include <map>
 #include <sstream>
 #include <glm/gtc/matrix_transform.hpp>
+#include <glm/gtx/projection.hpp>
 #include <glm/gtx/rotate_vector.hpp>
 #include <glm/gtx/io.hpp>
 
@@ -37,17 +39,22 @@ PlayerController::PlayerController(World &world, Player &player)
 : world(world)
 , player(player)
 , move_dir(0.0f)
-, pitch(0.0f)
-, yaw(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 (dot(m, m) > 1.0f) {
-               move_dir = normalize(m);
+       if (glm::dot(m, m) > 1.0f) {
+               move_dir = glm::normalize(m);
        } else {
                move_dir = m;
        }
@@ -55,19 +62,15 @@ void PlayerController::SetMovement(const glm::vec3 &m) noexcept {
 }
 
 void PlayerController::TurnHead(float dp, float dy) noexcept {
-       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;
-       }
-       Invalidate();
+       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 {
@@ -83,17 +86,13 @@ void PlayerController::Invalidate() noexcept {
 }
 
 void PlayerController::UpdatePlayer() noexcept {
-       constexpr float max_vel = 5.0f; // in m/s
        if (dirty) {
-               player.GetEntity().Orientation(glm::quat(glm::vec3(0.0f, yaw, 0.0f)));
-               player.GetEntity().GetModel().EyesState().orientation = glm::quat(glm::vec3(pitch, 0.0f, 0.0f));
-               player.GetEntity().TargetVelocity(glm::rotateY(move_dir * max_vel, yaw));
-
                Ray aim = player.Aim();
-               if (!world.Intersection(aim, glm::mat4(1.0f), player.GetEntity().ChunkCoords(), aim_world)) {
+               Entity &entity = player.GetEntity();
+               if (!world.Intersection(aim, entity.ChunkCoords(), aim_world)) {
                        aim_world = WorldCollision();
                }
-               if (!world.Intersection(aim, glm::mat4(1.0f), player.GetEntity(), aim_entity)) {
+               if (!world.Intersection(aim, entity, aim_entity)) {
                        aim_entity = EntityCollision();
                }
                if (aim_world && aim_entity) {
@@ -104,6 +103,20 @@ void PlayerController::UpdatePlayer() noexcept {
                                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;
        }
 }
@@ -112,12 +125,12 @@ void PlayerController::UpdatePlayer() noexcept {
 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();
 
@@ -169,14 +182,59 @@ void DirectInput::PickBlock() {
 }
 
 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;
        }
-       manip.SetBlock(next_block.GetChunk(), next_block.GetBlockIndex(), Block(InventorySlot() + 1));
+
+       // "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();
 }
 
@@ -209,11 +267,19 @@ HUD::HUD(Environment &env, Config &config, const Player &player)
 , block_text()
 , show_block(false)
 , show_entity(false)
+// net stats
+, bandwidth_text()
+, rtt_text()
+, packet_loss_text()
+, show_net(false)
 // message box
 , messages(env.assets.small_ui_font)
 , msg_timer(5000)
+, msg_keep(false)
 // crosshair
 , crosshair() {
+       const float ls = env.assets.small_ui_font.LineSkip();
+
        // "inventory"
        block_transform = glm::translate(block_transform, glm::vec3(50.0f, 50.0f, 0.0f));
        block_transform = glm::scale(block_transform, glm::vec3(50.0f));
@@ -231,42 +297,56 @@ HUD::HUD(Environment &env, Config &config, const Player &player)
        counter_text.Position(glm::vec3(-25.0f, 25.0f, 0.0f), Gravity::NORTH_EAST);
        counter_text.Foreground(glm::vec4(1.0f));
        counter_text.Background(glm::vec4(0.5f));
-       position_text.Position(glm::vec3(-25.0f, 25.0f + env.assets.small_ui_font.LineSkip(), 0.0f), Gravity::NORTH_EAST);
+       position_text.Position(glm::vec3(-25.0f, 25.0f + ls, 0.0f), Gravity::NORTH_EAST);
        position_text.Foreground(glm::vec4(1.0f));
        position_text.Background(glm::vec4(0.5f));
-       orientation_text.Position(glm::vec3(-25.0f, 25.0f + 2 * env.assets.small_ui_font.LineSkip(), 0.0f), Gravity::NORTH_EAST);
+       orientation_text.Position(glm::vec3(-25.0f, 25.0f + 2 * ls, 0.0f), Gravity::NORTH_EAST);
        orientation_text.Foreground(glm::vec4(1.0f));
        orientation_text.Background(glm::vec4(0.5f));
-       block_text.Position(glm::vec3(-25.0f, 25.0f + 4 * env.assets.small_ui_font.LineSkip(), 0.0f), Gravity::NORTH_EAST);
+       block_text.Position(glm::vec3(-25.0f, 25.0f + 4 * ls, 0.0f), Gravity::NORTH_EAST);
        block_text.Foreground(glm::vec4(1.0f));
        block_text.Background(glm::vec4(0.5f));
        block_text.Set(env.assets.small_ui_font, "Block: none");
-       entity_text.Position(glm::vec3(-25.0f, 25.0f + 4 * env.assets.small_ui_font.LineSkip(), 0.0f), Gravity::NORTH_EAST);
+       entity_text.Position(glm::vec3(-25.0f, 25.0f + 4 * ls, 0.0f), Gravity::NORTH_EAST);
        entity_text.Foreground(glm::vec4(1.0f));
        entity_text.Background(glm::vec4(0.5f));
        entity_text.Set(env.assets.small_ui_font, "Entity: none");
 
+       // net stats
+       bandwidth_text.Position(glm::vec3(-25.0f, 25.0f + 6 * ls, 0.0f), Gravity::NORTH_EAST);
+       bandwidth_text.Foreground(glm::vec4(1.0f));
+       bandwidth_text.Background(glm::vec4(0.5f));
+       bandwidth_text.Set(env.assets.small_ui_font, "TX: 0.0KB/s RX: 0.0KB/s");
+       rtt_text.Position(glm::vec3(-25.0f, 25.0f + 7 * ls, 0.0f), Gravity::NORTH_EAST);
+       rtt_text.Foreground(glm::vec4(1.0f));
+       rtt_text.Background(glm::vec4(0.5f));
+       rtt_text.Set(env.assets.small_ui_font, "RTT: unavailable");
+       packet_loss_text.Position(glm::vec3(-25.0f, 25.0f + 8 * ls, 0.0f), Gravity::NORTH_EAST);
+       packet_loss_text.Foreground(glm::vec4(1.0f));
+       packet_loss_text.Background(glm::vec4(0.5f));
+       packet_loss_text.Set(env.assets.small_ui_font, "Packet loss: 0.0%");
+
        // message box
-       messages.Position(glm::vec3(25.0f, -25.0f, 0.0f), Gravity::SOUTH_WEST);
+       messages.Position(glm::vec3(25.0f, -25.0f - 2 * ls, 0.0f), Gravity::SOUTH_WEST);
        messages.Foreground(glm::vec4(1.0f));
        messages.Background(glm::vec4(0.5f));
 
        // crosshair
-       OutlineMesh::Buffer buf;
+       PrimitiveMesh::Buffer buf;
        buf.vertices = std::vector<glm::vec3>({
                { -10.0f,   0.0f, 0.0f }, { 10.0f,  0.0f, 0.0f },
                {   0.0f, -10.0f, 0.0f }, {  0.0f, 10.0f, 0.0f },
        });
-       buf.indices = std::vector<OutlineMesh::Index>({
+       buf.indices = std::vector<PrimitiveMesh::Index>({
                0, 1, 2, 3
        });
-       buf.colors.resize(4, { 10.0f, 10.0f, 10.0f });
+       buf.colors.resize(4, { 255, 255, 255, 255 });
        crosshair.Update(buf);
 }
 
 namespace {
 
-OutlineMesh::Buffer outl_buf;
+PrimitiveMesh::Buffer outl_buf;
 
 }
 
@@ -274,7 +354,7 @@ void HUD::FocusBlock(const Chunk &chunk, int index) {
        const Block &block = chunk.BlockAt(index);
        const BlockType &type = chunk.Type(index);
        outl_buf.Clear();
-       type.FillOutlineMesh(outl_buf);
+       type.OutlinePrimitiveMesh(outl_buf);
        outline.Update(outl_buf);
        outline_transform = chunk.Transform(player.GetEntity().ChunkCoords());
        outline_transform *= chunk.ToTransform(Chunk::ToPos(index), index);
@@ -345,10 +425,10 @@ void HUD::UpdatePosition() {
 }
 
 void HUD::UpdateOrientation() {
-       //std::stringstream s;
-       //s << std::setprecision(3) << "pitch: " << rad2deg(ctrl.Pitch())
-       //      << ", yaw: " << rad2deg(ctrl.Yaw());
-       //orientation_text.Set(env.assets.small_ui_font, s.str());
+       std::stringstream s;
+       s << std::setprecision(3) << "pitch: " << glm::degrees(player.GetEntity().Pitch())
+               << ", yaw: " << glm::degrees(player.GetEntity().Yaw());
+       orientation_text.Set(env.assets.small_ui_font, s.str());
 }
 
 void HUD::PostMessage(const char *msg) {
@@ -359,6 +439,27 @@ void HUD::PostMessage(const char *msg) {
 }
 
 
+void HUD::UpdateNetStats(const CongestionControl &stat) {
+       if (!config.video.debug) return;
+
+       std::stringstream s;
+       s << std::fixed << std::setprecision(1)
+               << "TX: " << stat.Upstream()
+               << "KB/s, RX: " << stat.Downstream() << "KB/s";
+       bandwidth_text.Set(env.assets.small_ui_font, s.str());
+
+       s.str("");
+       s << "RTT: " << stat.RoundTripTime() << "ms";
+       rtt_text.Set(env.assets.small_ui_font, s.str());
+
+       s.str("");
+       s << "Packet loss: " << (stat.PacketLoss() * 100.0f) << "%";
+       packet_loss_text.Set(env.assets.small_ui_font, s.str());
+
+       show_net = true;
+}
+
+
 void HUD::Update(int dt) {
        msg_timer.Update(dt);
        if (msg_timer.HitOnce()) {
@@ -377,9 +478,9 @@ void HUD::Update(int dt) {
 void HUD::Render(Viewport &viewport) noexcept {
        // block focus
        if (outline_visible && config.video.world) {
-               PlainColor &outline_prog = viewport.WorldOutlineProgram();
+               PlainColor &outline_prog = viewport.WorldColorProgram();
                outline_prog.SetM(outline_transform);
-               outline.Draw();
+               outline.DrawLines();
        }
 
        // clear depth buffer so everything renders above the world
@@ -390,6 +491,8 @@ void HUD::Render(Viewport &viewport) noexcept {
                if (block_visible) {
                        DirectionalLighting &world_prog = viewport.HUDProgram();
                        world_prog.SetLightDirection({ 1.0f, 3.0f, 5.0f });
+                       world_prog.SetLightColor({ 1.0f, 1.0f, 1.0f });
+                       world_prog.SetAmbientColor({ 0.1f, 0.1f, 0.1f });
                        // disable distance fog
                        world_prog.SetFogDensity(0.0f);
 
@@ -400,16 +503,16 @@ void HUD::Render(Viewport &viewport) noexcept {
                }
 
                // message box
-               if (msg_timer.Running()) {
+               if (msg_keep || msg_timer.Running()) {
                        messages.Render(viewport);
                }
 
                // crosshair
-               PlainColor &outline_prog = viewport.HUDOutlineProgram();
+               PlainColor &outline_prog = viewport.HUDColorProgram();
                viewport.EnableInvertBlending();
                viewport.SetCursor(glm::vec3(0.0f), Gravity::CENTER);
                outline_prog.SetM(viewport.Cursor());
-               crosshair.Draw();
+               crosshair.DrawLines();
        }
 
        // debug overlay
@@ -422,6 +525,11 @@ void HUD::Render(Viewport &viewport) noexcept {
                } else if (show_entity) {
                        entity_text.Render(viewport);
                }
+               if (show_net) {
+                       bandwidth_text.Render(viewport);
+                       rtt_text.Render(viewport);
+                       packet_loss_text.Render(viewport);
+               }
        }
 }
 
@@ -461,11 +569,20 @@ Interface::Interface(
 , client_ctrl(cc)
 , fwd(0)
 , rev(0)
-, slot(0)
-, num_slots(10) {
+, num_slots(10)
+, locked(false) {
+
+}
 
+void Interface::Lock() {
+       fwd = glm::ivec3(0);
+       rev = glm::ivec3(0);
+       locked = true;
 }
 
+void Interface::Unlock() {
+       locked = false;
+}
 
 void Interface::HandlePress(const SDL_KeyboardEvent &event) {
        if (!config.input.keyboard) return;
@@ -531,20 +648,19 @@ void Interface::HandlePress(const SDL_KeyboardEvent &event) {
                        break;
 
                case Keymap::TOGGLE_AUDIO:
-                       config.audio.enabled = !config.audio.enabled;
-                       client_ctrl.SetAudio(config.audio.enabled);
+                       client_ctrl.SetAudio(!config.audio.enabled);
                        break;
                case Keymap::TOGGLE_VIDEO:
-                       config.video.world = !config.video.world;
-                       client_ctrl.SetVideo(config.video.world);
+                       client_ctrl.SetVideo(!config.video.world);
                        break;
                case Keymap::TOGGLE_HUD:
-                       config.video.hud = !config.video.hud;
-                       client_ctrl.SetHUD(config.video.hud);
+                       client_ctrl.SetHUD(!config.video.hud);
                        break;
                case Keymap::TOGGLE_DEBUG:
-                       config.video.debug = !config.video.debug;
-                       client_ctrl.SetDebug(config.video.debug);
+                       client_ctrl.SetDebug(!config.video.debug);
+                       break;
+               case Keymap::CAMERA_NEXT:
+                       client_ctrl.NextCamera();
                        break;
 
                default:
@@ -597,7 +713,7 @@ void Interface::HandleRelease(const SDL_KeyboardEvent &event) {
 }
 
 void Interface::Handle(const SDL_MouseMotionEvent &event) {
-       if (!config.input.mouse) return;
+       if (locked || !config.input.mouse) return;
        player_ctrl.TurnHead(
                event.yrel * config.input.pitch_sensitivity,
                event.xrel * config.input.yaw_sensitivity);
@@ -651,7 +767,7 @@ void Interface::UpdateMovement() {
 }
 
 void Interface::InvAbs(int s) {
-       slot = s % num_slots;
+       int slot = s % num_slots;
        while (slot < 0) {
                slot += num_slots;
        }
@@ -659,7 +775,7 @@ void Interface::InvAbs(int s) {
 }
 
 void Interface::InvRel(int delta) {
-       InvAbs(slot + delta);
+       InvAbs(player_ctrl.GetPlayer().GetInventorySlot() + delta);
 }
 
 
@@ -714,7 +830,6 @@ void Keymap::LoadDefault() {
        Map(SDL_SCANCODE_0, INV_10);
 
        Map(SDL_SCANCODE_INSERT, SECONDARY);
-       Map(SDL_SCANCODE_RETURN, SECONDARY);
        Map(SDL_SCANCODE_MENU, TERTIARY);
        Map(SDL_SCANCODE_DELETE, PRIMARY);
        Map(SDL_SCANCODE_BACKSPACE, PRIMARY);
@@ -723,6 +838,7 @@ void Keymap::LoadDefault() {
        Map(SDL_SCANCODE_F2, TOGGLE_VIDEO);
        Map(SDL_SCANCODE_F3, TOGGLE_DEBUG);
        Map(SDL_SCANCODE_F4, TOGGLE_AUDIO);
+       Map(SDL_SCANCODE_F5, CAMERA_NEXT);
 
        Map(SDL_SCANCODE_ESCAPE, EXIT);
 }
@@ -808,6 +924,7 @@ std::map<std::string, Keymap::Action> action_map = {
        { "toggle_video", Keymap::TOGGLE_VIDEO },
        { "toggle_hud", Keymap::TOGGLE_HUD },
        { "toggle_debug", Keymap::TOGGLE_DEBUG },
+       { "camera_next", Keymap::CAMERA_NEXT },
 
        { "exit", Keymap::EXIT },
 };