X-Git-Url: http://git.localhorst.tv/?a=blobdiff_plain;f=src%2Fui%2Fui.cpp;h=5ff408be5fddc4f615eb2122230603b6b8cc6516;hb=dcd54cacda98c2c0f7cf0c7a9131fb858d8ee10a;hp=1e8a980112c0982fe69a6729849d37b75e50a959;hpb=b066e776622f96e906600a0c4a08de392bd03676;p=blank.git diff --git a/src/ui/ui.cpp b/src/ui/ui.cpp index 1e8a980..5ff408b 100644 --- a/src/ui/ui.cpp +++ b/src/ui/ui.cpp @@ -12,10 +12,13 @@ #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/shapes.hpp" +#include "../model/bounds.hpp" +#include "../net/CongestionControl.hpp" #include "../world/BlockLookup.hpp" #include "../world/World.hpp" #include "../world/WorldManipulator.hpp" @@ -26,30 +29,110 @@ #include #include #include +#include #include #include namespace blank { -DirectInput::DirectInput(World &world, Player &player, WorldManipulator &manip) +PlayerController::PlayerController(World &world, Player &player) : 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) { +, 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 DirectInput::Update(int dt) { - dirty = true; // world has changed in the meantime +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); @@ -63,31 +146,6 @@ void DirectInput::Update(int dt) { } } -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(); @@ -118,58 +176,74 @@ 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); + if (!BlockFocus()) return; + SelectInventory(BlockFocus().GetBlock().type - 1); } void DirectInput::PlaceBlock() { + // update block focus UpdatePlayer(); - if (!aim_world) return; + // do nothing if not looking at any block + if (!BlockFocus()) return; - BlockLookup next_block(aim_world.chunk, aim_world.BlockPos(), Block::NormalFace(aim_world.normal)); + // 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(player.GetInventorySlot() + 1)); - dirty = true; + + // "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 (!aim_world) return; - manip.SetBlock(aim_world.GetChunk(), aim_world.block, Block(0)); - dirty = true; + if (!BlockFocus()) return; + manip.SetBlock(BlockFocus().GetChunk(), BlockFocus().block, Block(0)); + Invalidate(); } @@ -194,11 +268,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)); @@ -216,42 +298,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 - OutlineModel::Buffer buf; + PrimitiveMesh::Buffer buf; buf.vertices = std::vector({ { -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({ + buf.indices = std::vector({ 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 { -OutlineModel::Buffer outl_buf; +PrimitiveMesh::Buffer outl_buf; } @@ -259,7 +355,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.FillOutlineModel(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); @@ -299,7 +395,7 @@ void HUD::DisplayNone() { void HUD::Display(const BlockType &type) { block_buf.Clear(); - type.FillEntityModel(block_buf); + type.FillEntityMesh(block_buf); block.Update(block_buf); block_label.Set(env.assets.small_ui_font, type.label); @@ -330,10 +426,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) { @@ -344,6 +440,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()) { @@ -362,9 +479,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 @@ -375,6 +492,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); @@ -385,16 +504,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 @@ -407,26 +526,35 @@ 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); + } } } -InteractiveManipulator::InteractiveManipulator(Environment &env, Entity &player) +InteractiveManipulator::InteractiveManipulator(Audio &audio, const SoundBank &sounds, Entity &player) : player(player) -, audio(env.audio) -, place_sound(env.loader.LoadSound("thump")) -, remove_sound(env.loader.LoadSound("plop")) { +, audio(audio) +, sounds(sounds) { } void InteractiveManipulator::SetBlock(Chunk &chunk, int index, const Block &block) { + const BlockType &old_type = chunk.Type(index); chunk.SetBlock(index, block); + const BlockType &new_type = chunk.Type(index); glm::vec3 coords = chunk.ToSceneCoords(player.ChunkCoords(), Chunk::ToCoords(index)); - // TODO: get sound effect from block type - if (block.type == 0) { - audio.Play(remove_sound, coords); + if (new_type.id == 0) { + if (old_type.remove_sound >= 0) { + audio.Play(sounds[old_type.remove_sound], coords); + } } else { - audio.Play(place_sound, coords); + if (new_type.place_sound >= 0) { + audio.Play(sounds[new_type.place_sound], coords); + } } } @@ -442,11 +570,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; @@ -512,20 +649,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: @@ -578,7 +714,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); @@ -632,14 +768,15 @@ void Interface::UpdateMovement() { } void Interface::InvAbs(int s) { - slot = s % num_slots; + int slot = s % num_slots; while (slot < 0) { slot += num_slots; } + player_ctrl.SelectInventory(slot); } void Interface::InvRel(int delta) { - InvAbs(slot + delta); + InvAbs(player_ctrl.GetPlayer().GetInventorySlot() + delta); } @@ -694,7 +831,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); @@ -703,6 +839,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); } @@ -788,6 +925,7 @@ std::map 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 }, };