From b992a826eaa7618eeb2274d47e3e2de2f8dd026a Mon Sep 17 00:00:00 2001 From: Asuro Date: Tue, 18 Feb 2025 02:46:25 +0100 Subject: [PATCH] camera stuff --- src/engine/Shared.h | 8 +++++ src/engine/Window.cpp | 6 ++-- src/game/Global.cpp | 40 ++++++++++++++++++--- src/game/Global.h | 4 +++ src/game/Instance.h | 3 +- src/game/Level.cpp | 43 ++++++++++++++++------- src/game/Setup.cpp | 3 +- src/game/compiled-shaders/dx11/frag.bin | Bin 12522 -> 12186 bytes src/game/compiled-shaders/glsl/frag.bin | Bin 13175 -> 13148 bytes src/game/compiled-shaders/spirv/frag.bin | Bin 7810 -> 7474 bytes src/game/rendering/Rendering.cpp | 17 ++++----- 11 files changed, 95 insertions(+), 29 deletions(-) diff --git a/src/engine/Shared.h b/src/engine/Shared.h index 9669249..e0cd99a 100644 --- a/src/engine/Shared.h +++ b/src/engine/Shared.h @@ -9,9 +9,17 @@ enum class PerfCounterType { WindowEvents, GameDelta, + GameLevelUpdate, + Submit, COUNT }; +constexpr const char* PerfCounterNames[(int32_t)PerfCounterType::COUNT]{ + "WindowEvt", + "Delta", + "Level", + "Submit", +}; struct PerfCounter { static constexpr int32_t TimeWindow = 128; diff --git a/src/engine/Window.cpp b/src/engine/Window.cpp index 623b4e1..bf3960e 100644 --- a/src/engine/Window.cpp +++ b/src/engine/Window.cpp @@ -1,4 +1,5 @@ #include "SDL3/SDL_events.h" +#include "SDL3/SDL_mouse.h" #include "Shared.h" #include "Window.h" #include "bx/timer.h" @@ -27,6 +28,7 @@ void EngineWindow::Startup(SharedWindowData& shared) printf("Failed to get window pointer!\n"); return; } + SDL_SetWindowRelativeMouseMode(Window, true); } void EngineWindow::Update(SharedWindowData& shared) @@ -59,8 +61,6 @@ void EngineWindow::Update(SharedWindowData& shared) } case SDL_EVENT_MOUSE_MOTION: { - shared.MouseDeltaX += evt.motion.xrel; - shared.MouseDeltaY += evt.motion.yrel; break; } case SDL_EVENT_MOUSE_BUTTON_DOWN: @@ -77,6 +77,8 @@ void EngineWindow::Update(SharedWindowData& shared) break; } } + SDL_GetRelativeMouseState(&shared.MouseDeltaX, &shared.MouseDeltaY); + END_PERF(shared.PerfCounters, PerfCounterType::WindowEvents, shared.FrameCounter); } diff --git a/src/game/Global.cpp b/src/game/Global.cpp index 1865573..5ab0a52 100644 --- a/src/game/Global.cpp +++ b/src/game/Global.cpp @@ -5,6 +5,7 @@ #include "bx/math.h" #include #include +#include namespace { @@ -33,11 +34,8 @@ void Transform::Translate(bx::Vec3 offset) void Transform::TranslateLocal(bx::Vec3 offset) { - UpdateMatrix(); - float offsetPtr[4]{offset.x, offset.y, offset.z, 0.0f}; - float localOffset[4]{0.0f}; - bx::vec4MulMtx(localOffset, offsetPtr, MI.M); - Position = bx::add(Position, {localOffset[0], localOffset[1], localOffset[2]}); + bx::Vec3 localOffset = GlobalToLocalDirection(offset); + Position = bx::add(Position, localOffset); } void Transform::Rotate(bx::Vec3 rotation) @@ -122,3 +120,35 @@ namespace Game return reinterpret_cast(ptrAligned); } } // namespace Game +bx::Vec3 Transform::GlobalToLocalDirection(bx::Vec3 global) +{ + UpdateMatrix(); + float in[4]{global.x, global.y, global.z, 0.0f}; + float out[4]{0.0f}; + bx::vec4MulMtx(out, in, MI.M); + return {out[0], out[1], out[2]}; +} +bx::Vec3 Transform::GlobalToLocalPoint(bx::Vec3 global) +{ + UpdateMatrix(); + float in[4]{global.x, global.y, global.z, 1.0f}; + float out[4]{0.0f}; + bx::vec4MulMtx(out, in, MI.M); + return {out[0], out[1], out[2]}; +} +bx::Vec3 Transform::LocalToGlobalDirection(bx::Vec3 local) +{ + UpdateMatrix(); + float in[4]{local.x, local.y, local.z, 0.0f}; + float out[4]{0.0f}; + bx::vec4MulMtx(out, in, M.M); + return {out[0], out[1], out[2]}; +} +bx::Vec3 Transform::LocalToGlobalPoint(bx::Vec3 local) +{ + UpdateMatrix(); + float in[4]{local.x, local.y, local.z, 1.0f}; + float out[4]{0.0f}; + bx::vec4MulMtx(out, in, M.M); + return {out[0], out[1], out[2]}; +} diff --git a/src/game/Global.h b/src/game/Global.h index 2983719..bd275de 100644 --- a/src/game/Global.h +++ b/src/game/Global.h @@ -59,6 +59,10 @@ struct Transform void TranslateLocal(bx::Vec3 offset); void Rotate(bx::Vec3 rotation); void RotateLocal(bx::Vec3 rotation); + bx::Vec3 LocalToGlobalPoint(bx::Vec3 local); + bx::Vec3 LocalToGlobalDirection(bx::Vec3 local); + bx::Vec3 GlobalToLocalPoint(bx::Vec3 global); + bx::Vec3 GlobalToLocalDirection(bx::Vec3 global); bx::Vec3 Right() const; bx::Vec3 Up() const; bx::Vec3 Forward() const; diff --git a/src/game/Instance.h b/src/game/Instance.h index 72762fa..4d387d0 100644 --- a/src/game/Instance.h +++ b/src/game/Instance.h @@ -25,7 +25,8 @@ namespace Game Transform FreeflyCamTransform; float FreeflyXRot = 0.0f; float FreeflyYRot = 0.0f; - Transform PlayerTransform; + float WalkXRot = 0.0f; + float WalkYRot = 0.0f; Transform PlayerCamTransform; PlayerMode Mode = PlayerMode::Freefly; }; diff --git a/src/game/Level.cpp b/src/game/Level.cpp index 7209615..d7f9341 100644 --- a/src/game/Level.cpp +++ b/src/game/Level.cpp @@ -67,9 +67,11 @@ namespace Game void Level::Update() { + START_PERF(); PlayerData& player = GetInstance().Player; float delta = GetInstance().Time.Delta; + delta = 1.0f / 144.0f; constexpr float moveSpeed = 10.0f; constexpr float rotSpeed = 0.6f; @@ -78,25 +80,42 @@ namespace Game bx::Vec3 moveInput = bx::Vec3{rightInput, forwardInput, 0.0f}; moveInput = bx::normalize(moveInput); bx::Vec3 inputVec = {moveInput.x * delta * moveSpeed, 0.0f, moveInput.y * delta * moveSpeed}; + Vec2 mouseMovement = GetMouseMovement(); + bx::Vec3 rotInput = {mouseMovement.y * delta * rotSpeed, mouseMovement.x * delta * rotSpeed, 0.0f}; - bx::Vec3 camForward = player.FreeflyCamTransform.Forward(); - bx::Vec3 camRight = player.FreeflyCamTransform.Right(); - - if (GetMouseButton(MouseButton::Left)) + if (GetKeyPressedNow(ScanCode::F1)) { - Vec2 mouseMovement = GetMouseMovement(); - bx::Vec3 rotInput = {mouseMovement.y * delta * rotSpeed, mouseMovement.x * delta * rotSpeed, 0.0f}; - player.FreeflyXRot += rotInput.x; - player.FreeflyYRot += rotInput.y; - bx::mtxRotateY(player.FreeflyCamTransform.Rotation.M, player.FreeflyYRot); - player.FreeflyCamTransform.RotateLocal({player.FreeflyXRot, 0.0f, 0.0f}); + player.Mode = player.Mode == PlayerMode::Walk ? PlayerMode::Freefly : PlayerMode::Walk; } - player.FreeflyCamTransform.TranslateLocal({0.0f, 0.0f, -inputVec.z}); - player.FreeflyCamTransform.TranslateLocal({-inputVec.x, 0.0f, 0.0f}); + if (player.Mode == PlayerMode::Freefly) + { + if (GetMouseButton(MouseButton::Left)) + { + player.FreeflyXRot += rotInput.x; + player.FreeflyYRot += rotInput.y; + bx::mtxRotateY(player.FreeflyCamTransform.Rotation.M, player.FreeflyYRot); + player.FreeflyCamTransform.RotateLocal({player.FreeflyXRot, 0.0f, 0.0f}); + } + + player.FreeflyCamTransform.TranslateLocal({0.0f, 0.0f, -inputVec.z}); + player.FreeflyCamTransform.TranslateLocal({-inputVec.x, 0.0f, 0.0f}); + } + else if (player.Mode == PlayerMode::Walk) + { + player.PlayerCamTransform.TranslateLocal({0.0f, 0.0f, -inputVec.z}); + player.PlayerCamTransform.TranslateLocal({-inputVec.x, 0.0f, 0.0f}); + player.PlayerCamTransform.Position.y = -3.0f; + + player.WalkXRot += rotInput.x; + player.WalkYRot += rotInput.y; + bx::mtxRotateY(player.PlayerCamTransform.Rotation.M, player.WalkYRot); + player.PlayerCamTransform.RotateLocal({player.WalkXRot, 0.0f, 0.0f}); + } Cubes.Update(); Tests.Update(); + END_PERF(GetShared().Window.PerfCounters, PerfCounterType::GameLevelUpdate, GetShared().Window.FrameCounter); } void Cube::Setup() diff --git a/src/game/Setup.cpp b/src/game/Setup.cpp index ae12fc9..40e4542 100644 --- a/src/game/Setup.cpp +++ b/src/game/Setup.cpp @@ -45,6 +45,7 @@ namespace Game Log("Game instance size changed, resetting!"); instance = {}; } + instance.UsedScratchAmount = 0; SetShared(shared); SetInstance(instance); SetupInstance.Rendering.Setup(); @@ -55,7 +56,7 @@ namespace Game void Update() { auto& inst = GetInstance(); - int64_t newNowHP = bx::getHPCounter() - GetInstance().Time.StartTime; + int64_t newNowHP = bx::getHPCounter() - inst.Time.StartTime; inst.Time.DeltaHP = newNowHP - inst.Time.NowHP; inst.Time.NowHP = newNowHP; inst.Time.Now = (double)inst.Time.NowHP / bx::getHPFrequency(); diff --git a/src/game/compiled-shaders/dx11/frag.bin b/src/game/compiled-shaders/dx11/frag.bin index df77b99c9c530962bec3b64238f2cb797ecd226d..54ac779da2d227deb077ab8dbe04acf38cdd3d5c 100644 GIT binary patch delta 146 zcmaErI4gdFpPY+61A|M1lk+W!n|2}RJk-vuYkfcM+uB2n3=9k|`V(`$aT@3`Fa$6# zF!XHBX4K~0yooP`aq=;loW=757&q?{%wgQ@B%Hyy`It-!BdY}i1H04Y{ese))8u>@ zH=mQwVcZ<1n87)@j!j_mE3FX5%~3izjGQ24N(=%FjFZpHh;L@n^I_barQg5^0HLBT A=>Px# delta 211 zcmbOg|0;2UpWGA!1_qZ1C+A!1otyMN)@xkKTKuM)oxg{Xfq`L)!Ni! z`I1luC!@*a)6!Cm_M5F_Qb3CC%S&&bCks{&VsgoYnfbz!Obzyve`(5Wo}}QzIr$%p k6jOu!=6jkcjFYPrA*MNL=O8%ibin%F>q&2x(raJ@07}tCFaQ7m diff --git a/src/game/compiled-shaders/glsl/frag.bin b/src/game/compiled-shaders/glsl/frag.bin index 9230797c8c1dd087fb261fa017331408d78be37f..088a1ef0742c08de33b1408b61a9ed7b36bb8029 100644 GIT binary patch delta 29 hcmeyKb|;PBE!czGPyA0l0~mO2LSz4@MtB{*mp#kCOC>ZJ)Xj*T6uaV0I E01h+~EdT%j diff --git a/src/game/compiled-shaders/spirv/frag.bin b/src/game/compiled-shaders/spirv/frag.bin index 30eb60682a7003dd92f9688b966d43e73699ee02..7dbdd153b1cb138b3bec650ae9caef9329275afd 100644 GIT binary patch literal 7474 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 literal 7810 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 XlCxuA2G4DR