diff --git a/destrum/assets_src/DJI_0001_stitch.jpg b/destrum/assets_src/DJI_0001_stitch.jpg new file mode 100644 index 0000000..7d7a00f Binary files /dev/null and b/destrum/assets_src/DJI_0001_stitch.jpg differ diff --git a/destrum/assets_src/kenny/Dark/texture_01.png b/destrum/assets_src/kenny/Dark/texture_01.png new file mode 100644 index 0000000..69be211 Binary files /dev/null and b/destrum/assets_src/kenny/Dark/texture_01.png differ diff --git a/destrum/assets_src/kenny/Dark/texture_02.png b/destrum/assets_src/kenny/Dark/texture_02.png new file mode 100644 index 0000000..6fb471b Binary files /dev/null and b/destrum/assets_src/kenny/Dark/texture_02.png differ diff --git a/destrum/assets_src/kenny/Dark/texture_03.png b/destrum/assets_src/kenny/Dark/texture_03.png new file mode 100644 index 0000000..3f8b186 Binary files /dev/null and b/destrum/assets_src/kenny/Dark/texture_03.png differ diff --git a/destrum/assets_src/kenny/Dark/texture_04.png b/destrum/assets_src/kenny/Dark/texture_04.png new file mode 100644 index 0000000..e2bc22b Binary files /dev/null and b/destrum/assets_src/kenny/Dark/texture_04.png differ diff --git a/destrum/assets_src/kenny/Dark/texture_05.png b/destrum/assets_src/kenny/Dark/texture_05.png new file mode 100644 index 0000000..3fd2e56 Binary files /dev/null and b/destrum/assets_src/kenny/Dark/texture_05.png differ diff --git a/destrum/assets_src/kenny/Dark/texture_06.png b/destrum/assets_src/kenny/Dark/texture_06.png new file mode 100644 index 0000000..45d4a34 Binary files /dev/null and b/destrum/assets_src/kenny/Dark/texture_06.png differ diff --git a/destrum/assets_src/kenny/Dark/texture_07.png b/destrum/assets_src/kenny/Dark/texture_07.png new file mode 100644 index 0000000..adf5e6f Binary files /dev/null and b/destrum/assets_src/kenny/Dark/texture_07.png differ diff --git a/destrum/assets_src/kenny/Dark/texture_08.png b/destrum/assets_src/kenny/Dark/texture_08.png new file mode 100644 index 0000000..a5a9f24 Binary files /dev/null and b/destrum/assets_src/kenny/Dark/texture_08.png differ diff --git a/destrum/assets_src/kenny/Dark/texture_09.png b/destrum/assets_src/kenny/Dark/texture_09.png new file mode 100644 index 0000000..57cc607 Binary files /dev/null and b/destrum/assets_src/kenny/Dark/texture_09.png differ diff --git a/destrum/assets_src/kenny/Dark/texture_10.png b/destrum/assets_src/kenny/Dark/texture_10.png new file mode 100644 index 0000000..4c737d0 Binary files /dev/null and b/destrum/assets_src/kenny/Dark/texture_10.png differ diff --git a/destrum/assets_src/kenny/Dark/texture_11.png b/destrum/assets_src/kenny/Dark/texture_11.png new file mode 100644 index 0000000..424ba71 Binary files /dev/null and b/destrum/assets_src/kenny/Dark/texture_11.png differ diff --git a/destrum/assets_src/kenny/Dark/texture_12.png b/destrum/assets_src/kenny/Dark/texture_12.png new file mode 100644 index 0000000..32169db Binary files /dev/null and b/destrum/assets_src/kenny/Dark/texture_12.png differ diff --git a/destrum/assets_src/kenny/Dark/texture_13.png b/destrum/assets_src/kenny/Dark/texture_13.png new file mode 100644 index 0000000..13c4388 Binary files /dev/null and b/destrum/assets_src/kenny/Dark/texture_13.png differ diff --git a/destrum/assets_src/kenny/Green/texture_01.png b/destrum/assets_src/kenny/Green/texture_01.png new file mode 100644 index 0000000..d576514 Binary files /dev/null and b/destrum/assets_src/kenny/Green/texture_01.png differ diff --git a/destrum/assets_src/kenny/Green/texture_02.png b/destrum/assets_src/kenny/Green/texture_02.png new file mode 100644 index 0000000..7bc7cf8 Binary files /dev/null and b/destrum/assets_src/kenny/Green/texture_02.png differ diff --git a/destrum/assets_src/kenny/Green/texture_03.png b/destrum/assets_src/kenny/Green/texture_03.png new file mode 100644 index 0000000..e2a3889 Binary files /dev/null and b/destrum/assets_src/kenny/Green/texture_03.png differ diff --git a/destrum/assets_src/kenny/Green/texture_04.png b/destrum/assets_src/kenny/Green/texture_04.png new file mode 100644 index 0000000..3952bef Binary files /dev/null and b/destrum/assets_src/kenny/Green/texture_04.png differ diff --git a/destrum/assets_src/kenny/Green/texture_05.png b/destrum/assets_src/kenny/Green/texture_05.png new file mode 100644 index 0000000..84976ba Binary files /dev/null and b/destrum/assets_src/kenny/Green/texture_05.png differ diff --git a/destrum/assets_src/kenny/Green/texture_06.png b/destrum/assets_src/kenny/Green/texture_06.png new file mode 100644 index 0000000..1128198 Binary files /dev/null and b/destrum/assets_src/kenny/Green/texture_06.png differ diff --git a/destrum/assets_src/kenny/Green/texture_07.png b/destrum/assets_src/kenny/Green/texture_07.png new file mode 100644 index 0000000..3160860 Binary files /dev/null and b/destrum/assets_src/kenny/Green/texture_07.png differ diff --git a/destrum/assets_src/kenny/Green/texture_08.png b/destrum/assets_src/kenny/Green/texture_08.png new file mode 100644 index 0000000..386293d Binary files /dev/null and b/destrum/assets_src/kenny/Green/texture_08.png differ diff --git a/destrum/assets_src/kenny/Green/texture_09.png b/destrum/assets_src/kenny/Green/texture_09.png new file mode 100644 index 0000000..48234f6 Binary files /dev/null and b/destrum/assets_src/kenny/Green/texture_09.png differ diff --git a/destrum/assets_src/kenny/Green/texture_10.png b/destrum/assets_src/kenny/Green/texture_10.png new file mode 100644 index 0000000..e4ab057 Binary files /dev/null and b/destrum/assets_src/kenny/Green/texture_10.png differ diff --git a/destrum/assets_src/kenny/Green/texture_11.png b/destrum/assets_src/kenny/Green/texture_11.png new file mode 100644 index 0000000..82ad458 Binary files /dev/null and b/destrum/assets_src/kenny/Green/texture_11.png differ diff --git a/destrum/assets_src/kenny/Green/texture_12.png b/destrum/assets_src/kenny/Green/texture_12.png new file mode 100644 index 0000000..a15000d Binary files /dev/null and b/destrum/assets_src/kenny/Green/texture_12.png differ diff --git a/destrum/assets_src/kenny/Green/texture_13.png b/destrum/assets_src/kenny/Green/texture_13.png new file mode 100644 index 0000000..930fdf0 Binary files /dev/null and b/destrum/assets_src/kenny/Green/texture_13.png differ diff --git a/destrum/assets_src/kenny/Light/texture_01.png b/destrum/assets_src/kenny/Light/texture_01.png new file mode 100644 index 0000000..60b632b Binary files /dev/null and b/destrum/assets_src/kenny/Light/texture_01.png differ diff --git a/destrum/assets_src/kenny/Light/texture_02.png b/destrum/assets_src/kenny/Light/texture_02.png new file mode 100644 index 0000000..19aad62 Binary files /dev/null and b/destrum/assets_src/kenny/Light/texture_02.png differ diff --git a/destrum/assets_src/kenny/Light/texture_03.png b/destrum/assets_src/kenny/Light/texture_03.png new file mode 100644 index 0000000..a8a6c06 Binary files /dev/null and b/destrum/assets_src/kenny/Light/texture_03.png differ diff --git a/destrum/assets_src/kenny/Light/texture_04.png b/destrum/assets_src/kenny/Light/texture_04.png new file mode 100644 index 0000000..b8270e1 Binary files /dev/null and b/destrum/assets_src/kenny/Light/texture_04.png differ diff --git a/destrum/assets_src/kenny/Light/texture_05.png b/destrum/assets_src/kenny/Light/texture_05.png new file mode 100644 index 0000000..5b985bf Binary files /dev/null and b/destrum/assets_src/kenny/Light/texture_05.png differ diff --git a/destrum/assets_src/kenny/Light/texture_06.png b/destrum/assets_src/kenny/Light/texture_06.png new file mode 100644 index 0000000..195ad77 Binary files /dev/null and b/destrum/assets_src/kenny/Light/texture_06.png differ diff --git a/destrum/assets_src/kenny/Light/texture_07.png b/destrum/assets_src/kenny/Light/texture_07.png new file mode 100644 index 0000000..fbf92e0 Binary files /dev/null and b/destrum/assets_src/kenny/Light/texture_07.png differ diff --git a/destrum/assets_src/kenny/Light/texture_08.png b/destrum/assets_src/kenny/Light/texture_08.png new file mode 100644 index 0000000..72e1a07 Binary files /dev/null and b/destrum/assets_src/kenny/Light/texture_08.png differ diff --git a/destrum/assets_src/kenny/Light/texture_09.png b/destrum/assets_src/kenny/Light/texture_09.png new file mode 100644 index 0000000..9ada5dd Binary files /dev/null and b/destrum/assets_src/kenny/Light/texture_09.png differ diff --git a/destrum/assets_src/kenny/Light/texture_10.png b/destrum/assets_src/kenny/Light/texture_10.png new file mode 100644 index 0000000..4fbcc80 Binary files /dev/null and b/destrum/assets_src/kenny/Light/texture_10.png differ diff --git a/destrum/assets_src/kenny/Light/texture_11.png b/destrum/assets_src/kenny/Light/texture_11.png new file mode 100644 index 0000000..840ecec Binary files /dev/null and b/destrum/assets_src/kenny/Light/texture_11.png differ diff --git a/destrum/assets_src/kenny/Light/texture_12.png b/destrum/assets_src/kenny/Light/texture_12.png new file mode 100644 index 0000000..25c2e78 Binary files /dev/null and b/destrum/assets_src/kenny/Light/texture_12.png differ diff --git a/destrum/assets_src/kenny/Light/texture_13.png b/destrum/assets_src/kenny/Light/texture_13.png new file mode 100644 index 0000000..a235965 Binary files /dev/null and b/destrum/assets_src/kenny/Light/texture_13.png differ diff --git a/destrum/assets_src/kenny/Orange/texture_01.png b/destrum/assets_src/kenny/Orange/texture_01.png new file mode 100644 index 0000000..4f5bf92 Binary files /dev/null and b/destrum/assets_src/kenny/Orange/texture_01.png differ diff --git a/destrum/assets_src/kenny/Orange/texture_02.png b/destrum/assets_src/kenny/Orange/texture_02.png new file mode 100644 index 0000000..dec5b59 Binary files /dev/null and b/destrum/assets_src/kenny/Orange/texture_02.png differ diff --git a/destrum/assets_src/kenny/Orange/texture_03.png b/destrum/assets_src/kenny/Orange/texture_03.png new file mode 100644 index 0000000..666197f Binary files /dev/null and b/destrum/assets_src/kenny/Orange/texture_03.png differ diff --git a/destrum/assets_src/kenny/Orange/texture_04.png b/destrum/assets_src/kenny/Orange/texture_04.png new file mode 100644 index 0000000..23d6fc4 Binary files /dev/null and b/destrum/assets_src/kenny/Orange/texture_04.png differ diff --git a/destrum/assets_src/kenny/Orange/texture_05.png b/destrum/assets_src/kenny/Orange/texture_05.png new file mode 100644 index 0000000..1e0448a Binary files /dev/null and b/destrum/assets_src/kenny/Orange/texture_05.png differ diff --git a/destrum/assets_src/kenny/Orange/texture_06.png b/destrum/assets_src/kenny/Orange/texture_06.png new file mode 100644 index 0000000..5486825 Binary files /dev/null and b/destrum/assets_src/kenny/Orange/texture_06.png differ diff --git a/destrum/assets_src/kenny/Orange/texture_07.png b/destrum/assets_src/kenny/Orange/texture_07.png new file mode 100644 index 0000000..95f2790 Binary files /dev/null and b/destrum/assets_src/kenny/Orange/texture_07.png differ diff --git a/destrum/assets_src/kenny/Orange/texture_08.png b/destrum/assets_src/kenny/Orange/texture_08.png new file mode 100644 index 0000000..5a500d9 Binary files /dev/null and b/destrum/assets_src/kenny/Orange/texture_08.png differ diff --git a/destrum/assets_src/kenny/Orange/texture_09.png b/destrum/assets_src/kenny/Orange/texture_09.png new file mode 100644 index 0000000..adcfa33 Binary files /dev/null and b/destrum/assets_src/kenny/Orange/texture_09.png differ diff --git a/destrum/assets_src/kenny/Orange/texture_10.png b/destrum/assets_src/kenny/Orange/texture_10.png new file mode 100644 index 0000000..aa227e5 Binary files /dev/null and b/destrum/assets_src/kenny/Orange/texture_10.png differ diff --git a/destrum/assets_src/kenny/Orange/texture_11.png b/destrum/assets_src/kenny/Orange/texture_11.png new file mode 100644 index 0000000..dc94567 Binary files /dev/null and b/destrum/assets_src/kenny/Orange/texture_11.png differ diff --git a/destrum/assets_src/kenny/Orange/texture_12.png b/destrum/assets_src/kenny/Orange/texture_12.png new file mode 100644 index 0000000..b730544 Binary files /dev/null and b/destrum/assets_src/kenny/Orange/texture_12.png differ diff --git a/destrum/assets_src/kenny/Orange/texture_13.png b/destrum/assets_src/kenny/Orange/texture_13.png new file mode 100644 index 0000000..01f4aa8 Binary files /dev/null and b/destrum/assets_src/kenny/Orange/texture_13.png differ diff --git a/destrum/assets_src/kenny/Purple/texture_01.png b/destrum/assets_src/kenny/Purple/texture_01.png new file mode 100644 index 0000000..d501875 Binary files /dev/null and b/destrum/assets_src/kenny/Purple/texture_01.png differ diff --git a/destrum/assets_src/kenny/Purple/texture_02.png b/destrum/assets_src/kenny/Purple/texture_02.png new file mode 100644 index 0000000..48a51c1 Binary files /dev/null and b/destrum/assets_src/kenny/Purple/texture_02.png differ diff --git a/destrum/assets_src/kenny/Purple/texture_03.png b/destrum/assets_src/kenny/Purple/texture_03.png new file mode 100644 index 0000000..5f97f24 Binary files /dev/null and b/destrum/assets_src/kenny/Purple/texture_03.png differ diff --git a/destrum/assets_src/kenny/Purple/texture_04.png b/destrum/assets_src/kenny/Purple/texture_04.png new file mode 100644 index 0000000..b81951c Binary files /dev/null and b/destrum/assets_src/kenny/Purple/texture_04.png differ diff --git a/destrum/assets_src/kenny/Purple/texture_05.png b/destrum/assets_src/kenny/Purple/texture_05.png new file mode 100644 index 0000000..52352c8 Binary files /dev/null and b/destrum/assets_src/kenny/Purple/texture_05.png differ diff --git a/destrum/assets_src/kenny/Purple/texture_06.png b/destrum/assets_src/kenny/Purple/texture_06.png new file mode 100644 index 0000000..86deeb3 Binary files /dev/null and b/destrum/assets_src/kenny/Purple/texture_06.png differ diff --git a/destrum/assets_src/kenny/Purple/texture_07.png b/destrum/assets_src/kenny/Purple/texture_07.png new file mode 100644 index 0000000..b71ce4e Binary files /dev/null and b/destrum/assets_src/kenny/Purple/texture_07.png differ diff --git a/destrum/assets_src/kenny/Purple/texture_08.png b/destrum/assets_src/kenny/Purple/texture_08.png new file mode 100644 index 0000000..470cc5c Binary files /dev/null and b/destrum/assets_src/kenny/Purple/texture_08.png differ diff --git a/destrum/assets_src/kenny/Purple/texture_09.png b/destrum/assets_src/kenny/Purple/texture_09.png new file mode 100644 index 0000000..4a3f689 Binary files /dev/null and b/destrum/assets_src/kenny/Purple/texture_09.png differ diff --git a/destrum/assets_src/kenny/Purple/texture_10.png b/destrum/assets_src/kenny/Purple/texture_10.png new file mode 100644 index 0000000..ab7e84b Binary files /dev/null and b/destrum/assets_src/kenny/Purple/texture_10.png differ diff --git a/destrum/assets_src/kenny/Purple/texture_11.png b/destrum/assets_src/kenny/Purple/texture_11.png new file mode 100644 index 0000000..ad7bebc Binary files /dev/null and b/destrum/assets_src/kenny/Purple/texture_11.png differ diff --git a/destrum/assets_src/kenny/Purple/texture_12.png b/destrum/assets_src/kenny/Purple/texture_12.png new file mode 100644 index 0000000..979ac5a Binary files /dev/null and b/destrum/assets_src/kenny/Purple/texture_12.png differ diff --git a/destrum/assets_src/kenny/Purple/texture_13.png b/destrum/assets_src/kenny/Purple/texture_13.png new file mode 100644 index 0000000..2a9df22 Binary files /dev/null and b/destrum/assets_src/kenny/Purple/texture_13.png differ diff --git a/destrum/assets_src/kenny/Red/texture_01.png b/destrum/assets_src/kenny/Red/texture_01.png new file mode 100644 index 0000000..1aaab41 Binary files /dev/null and b/destrum/assets_src/kenny/Red/texture_01.png differ diff --git a/destrum/assets_src/kenny/Red/texture_02.png b/destrum/assets_src/kenny/Red/texture_02.png new file mode 100644 index 0000000..bf1cb17 Binary files /dev/null and b/destrum/assets_src/kenny/Red/texture_02.png differ diff --git a/destrum/assets_src/kenny/Red/texture_03.png b/destrum/assets_src/kenny/Red/texture_03.png new file mode 100644 index 0000000..ff09c22 Binary files /dev/null and b/destrum/assets_src/kenny/Red/texture_03.png differ diff --git a/destrum/assets_src/kenny/Red/texture_04.png b/destrum/assets_src/kenny/Red/texture_04.png new file mode 100644 index 0000000..b5b77ff Binary files /dev/null and b/destrum/assets_src/kenny/Red/texture_04.png differ diff --git a/destrum/assets_src/kenny/Red/texture_05.png b/destrum/assets_src/kenny/Red/texture_05.png new file mode 100644 index 0000000..7827035 Binary files /dev/null and b/destrum/assets_src/kenny/Red/texture_05.png differ diff --git a/destrum/assets_src/kenny/Red/texture_06.png b/destrum/assets_src/kenny/Red/texture_06.png new file mode 100644 index 0000000..914a6f1 Binary files /dev/null and b/destrum/assets_src/kenny/Red/texture_06.png differ diff --git a/destrum/assets_src/kenny/Red/texture_07.png b/destrum/assets_src/kenny/Red/texture_07.png new file mode 100644 index 0000000..47d517b Binary files /dev/null and b/destrum/assets_src/kenny/Red/texture_07.png differ diff --git a/destrum/assets_src/kenny/Red/texture_08.png b/destrum/assets_src/kenny/Red/texture_08.png new file mode 100644 index 0000000..07cfc41 Binary files /dev/null and b/destrum/assets_src/kenny/Red/texture_08.png differ diff --git a/destrum/assets_src/kenny/Red/texture_09.png b/destrum/assets_src/kenny/Red/texture_09.png new file mode 100644 index 0000000..86d67d8 Binary files /dev/null and b/destrum/assets_src/kenny/Red/texture_09.png differ diff --git a/destrum/assets_src/kenny/Red/texture_10.png b/destrum/assets_src/kenny/Red/texture_10.png new file mode 100644 index 0000000..a9266d1 Binary files /dev/null and b/destrum/assets_src/kenny/Red/texture_10.png differ diff --git a/destrum/assets_src/kenny/Red/texture_11.png b/destrum/assets_src/kenny/Red/texture_11.png new file mode 100644 index 0000000..2acc544 Binary files /dev/null and b/destrum/assets_src/kenny/Red/texture_11.png differ diff --git a/destrum/assets_src/kenny/Red/texture_12.png b/destrum/assets_src/kenny/Red/texture_12.png new file mode 100644 index 0000000..7054c4d Binary files /dev/null and b/destrum/assets_src/kenny/Red/texture_12.png differ diff --git a/destrum/assets_src/kenny/Red/texture_13.png b/destrum/assets_src/kenny/Red/texture_13.png new file mode 100644 index 0000000..ab8c7b9 Binary files /dev/null and b/destrum/assets_src/kenny/Red/texture_13.png differ diff --git a/destrum/assets_src/shaders/mesh.frag b/destrum/assets_src/shaders/mesh.frag index 0f683f0..724bb05 100644 --- a/destrum/assets_src/shaders/mesh.frag +++ b/destrum/assets_src/shaders/mesh.frag @@ -91,16 +91,17 @@ void main() diffuseColor, roughness, metallic, f0, shadow); } - // emissive vec3 emissiveColor = material.emissiveColor.rgb * sampleTexture2D(material.emissiveTex, samplerID, inUV).rgb; fragColor += emissiveColor; - // ambient, the specular part keeps metals from turning black without lights float NoV = clamp(dot(n, v), 0.0001, 1.0); vec3 ambientLight = pcs.sceneData.ambientColor * pcs.sceneData.ambientIntensity; vec3 ambientSpecular = envBRDFApprox(f0, roughness, NoV); fragColor += (diffuseColor + ambientSpecular) * ambientLight * ambientOcclusion; + float fogAmount = pcs.sceneData.fogDensity * length(cameraPos - fragPos); + fragColor = mix(pcs.sceneData.fogColor, fragColor, exp(-fogAmount * fogAmount)); + if (pcs.sceneData.debugLightHeatmap != 0) { fragColor = mix(fragColor, lightHeatmapColor(tileLightCount), 0.5); } diff --git a/destrum/include/destrum/Components/Physics/Rigidbody.h b/destrum/include/destrum/Components/Physics/Rigidbody.h index 155b52b..d927ee4 100644 --- a/destrum/include/destrum/Components/Physics/Rigidbody.h +++ b/destrum/include/destrum/Components/Physics/Rigidbody.h @@ -58,6 +58,9 @@ public: [[nodiscard]] bool AllowsSleep() const { return m_AllowSleep; } void SetAllowSleep(bool allowSleep) { m_AllowSleep = allowSleep; NotifyPhysicsChanged(); } + [[nodiscard]] bool FreezesRotation() const { return m_FreezeRotation; } + void SetFreezeRotation(bool freezeRotation) { m_FreezeRotation = freezeRotation; NotifyPhysicsChanged(); } + [[nodiscard]] PhysicsWorld* GetPhysicsWorld() const { return m_World; } private: @@ -72,4 +75,5 @@ private: bool m_UseGravity{true}; bool m_AllowSleep{true}; + bool m_FreezeRotation{false}; }; diff --git a/destrum/include/destrum/Graphics/Pipelines/SkyboxPipeline.h b/destrum/include/destrum/Graphics/Pipelines/SkyboxPipeline.h index 5db2935..7280057 100644 --- a/destrum/include/destrum/Graphics/Pipelines/SkyboxPipeline.h +++ b/destrum/include/destrum/Graphics/Pipelines/SkyboxPipeline.h @@ -20,7 +20,7 @@ public: void init(GfxDevice& gfxDevice, RenderResources& resources, VkFormat drawImageFormat, VkFormat depthImageFormat); void cleanup(VkDevice device); - void draw(VkCommandBuffer cmd, RenderResources& resources, const Camera& camera); + void draw(VkCommandBuffer cmd, RenderResources& resources, const Camera& camera, float rotation); void setSkyboxImage(const ImageID skyboxId); @@ -30,8 +30,6 @@ private: std::unique_ptr pipeline; ImageID skyboxTextureId{NULL_IMAGE_ID}; - glm::mat4 skyboxRotation{1.f}; - struct SkyboxPushConstants { glm::mat4 invViewProj; glm::vec4 skyboxRot[3]; diff --git a/destrum/include/destrum/Graphics/Renderer.h b/destrum/include/destrum/Graphics/Renderer.h index 2c41691..86cedb6 100644 --- a/destrum/include/destrum/Graphics/Renderer.h +++ b/destrum/include/destrum/Graphics/Renderer.h @@ -34,6 +34,7 @@ public: float ambientIntensity; glm::vec3 fogColor; float fogDensity; + float skyboxRotation{0.0f}; // radians around the world up axis }; explicit GameRenderer(); diff --git a/destrum/include/destrum/Physics/JoltPhysicsWorld.h b/destrum/include/destrum/Physics/JoltPhysicsWorld.h index 9feaf8d..27d6295 100644 --- a/destrum/include/destrum/Physics/JoltPhysicsWorld.h +++ b/destrum/include/destrum/Physics/JoltPhysicsWorld.h @@ -53,7 +53,8 @@ public: bool Raycast(const glm::vec3& origin, const glm::vec3& direction, float maxDistance, - PhysicsRaycastHit& hit) const override; + PhysicsRaycastHit& hit, + PhysicsBodyHandle ignoreBody = {}) const override; std::vector ConsumeTriggerEvents() override; diff --git a/destrum/include/destrum/Physics/PhysicsTypes.h b/destrum/include/destrum/Physics/PhysicsTypes.h index e147308..72c1c5a 100644 --- a/destrum/include/destrum/Physics/PhysicsTypes.h +++ b/destrum/include/destrum/Physics/PhysicsTypes.h @@ -115,6 +115,7 @@ struct PhysicsBodyDesc { float mass{1.0f}; bool useGravity{true}; bool allowSleep{true}; + bool freezeRotation{false}; }; struct TriggerEvent { diff --git a/destrum/include/destrum/Physics/PhysicsWorld.h b/destrum/include/destrum/Physics/PhysicsWorld.h index 92ad1c3..7f06063 100644 --- a/destrum/include/destrum/Physics/PhysicsWorld.h +++ b/destrum/include/destrum/Physics/PhysicsWorld.h @@ -46,10 +46,12 @@ public: virtual void AddForce(PhysicsBodyHandle body, const glm::vec3& force) = 0; virtual void AddImpulse(PhysicsBodyHandle body, const glm::vec3& impulse) = 0; + // Triggers are never hit. ignoreBody is skipped too, e.g. so a character can cast from inside its own collider virtual bool Raycast(const glm::vec3& origin, const glm::vec3& direction, float maxDistance, - PhysicsRaycastHit& hit) const = 0; + PhysicsRaycastHit& hit, + PhysicsBodyHandle ignoreBody = {}) const = 0; // Trigger events accumulated during the last Step(). virtual std::vector ConsumeTriggerEvents() { return {}; } diff --git a/destrum/include/destrum/Physics/SimplePhysicsWorld.h b/destrum/include/destrum/Physics/SimplePhysicsWorld.h index a727f28..3c6057f 100644 --- a/destrum/include/destrum/Physics/SimplePhysicsWorld.h +++ b/destrum/include/destrum/Physics/SimplePhysicsWorld.h @@ -46,7 +46,8 @@ public: bool Raycast(const glm::vec3& origin, const glm::vec3& direction, float maxDistance, - PhysicsRaycastHit& hit) const override; + PhysicsRaycastHit& hit, + PhysicsBodyHandle ignoreBody = {}) const override; void SyncKinematicBodiesToPhysics(); void SyncDynamicBodiesToTransforms(); diff --git a/destrum/include/destrum/Scene/Scene.h b/destrum/include/destrum/Scene/Scene.h index 11bfb35..678ee1f 100644 --- a/destrum/include/destrum/Scene/Scene.h +++ b/destrum/include/destrum/Scene/Scene.h @@ -14,6 +14,18 @@ class CameraComponent; class GameObject; + + +struct SceneEnvironment { + glm::vec3 ambientColor{0.1f}; + float ambientIntensity{0.5f}; + + glm::vec3 fogColor{0.5f}; + float fogDensity{0.0f}; + + float skyboxRotationSpeed{0.01f}; + float skyboxRotation{0.0f}; +}; class SceneSerializer; class PhysicsWorld; @@ -107,6 +119,9 @@ public: PhysicsSceneBridge& GetPhysics() { return m_Physics; } + [[nodiscard]] SceneEnvironment& GetEnvironment() { return m_Environment; } + [[nodiscard]] const SceneEnvironment& GetEnvironment() const { return m_Environment; } + [[nodiscard]] CameraComponent* GetCurrentCamera() const; [[nodiscard]] std::vector GetCameras() const; void SetCurrentCamera(const CameraComponent* camera); @@ -134,6 +149,10 @@ private: ObjectId m_CurrentCameraId{InvalidObjectId}; + SceneEnvironment m_Environment{}; + + void DrawSettingsImGui(); + std::function m_registerBindings; std::function m_unregisterBindings; bool m_bindingsUnloaded{false}; diff --git a/destrum/include/destrum/Util/DeltaTime.h b/destrum/include/destrum/Util/DeltaTime.h index a566696..9caf2f2 100644 --- a/destrum/include/destrum/Util/DeltaTime.h +++ b/destrum/include/destrum/Util/DeltaTime.h @@ -14,6 +14,10 @@ public: [[nodiscard]] double FixedDeltaTime() const; [[nodiscard]] double DeltaTime() const; + // How far the current frame is between the last two fixed steps (0..1), used to interpolate physics objects + [[nodiscard]] float FixedAlpha() const { return m_FixedAlpha; } + void SetFixedAlpha(float alpha) { m_FixedAlpha = alpha; } + [[nodiscard]] std::chrono::nanoseconds SleepDuration() const; void Update(); @@ -25,6 +29,7 @@ private: static constexpr double m_FixedDeltaTime{1.0 / 60.0}; static constexpr double FPS{60}; double m_DeltaTime{}; + float m_FixedAlpha{1.0f}; std::chrono::high_resolution_clock::time_point m_PrevTime; }; diff --git a/destrum/include/destrum/Util/ImGuiUtils.h b/destrum/include/destrum/Util/ImGuiUtils.h index cff2ef2..d3e1457 100644 --- a/destrum/include/destrum/Util/ImGuiUtils.h +++ b/destrum/include/destrum/Util/ImGuiUtils.h @@ -24,6 +24,7 @@ namespace ImGuiUtils { const std::vector>& pendingAdditions, ObjectId& selectedObjectId, const std::function& createGameObject, + const std::function& drawSceneSettings, bool* open = nullptr); } diff --git a/destrum/src/App.cpp b/destrum/src/App.cpp index d9a4e9a..437e87c 100644 --- a/destrum/src/App.cpp +++ b/destrum/src/App.cpp @@ -1,3 +1,4 @@ +#include #include #include #include @@ -274,8 +275,7 @@ if (!isRunning) break; TracyPlot("Fixed Steps", static_cast(steps)); } - const float alpha = accumulator / fixedDt; - (void)alpha; + Time::GetInstance().SetFixedAlpha(std::clamp(accumulator / fixedDt, 0.0f, 1.0f)); { ZoneScopedN("Swapchain Resize Check"); diff --git a/destrum/src/Components/Physics/Rigidbody.cpp b/destrum/src/Components/Physics/Rigidbody.cpp index 80198ca..67020df 100644 --- a/destrum/src/Components/Physics/Rigidbody.cpp +++ b/destrum/src/Components/Physics/Rigidbody.cpp @@ -106,7 +106,8 @@ nlohmann::json Rigidbody::Serialize() const { {"friction", m_Friction}, {"restitution", m_Restitution}, {"useGravity", m_UseGravity}, - {"allowSleep", m_AllowSleep} + {"allowSleep", m_AllowSleep}, + {"freezeRotation", m_FreezeRotation} }; } @@ -129,6 +130,9 @@ void Rigidbody::Deserialize(const nlohmann::json& data) { if (data.contains("allowSleep")) { m_AllowSleep = data.at("allowSleep").get(); } + if (data.contains("freezeRotation")) { + m_FreezeRotation = data.at("freezeRotation").get(); + } } void Rigidbody::ImGuiInspector() { @@ -163,6 +167,11 @@ void Rigidbody::ImGuiInspector() { SetAllowSleep(allowSleep); } + bool freezeRotation = m_FreezeRotation; + if (ImGui::Checkbox("Freeze rotation", &freezeRotation)) { + SetFreezeRotation(freezeRotation); + } + if (HasPhysicsBody()) { const glm::vec3 velocity = GetLinearVelocity(); ImGui::Text( diff --git a/destrum/src/Graphics/Pipelines/SkyboxPipeline.cpp b/destrum/src/Graphics/Pipelines/SkyboxPipeline.cpp index b46db4e..f6fd3c4 100644 --- a/destrum/src/Graphics/Pipelines/SkyboxPipeline.cpp +++ b/destrum/src/Graphics/Pipelines/SkyboxPipeline.cpp @@ -2,7 +2,6 @@ #include #include -#include #include "volk.h" #include "glm/ext/matrix_transform.hpp" @@ -98,26 +97,20 @@ void SkyboxPipeline::cleanup(VkDevice device) void SkyboxPipeline::draw( VkCommandBuffer cmd, RenderResources& resources, - const Camera& camera) + const Camera& camera, + float rotation) { if (skyboxTextureId == NULL_IMAGE_ID) { return; } - const float rotationSpeed = 0.01f; - - skyboxRotation = glm::rotate( - skyboxRotation, - rotationSpeed * static_cast(Time::GetInstance().DeltaTime()), - glm::vec3(0.f, 1.f, 0.f)); - pipeline->bind(cmd); vkCmdSetPolygonModeEXT(cmd, VK_POLYGON_MODE_FILL); resources.bindBindlessDescSet(cmd, pipelineLayout); - const glm::mat3 r = glm::mat3(skyboxRotation); + const glm::mat3 r = glm::mat3(glm::rotate(glm::mat4{1.f}, rotation, glm::vec3(0.f, 1.f, 0.f))); // rotation only, translation in the inverse makes the sky jitter far from the origin const glm::mat4 viewRotation = glm::mat4(glm::mat3(camera.GetViewMatrix())); diff --git a/destrum/src/Graphics/Renderer.cpp b/destrum/src/Graphics/Renderer.cpp index 6a18f17..8a55b1a 100644 --- a/destrum/src/Graphics/Renderer.cpp +++ b/destrum/src/Graphics/Renderer.cpp @@ -306,7 +306,7 @@ void GameRenderer::draw(VkCommandBuffer cmd, GfxDevice& gfxDevice, const Camera& TracyVkZone(gfxDevice.getTracyVkCtx(), cmd, "SkyboxPipeline::draw"); vkutil::ScopedCmdLabel label(cmd, "SkyboxPipeline::draw", vkutil::labelColors::sky); - skyboxPipeline->draw(cmd, *resources, camera); + skyboxPipeline->draw(cmd, *resources, camera, sceneData.skyboxRotation); } { diff --git a/destrum/src/Physics/JoltPhysicsWorld.cpp b/destrum/src/Physics/JoltPhysicsWorld.cpp index 7852bdc..76f0e37 100644 --- a/destrum/src/Physics/JoltPhysicsWorld.cpp +++ b/destrum/src/Physics/JoltPhysicsWorld.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -95,6 +96,29 @@ namespace } } + // Raycasts skip triggers and optionally one body (e.g. the caster's own collider) + class RaycastBodyFilter final : public BodyFilter + { + public: + explicit RaycastBodyFilter(const BodyID& ignoredBody) + : m_IgnoredBody(ignoredBody) + { + } + + bool ShouldCollide(const BodyID& inBodyID) const override + { + return inBodyID != m_IgnoredBody; + } + + bool ShouldCollideLocked(const Body& inBody) const override + { + return !inBody.IsSensor(); + } + + private: + BodyID m_IgnoredBody; + }; + namespace Layers { static constexpr ObjectLayer NON_MOVING = 0; @@ -447,6 +471,10 @@ public: settings.mIsSensor = desc.shape.isTrigger; settings.mAllowSleeping = desc.allowSleep; settings.mGravityFactor = desc.useGravity ? 1.0f : 0.0f; + if (desc.freezeRotation) + { + settings.mAllowedDOFs = EAllowedDOFs::TranslationX | EAllowedDOFs::TranslationY | EAllowedDOFs::TranslationZ; + } settings.mUserData = reinterpret_cast(desc.owner); if (desc.type == RigidbodyType::Dynamic) @@ -631,7 +659,8 @@ public: bool Raycast(const glm::vec3& origin, const glm::vec3& direction, float maxDistance, - PhysicsRaycastHit& hit) const + PhysicsRaycastHit& hit, + PhysicsBodyHandle ignoreBody) const { if (maxDistance <= 0.0f || glm::length2(direction) <= 0.0000001f) { @@ -645,8 +674,12 @@ public: ToJoltVec3(directionNormalized * maxDistance) }; + const BodyRecord* ignored = ignoreBody.IsValid() ? FindBody(ignoreBody) : nullptr; + const RaycastBodyFilter bodyFilter(ignored != nullptr ? ignored->bodyID : BodyID{}); + RayCastResult result{}; - if (!m_PhysicsSystem.GetNarrowPhaseQuery().CastRay(ray, result)) + if (!m_PhysicsSystem.GetNarrowPhaseQuery().CastRay( + ray, result, BroadPhaseLayerFilter{}, ObjectLayerFilter{}, bodyFilter)) { return false; } @@ -838,9 +871,10 @@ void JoltPhysicsWorld::AddImpulse(PhysicsBodyHandle body, const glm::vec3& impul bool JoltPhysicsWorld::Raycast(const glm::vec3& origin, const glm::vec3& direction, float maxDistance, - PhysicsRaycastHit& hit) const + PhysicsRaycastHit& hit, + PhysicsBodyHandle ignoreBody) const { - return m_Impl->Raycast(origin, direction, maxDistance, hit); + return m_Impl->Raycast(origin, direction, maxDistance, hit, ignoreBody); } std::vector JoltPhysicsWorld::ConsumeTriggerEvents() diff --git a/destrum/src/Physics/PhysicsWorld.cpp b/destrum/src/Physics/PhysicsWorld.cpp index 5b25f2e..fa78174 100644 --- a/destrum/src/Physics/PhysicsWorld.cpp +++ b/destrum/src/Physics/PhysicsWorld.cpp @@ -70,6 +70,7 @@ void PhysicsWorld::RegisterRigidbody(Rigidbody& rigidbody) { desc.mass = rigidbody.GetMass(); desc.useGravity = rigidbody.UsesGravity(); desc.allowSleep = rigidbody.AllowsSleep(); + desc.freezeRotation = rigidbody.FreezesRotation(); desc.material.friction = rigidbody.GetFriction(); desc.material.restitution = rigidbody.GetRestitution(); diff --git a/destrum/src/Physics/SimplePhysicsWorld.cpp b/destrum/src/Physics/SimplePhysicsWorld.cpp index be408c6..f6cfd74 100644 --- a/destrum/src/Physics/SimplePhysicsWorld.cpp +++ b/destrum/src/Physics/SimplePhysicsWorld.cpp @@ -165,7 +165,8 @@ void SimplePhysicsWorld::SyncDynamicBodiesToTransforms() { bool SimplePhysicsWorld::Raycast(const glm::vec3& origin, const glm::vec3& direction, float maxDistance, - PhysicsRaycastHit& hit) const { + PhysicsRaycastHit& hit, + PhysicsBodyHandle ignoreBody) const { if (maxDistance <= 0.0f) { return false; } @@ -183,7 +184,9 @@ bool SimplePhysicsWorld::Raycast(const glm::vec3& origin, for (const BodyRecord& body : m_Bodies) { if (!body.alive || body.desc.shape.type == PhysicsShapeType::None || !body.desc.owner || body.desc.owner->IsBeingDestroyed() || - !body.desc.owner->IsActiveInHierarchy()) { + !body.desc.owner->IsActiveInHierarchy() || + body.desc.shape.isTrigger || + (ignoreBody.IsValid() && body.handle.id == ignoreBody.id)) { continue; } diff --git a/destrum/src/Scene/Scene.cpp b/destrum/src/Scene/Scene.cpp index 898b9d6..cbdd0de 100644 --- a/destrum/src/Scene/Scene.cpp +++ b/destrum/src/Scene/Scene.cpp @@ -4,6 +4,10 @@ #include #include +#include + +#include +#include unsigned int Scene::m_idCounter = 0; @@ -131,6 +135,32 @@ void Scene::Update(float dt) { } } + m_Environment.skyboxRotation = std::fmod( + m_Environment.skyboxRotation + m_Environment.skyboxRotationSpeed * dt, + glm::two_pi()); +} + +void Scene::DrawSettingsImGui() { + SceneEnvironment& environment = m_Environment; + + ImGui::SeparatorText("Ambient"); + ImGui::ColorEdit3("Ambient color", &environment.ambientColor.x); + ImGui::DragFloat("Ambient intensity", &environment.ambientIntensity, 0.01f, 0.0f, 100.0f); + + ImGui::SeparatorText("Fog"); + ImGui::ColorEdit3("Fog color", &environment.fogColor.x, ImGuiColorEditFlags_HDR | ImGuiColorEditFlags_Float); + ImGui::DragFloat("Fog density", &environment.fogDensity, 0.001f, 0.0f, 1.0f, "%.4f"); + + ImGui::SeparatorText("Skybox"); + float speedDegrees = glm::degrees(environment.skyboxRotationSpeed); + if (ImGui::DragFloat("Spin (deg/s)", &speedDegrees, 0.05f)) { + environment.skyboxRotationSpeed = glm::radians(speedDegrees); + } + + float rotationDegrees = glm::degrees(environment.skyboxRotation); + if (ImGui::SliderFloat("Rotation (deg)", &rotationDegrees, -360.0f, 360.0f)) { + environment.skyboxRotation = glm::radians(rotationDegrees); + } } void Scene::FixedUpdate(float dt) { @@ -200,6 +230,7 @@ void Scene::RenderImgui(bool* open) { m_pendingAdditions, m_SelectedObjectId, [this] { return CreateGameObject("GameObject"); }, + [this] { DrawSettingsImGui(); }, open); } diff --git a/destrum/src/Serialization/SceneSerializer.cpp b/destrum/src/Serialization/SceneSerializer.cpp index e883ed1..92d3ac1 100644 --- a/destrum/src/Serialization/SceneSerializer.cpp +++ b/destrum/src/Serialization/SceneSerializer.cpp @@ -300,6 +300,15 @@ bool SceneSerializer::Save(Scene& scene, const std::filesystem::path& path) { if (scene.m_CurrentCameraId != InvalidObjectId) { root["currentCamera"] = scene.m_CurrentCameraId; } + const SceneEnvironment& environment = scene.GetEnvironment(); + root["environment"] = { + {"ambientColor", {environment.ambientColor.x, environment.ambientColor.y, environment.ambientColor.z}}, + {"ambientIntensity", environment.ambientIntensity}, + {"fogColor", {environment.fogColor.x, environment.fogColor.y, environment.fogColor.z}}, + {"fogDensity", environment.fogDensity}, + {"skyboxRotationSpeed", environment.skyboxRotationSpeed}, + {"skyboxRotation", environment.skyboxRotation}, + }; for (const auto& objectPtr : scene.GetObjects()) { if (!objectPtr || objectPtr->IsBeingDestroyed()) { @@ -573,6 +582,23 @@ bool SceneSerializer::ConstructScene(Scene& scene, LoadResult& result, scene.m_name = root.at("name").get(); } scene.m_CurrentCameraId = root.value("currentCamera", InvalidObjectId); + scene.m_Environment = SceneEnvironment{}; + if (root.contains("environment") && root.at("environment").is_object()) { + const auto& data = root.at("environment"); + SceneEnvironment& environment = scene.m_Environment; + const auto readColor = [&data](const char* key, glm::vec3& color) { + if (data.contains(key) && data.at(key).is_array() && data.at(key).size() == 3) { + const auto& value = data.at(key); + color = {value.at(0).get(), value.at(1).get(), value.at(2).get()}; + } + }; + readColor("ambientColor", environment.ambientColor); + environment.ambientIntensity = data.value("ambientIntensity", environment.ambientIntensity); + readColor("fogColor", environment.fogColor); + environment.fogDensity = data.value("fogDensity", environment.fogDensity); + environment.skyboxRotationSpeed = data.value("skyboxRotationSpeed", environment.skyboxRotationSpeed); + environment.skyboxRotation = data.value("skyboxRotation", environment.skyboxRotation); + } if (progressCallback) progressCallback(0.9f); diff --git a/destrum/src/Util/ImGuiUtils.cpp b/destrum/src/Util/ImGuiUtils.cpp index d7789a6..4f49dbc 100644 --- a/destrum/src/Util/ImGuiUtils.cpp +++ b/destrum/src/Util/ImGuiUtils.cpp @@ -125,6 +125,7 @@ namespace ImGuiUtils { const std::vector>& pendingAdditions, ObjectId& selectedObjectId, const std::function& createGameObject, + const std::function& drawSceneSettings, bool* open) { RegisterEngineComponents(); @@ -176,6 +177,9 @@ namespace ImGuiUtils { } } } + if (drawSceneSettings && ImGui::CollapsingHeader("Scene Settings")) { + drawSceneSettings(); + } ImGui::Separator(); const float availableWidth = ImGui::GetContentRegionAvail().x; diff --git a/lightkeeper/include/Lightkeeper.h b/lightkeeper/include/Lightkeeper.h index 54ce369..f0653a9 100644 --- a/lightkeeper/include/Lightkeeper.h +++ b/lightkeeper/include/Lightkeeper.h @@ -32,6 +32,7 @@ private: void createEmptyScene(); void submitBoxColliderDebugLines(); [[nodiscard]] CameraComponent* getSceneCamera() const; + [[nodiscard]] bool isPlayerViewActive() const; Camera camera{glm::vec3(0.f, 0.f, -5.f), glm::vec3(0, 1, 0)}; diff --git a/lightkeeper/include/components/FirstPersonController.h b/lightkeeper/include/components/FirstPersonController.h index 044eb25..d2c0c67 100644 --- a/lightkeeper/include/components/FirstPersonController.h +++ b/lightkeeper/include/components/FirstPersonController.h @@ -8,6 +8,8 @@ class Rigidbody; class CapsuleCollider; +// Dynamic capsule character. Velocity is driven directly (no forces) so movement stays responsive: +// acceleration towards the wished velocity, own gravity, coyote time and jump buffering. class FirstPersonController final : public Component { public: explicit FirstPersonController(GameObject& parent); @@ -22,27 +24,53 @@ public: void Deserialize(const nlohmann::json& data) override; [[nodiscard]] float GetYaw() const { return m_Yaw; } + void SetYaw(float yawRadians) { m_Yaw = yawRadians; } [[nodiscard]] float GetPitch() const { return m_Pitch; } [[nodiscard]] float GetEyeHeight() const { return m_EyeHeight; } - void SetMovementSpeed(float speed) { m_MovementSpeed = speed; } - [[nodiscard]] float GetMovementSpeed() const { return m_MovementSpeed; } + // Eye position interpolated between the last two physics steps, so the camera moves smoothly at any framerate + [[nodiscard]] glm::vec3 GetEyePosition() const; + + void SetMovementSpeed(float speed) { m_WalkSpeed = speed; } + [[nodiscard]] float GetMovementSpeed() const { return m_WalkSpeed; } void SetMouseSensitivity(float sens) { m_MouseSensitivity = sens; } [[nodiscard]] float GetMouseSensitivity() const { return m_MouseSensitivity; } + [[nodiscard]] bool IsGrounded() const { return m_Grounded; } + [[nodiscard]] bool IsMouseCaptured() const { return InputManager::GetInstance().IsMouseCaptured(); } void SetMouseCaptured(bool captured); private: + [[nodiscard]] bool CheckGrounded() const; + [[nodiscard]] glm::vec3 GetWishDirection() const; + Rigidbody* m_Rigidbody{nullptr}; CapsuleCollider* m_Capsule{nullptr}; float m_Yaw{0.0f}; float m_Pitch{0.0f}; - float m_MovementSpeed{5.0f}; float m_MouseSensitivity{0.002f}; - float m_EyeHeight{1.6f}; + float m_EyeHeight{0.7f}; // above the capsule center + + float m_WalkSpeed{5.0f}; + float m_SprintSpeed{8.5f}; + float m_GroundAcceleration{60.0f}; + float m_AirAcceleration{15.0f}; + float m_JumpHeight{1.3f}; + float m_Gravity{22.0f}; + float m_FallGravityMultiplier{1.6f}; + float m_CoyoteTime{0.1f}; + float m_JumpBufferTime{0.12f}; + + bool m_Grounded{false}; + bool m_JumpHeld{false}; + float m_CoyoteTimer{0.0f}; + float m_JumpBufferTimer{0.0f}; + + glm::vec3 m_PreviousPosition{0.0f}; + bool m_HasPreviousPosition{false}; }; -#endif \ No newline at end of file +#endif diff --git a/lightkeeper/src/Lightkeeper.cpp b/lightkeeper/src/Lightkeeper.cpp index 21446b9..e0d46bd 100644 --- a/lightkeeper/src/Lightkeeper.cpp +++ b/lightkeeper/src/Lightkeeper.cpp @@ -7,10 +7,12 @@ #include #include #include "glm/gtc/constants.hpp" +#include "glm/gtc/quaternion.hpp" #include "glm/gtx/transform.hpp" #include "spdlog/spdlog.h" #include #include +#include #include #include @@ -53,6 +55,55 @@ namespace { const std::filesystem::path path{value}; return path.is_absolute() ? path : exeDir / path; } + + // Box centered on the origin. UVs are in meters, so tiling textures keep the same size on every box + CPUMesh MakeBoxMesh(const glm::vec3& size) + { + struct Face { + glm::vec3 normal; + glm::vec3 tangent; + }; + constexpr std::array faces{{ + {{1.0f, 0.0f, 0.0f}, {0.0f, 0.0f, -1.0f}}, + {{-1.0f, 0.0f, 0.0f}, {0.0f, 0.0f, 1.0f}}, + {{0.0f, 1.0f, 0.0f}, {1.0f, 0.0f, 0.0f}}, + {{0.0f, -1.0f, 0.0f}, {1.0f, 0.0f, 0.0f}}, + {{0.0f, 0.0f, 1.0f}, {1.0f, 0.0f, 0.0f}}, + {{0.0f, 0.0f, -1.0f}, {-1.0f, 0.0f, 0.0f}}, + }}; + constexpr std::array corners{{ + {-1.0f, -1.0f}, {1.0f, -1.0f}, {1.0f, 1.0f}, {-1.0f, 1.0f}, + }}; + + CPUMesh mesh{}; + mesh.name = "Box"; + mesh.minPos = -size * 0.5f; + mesh.maxPos = size * 0.5f; + + for (const Face& face : faces) { + const glm::vec3 bitangent = glm::cross(face.normal, face.tangent); + const float width = glm::dot(glm::abs(face.tangent), size); + const float height = glm::dot(glm::abs(bitangent), size); + const auto first = static_cast(mesh.vertices.size()); + + for (const glm::vec2& corner : corners) { + // v runs against the bitangent so images are upright on the side faces (w = -1 to match) + mesh.vertices.push_back(CPUMesh::Vertex{ + .position = (face.normal + face.tangent * corner.x + bitangent * corner.y) * size * 0.5f, + .uv_x = (corner.x + 1.0f) * 0.5f * width, + .normal = face.normal, + .uv_y = (1.0f - corner.y) * 0.5f * height, + .tangent = glm::vec4{face.tangent, -1.0f}, + }); + } + + for (const std::uint32_t index : {0u, 1u, 2u, 0u, 2u, 3u}) { + mesh.indices.push_back(first + index); + } + } + + return mesh; + } } LightKeeper::LightKeeper() : App() @@ -88,12 +139,25 @@ void LightKeeper::customInit() camera.SetRotation(glm::radians(glm::vec2(90.f, 0.f))); auto& scene = SceneManager::GetInstance().CreateScene("Main"); + scene.GetEnvironment().fogDensity = 0.01f; + + // Directional lights shine down their local -Z: tilt it 50 degrees down, then turn it around the up axis + const auto sunObj = scene.CreateGameObject("Sun"); + sunObj->GetTransform().SetWorldPosition(glm::vec3(0.f, 10.f, 0.f)); + sunObj->GetTransform().SetWorldRotation( + glm::angleAxis(glm::radians(35.f), glm::vec3{0.f, 1.f, 0.f}) * + glm::angleAxis(glm::radians(-50.f), glm::vec3{1.f, 0.f, 0.f})); + auto* sun = sunObj->AddComponent(LightType::Directional); + sun->SetColor(glm::vec3{1.0f, 0.95f, 0.85f}); + sun->SetIntensity(3.0f); + sun->SetCastShadows(true); // scene.Add(testCube); // const auto skyboxID = AssetFS::GetInstance().GetFullPath("engine://textures/skybox.jpg"); // const auto skyboxID = AssetFS::GetInstance().GetFullPath("engine://textures/mars.jpg"); - const auto skyboxID = AssetFS::GetInstance().GetCookedPathForFile("game://starmap_2020_4k.exr"); + // const auto skyboxID = AssetFS::GetInstance().GetCookedPathForFile("game://starmap_2020_4k.exr"); + const auto skyboxID = AssetFS::GetInstance().GetCookedPathForFile("engine://DJI_0001_stitch.jpg"); // // const auto skyboxID = AssetFS::GetInstance().GetFullPath("engine://textures/test-skybox.png"); // @@ -136,11 +200,71 @@ void LightKeeper::customInit() planeMeshComp->SetMaterialID(planeMaterialID); planeObj->GetTransform().SetWorldPosition(glm::vec3(0.f, -1.0f, 0.f)); planeObj->GetTransform().SetWorldScale(glm::vec3(10.f, 1.f, 10.f)); - planeObj->AddComponent(glm::vec3{10.0f, 0.5f, 10.0f}); - auto* floorRb = planeObj->AddComponent(); - floorRb->SetType(RigidbodyType::Static); - scene.GetPhysics().RegisterGameObject(*planeObj); + // Kenney prototype textures, texture_01 is a 1 x 1 meter grid + const auto addPrototypeMaterial = [&](const std::string& color, float roughness) + { + Material material = Material::SimpleColor(glm::vec3{1.0f}, "Prototype" + color); + material.roughnessFactor = roughness; + material.textureFilteringMode = TextureFilteringMode::Anisotropic; + material.diffuseTexture = resources.loadImageFromFile( + gfxDevice, AssetFS::GetInstance().GetFullPath("engine://kenny/" + color + "/texture_01.png")); + return resources.materials().addMaterial(std::move(material)); + }; + const auto createBox = [&](const std::string& name, const glm::vec3& position, const glm::vec3& size, + MaterialID materialID, RigidbodyType type) + { + const auto box = scene.CreateGameObject(name); + box->GetTransform().SetWorldPosition(position); + + const auto boxMeshComp = box->AddComponent(); + boxMeshComp->SetMeshID(resources.meshes().addMesh(gfxDevice, MakeBoxMesh(size))); + boxMeshComp->SetMaterialID(materialID); + + box->AddComponent(size * 0.5f); + auto* boxRb = box->AddComponent(); + boxRb->SetType(type); + boxRb->SetMass(10.0f); + scene.GetPhysics().RegisterGameObject(*box); + return box; + }; + + // The plane mesh is flat at y = 0, so the floor collider sits right below it + const auto groundObj = scene.CreateGameObject("GroundCollider"); + groundObj->GetTransform().SetWorldPosition(glm::vec3(0.f, -1.5f, 0.f)); + groundObj->AddComponent(glm::vec3{10.0f, 0.5f, 10.0f}); + groundObj->AddComponent()->SetType(RigidbodyType::Static); + scene.GetPhysics().RegisterGameObject(*groundObj); + + // Parkour course, every platform is reachable with a jump from the previous one + const MaterialID platformMaterial = addPrototypeMaterial("Orange", 0.6f); + const MaterialID goalMaterial = addPrototypeMaterial("Green", 0.6f); + const MaterialID wallMaterial = addPrototypeMaterial("Dark", 0.9f); + const MaterialID crateMaterial = addPrototypeMaterial("Purple", 0.35f); + + const auto createPlatform = [&](const std::string& name, const glm::vec3& center, float top, const glm::vec3& size, + MaterialID materialID) + { + const float height = top - (-1.0f); + createBox(name, glm::vec3{center.x, top - height * 0.5f, center.z}, glm::vec3{size.x, height, size.z}, + materialID, RigidbodyType::Static); + }; + createPlatform("Parkour_Step", {0.0f, 0.0f, 4.0f}, -0.5f, {2.0f, 0.0f, 2.0f}, platformMaterial); + createPlatform("Parkour_Block", {0.0f, 0.0f, 7.0f}, 0.4f, {2.0f, 0.0f, 2.0f}, platformMaterial); + createPlatform("Parkour_Pillar1", {3.5f, 0.0f, 8.5f}, 1.2f, {1.5f, 0.0f, 1.5f}, platformMaterial); + createPlatform("Parkour_Pillar2", {6.5f, 0.0f, 7.5f}, 2.0f, {1.2f, 0.0f, 1.2f}, platformMaterial); + createBox("Parkour_Beam", {6.5f, 1.85f, 4.0f}, {0.6f, 0.3f, 5.0f}, platformMaterial, RigidbodyType::Static); + createPlatform("Parkour_Goal", {6.5f, 0.0f, -0.5f}, 2.8f, {2.5f, 0.0f, 2.5f}, goalMaterial); + + createBox("Wall", {-5.0f, 0.5f, 0.0f}, {0.5f, 3.0f, 8.0f}, wallMaterial, RigidbodyType::Static); + createBox("LowWall", {-2.5f, -0.6f, -4.0f}, {4.0f, 0.8f, 0.4f}, wallMaterial, RigidbodyType::Static); + + for (int i = 0; i < 4; ++i) + { + createBox("Crate" + std::to_string(i), + {-2.5f, -0.6f + 0.81f * static_cast(i), 2.0f}, + glm::vec3{0.8f}, crateMaterial, RigidbodyType::Dynamic); + } const auto helmetObj = scene.CreateGameObject("FlightHelmet"); helmetObj->GetTransform().SetWorldPosition(glm::vec3(2.f, -1.f, 0.f)); @@ -521,12 +645,15 @@ void LightKeeper::customInit() // orbit->SetPivotPosition(glm::vec3{0.0f, -0.25f, 0.0f}); // } - // auto playerObj = scene.CreateGameObject("Player"); - // playerObj->GetTransform().SetWorldPosition(glm::vec3(0.0f, 2.0f, -5.0f)); - // auto* fps = playerObj->AddComponent(); - // m_Player = playerObj; - // m_FPSController = fps; - // scene.GetPhysics().RegisterGameObject(*playerObj); + auto playerObj = scene.CreateGameObject("Player"); + playerObj->GetTransform().SetWorldPosition(glm::vec3(0.0f, 0.0f, -6.0f)); + playerObj->AddComponent(0.4f, 1.8f); + playerObj->AddComponent(); + auto* fps = playerObj->AddComponent(); + fps->SetYaw(glm::half_pi()); + m_Player = playerObj; + m_FPSController = fps; + scene.GetPhysics().RegisterGameObject(*playerObj); const auto texPath = AssetFS::GetInstance().GetFullPath("engine://textures/kobe.png"); texID = resources.loadImageFromFile(gfxDevice, texPath); @@ -540,16 +667,7 @@ void LightKeeper::customUpdate(float dt) { // LightKeeper owns a camera that hides App::camera; update this instance // after SDL events have been consumed. - if (m_FPSController != nullptr && m_FPSController->IsMouseCaptured()) - { - auto& playerTransform = m_Player->GetTransform(); - const glm::vec3 playerPos = playerTransform.GetWorldPosition(); - camera.SetRotation(m_FPSController->GetYaw(), m_FPSController->GetPitch()); - camera.m_position = playerPos + glm::vec3(0.0f, m_FPSController->GetEyeHeight(), 0.0f); - camera.CalculateViewMatrix(); - camera.CalculateProjectionMatrix(); - } - else if (getSceneCamera() == nullptr) + if (!isPlayerViewActive() && getSceneCamera() == nullptr) { camera.Update(dt); } @@ -591,6 +709,11 @@ void LightKeeper::customUpdate(float dt) } +bool LightKeeper::isPlayerViewActive() const +{ + return m_FPSController != nullptr && m_FPSController->IsMouseCaptured(); +} + CameraComponent* LightKeeper::getSceneCamera() const { const auto& activeScenes = SceneManager::GetInstance().GetActiveScenes(); @@ -809,19 +932,32 @@ void LightKeeper::customDraw() glm::vec2{100.0f, 100.0f}, glm::vec2{200.0f, 200.0f}, 0.0f, glm::vec4{1.0f}, texID); - CameraComponent* sceneCamera = getSceneCamera(); + // The player camera is placed here, after the fixed steps, so it can be interpolated between them + if (isPlayerViewActive()) + { + camera.SetRotation(m_FPSController->GetYaw(), m_FPSController->GetPitch()); + camera.m_position = m_FPSController->GetEyePosition(); + camera.CalculateViewMatrix(); + camera.CalculateProjectionMatrix(); + } + + CameraComponent* sceneCamera = isPlayerViewActive() ? nullptr : getSceneCamera(); Camera& viewCamera = sceneCamera != nullptr ? sceneCamera->GetCamera(camera.GetAspectRatio()) : camera; + const SceneEnvironment& environment = SceneManager::GetInstance().GetCurrentScene().GetEnvironment(); + const GameRenderer::SceneData sceneData{ + .camera = viewCamera, + .ambientColor = environment.ambientColor, + .ambientIntensity = environment.ambientIntensity, + .fogColor = environment.fogColor, + .fogDensity = environment.fogDensity, + .skyboxRotation = environment.skyboxRotation, + }; + const RenderContext ctx{ .renderer = renderer, .camera = viewCamera, - .sceneData = { - .camera = viewCamera, - .ambientColor = glm::vec3(0.1f), - .ambientIntensity = 0.5f, - .fogColor = glm::vec3(0.5f), - .fogDensity = 0.01f - } + .sceneData = sceneData, }; SceneManager::GetInstance().Render(ctx); @@ -829,10 +965,7 @@ void LightKeeper::customDraw() const auto& drawImage = renderer.getDrawImage(); - renderer.draw( - cmd, gfxDevice, viewCamera, GameRenderer::SceneData{ - viewCamera, glm::vec3(0.1f), 0.5f, glm::vec3(0.5f), 0.01f - }); + renderer.draw(cmd, gfxDevice, viewCamera, sceneData); gfxDevice.endFrame( cmd, drawImage, { diff --git a/lightkeeper/src/components/FirstPersonController.cpp b/lightkeeper/src/components/FirstPersonController.cpp index df1cb89..79b6b60 100644 --- a/lightkeeper/src/components/FirstPersonController.cpp +++ b/lightkeeper/src/components/FirstPersonController.cpp @@ -1,18 +1,33 @@ #include #include -#include #include #include #include #include #include +#include #include -#include +#include + +#include +#include #include +namespace { + glm::vec3 MoveTowards(const glm::vec3& current, const glm::vec3& target, float maxDelta) + { + const glm::vec3 delta = target - current; + const float distance = glm::length(delta); + if (distance <= maxDelta || distance < 1e-6f) { + return target; + } + return current + delta / distance * maxDelta; + } +} + FirstPersonController::FirstPersonController(GameObject& parent) : Component(parent, "FirstPersonController") { @@ -26,14 +41,26 @@ void FirstPersonController::SetMouseCaptured(bool captured) void FirstPersonController::Start() { auto* obj = GetGameObject(); - m_Capsule = obj->AddComponent(0.5f, 2.0f); - m_Rigidbody = obj->AddComponent(); + + m_Capsule = obj->GetComponent(); + if (m_Capsule == nullptr) { + m_Capsule = obj->AddComponent(0.4f, 1.8f); + } + + m_Rigidbody = obj->GetComponent(); + if (m_Rigidbody == nullptr) { + m_Rigidbody = obj->AddComponent(); + } + + // The controller owns gravity and friction, the physics body only resolves collisions m_Rigidbody->SetMass(80.0f); + m_Rigidbody->SetFriction(0.0f); m_Rigidbody->SetAllowSleep(false); - m_Rigidbody->SetUseGravity(true); + m_Rigidbody->SetUseGravity(false); + m_Rigidbody->SetFreezeRotation(true); } -void FirstPersonController::Update(float) +void FirstPersonController::Update(float dt) { const auto& input = InputManager::GetInstance(); @@ -44,6 +71,7 @@ void FirstPersonController::Update(float) if (!IsMouseCaptured()) { + m_JumpHeld = false; if (input.WasMousePressed(SDL_BUTTON_LEFT) && !ImGui::GetIO().WantCaptureMouse) { SetMouseCaptured(true); @@ -57,59 +85,144 @@ void FirstPersonController::Update(float) return; } - if (input.WasKeyPressed(SDL_SCANCODE_SPACE) && m_Rigidbody->HasPhysicsBody()) + // Jump presses are remembered for a moment, so pressing just before landing still jumps + m_JumpBufferTimer = std::max(m_JumpBufferTimer - dt, 0.0f); + if (input.WasKeyPressed(SDL_SCANCODE_SPACE)) { - m_Rigidbody->AddImpulse(glm::vec3(0.0f, 400.0f, 0.0f)); + m_JumpBufferTimer = m_JumpBufferTime; } + m_JumpHeld = input.IsKeyDown(SDL_SCANCODE_SPACE); m_Yaw += static_cast(input.MouseDeltaX()) * m_MouseSensitivity; m_Pitch -= static_cast(input.MouseDeltaY()) * m_MouseSensitivity; m_Pitch = glm::clamp(m_Pitch, glm::radians(-89.0f), glm::radians(89.0f)); } -void FirstPersonController::FixedUpdate(float) +void FirstPersonController::FixedUpdate(float fixedDt) { - if (!IsMouseCaptured() || m_Rigidbody == nullptr || !m_Rigidbody->HasPhysicsBody()) + if (m_Rigidbody == nullptr || !m_Rigidbody->HasPhysicsBody()) return; + m_PreviousPosition = GetTransform().GetWorldPosition(); + m_HasPreviousPosition = true; + + glm::vec3 velocity = m_Rigidbody->GetLinearVelocity(); + + m_Grounded = velocity.y <= 0.1f && CheckGrounded(); + m_CoyoteTimer = m_Grounded ? m_CoyoteTime : std::max(m_CoyoteTimer - fixedDt, 0.0f); + + const auto& input = InputManager::GetInstance(); + const bool sprinting = IsMouseCaptured() && input.IsKeyDown(SDL_SCANCODE_LSHIFT); + const glm::vec3 wishVelocity = GetWishDirection() * (sprinting ? m_SprintSpeed : m_WalkSpeed); + + const float acceleration = m_Grounded ? m_GroundAcceleration : m_AirAcceleration; + const glm::vec3 horizontal = MoveTowards( + glm::vec3{velocity.x, 0.0f, velocity.z}, + wishVelocity, + acceleration * fixedDt); + velocity.x = horizontal.x; + velocity.z = horizontal.z; + + if (m_JumpBufferTimer > 0.0f && m_CoyoteTimer > 0.0f) + { + velocity.y = std::sqrt(2.0f * m_Gravity * m_JumpHeight); + m_JumpBufferTimer = 0.0f; + m_CoyoteTimer = 0.0f; + m_Grounded = false; + } + + // Falling faster than rising, and letting go of jump early cuts the jump short + float gravity = m_Gravity; + if (velocity.y < 0.0f || !m_JumpHeld) + { + gravity *= m_FallGravityMultiplier; + } + velocity.y -= gravity * fixedDt; + + m_Rigidbody->SetLinearVelocity(velocity); +} + +bool FirstPersonController::CheckGrounded() const +{ + const auto* world = m_Rigidbody->GetPhysicsWorld(); + if (world == nullptr || m_Capsule == nullptr) + return false; + + const glm::vec3 center = GetTransform().GetWorldPosition() + m_Capsule->GetCenterOffset(); + const float radius = m_Capsule->GetRadius(); + const float bottom = center.y - m_Capsule->GetHeight() * 0.5f; + + // Rays start inside the capsule (ignoring it) so they are always above the floor, even when the solver + // leaves the capsule sunk in a little. The outer rays catch standing on an edge. + const float spread = radius * 0.7f; + const std::array offsets{ + glm::vec2{0.0f, 0.0f}, + glm::vec2{spread, 0.0f}, + glm::vec2{-spread, 0.0f}, + glm::vec2{0.0f, spread}, + glm::vec2{0.0f, -spread}, + }; + + constexpr float startHeight = 0.2f; + constexpr float probeDistance = 0.1f; + for (const glm::vec2& offset : offsets) + { + const glm::vec3 origin{center.x + offset.x, bottom + startHeight, center.z + offset.y}; + + PhysicsRaycastHit hit{}; + if (world->Raycast(origin, glm::vec3{0.0f, -1.0f, 0.0f}, startHeight + probeDistance, hit, + m_Rigidbody->GetBody()) && + hit.normal.y > 0.6f) + { + return true; + } + } + return false; +} + +glm::vec3 FirstPersonController::GetWishDirection() const +{ + if (!IsMouseCaptured()) + return glm::vec3{0.0f}; + const auto& input = InputManager::GetInstance(); - const float cosY = glm::cos(m_Yaw); - const float sinY = glm::sin(m_Yaw); - const glm::vec3 forward{cosY, 0.0f, sinY}; - const glm::vec3 right{-sinY, 0.0f, cosY}; + const glm::vec3 forward{std::cos(m_Yaw), 0.0f, std::sin(m_Yaw)}; + const glm::vec3 right{-std::sin(m_Yaw), 0.0f, std::cos(m_Yaw)}; - glm::vec3 moveDir(0.0f); - if (input.IsKeyDown(SDL_SCANCODE_W)) moveDir += forward; - if (input.IsKeyDown(SDL_SCANCODE_S)) moveDir -= forward; - if (input.IsKeyDown(SDL_SCANCODE_D)) moveDir += right; - if (input.IsKeyDown(SDL_SCANCODE_A)) moveDir -= right; + glm::vec3 direction(0.0f); + if (input.IsKeyDown(SDL_SCANCODE_W)) direction += forward; + if (input.IsKeyDown(SDL_SCANCODE_S)) direction -= forward; + if (input.IsKeyDown(SDL_SCANCODE_D)) direction += right; + if (input.IsKeyDown(SDL_SCANCODE_A)) direction -= right; - glm::vec3 velocity(0.0f); - if (glm::length2(moveDir) > 0.0f) - { - moveDir = glm::normalize(moveDir); - velocity = moveDir * m_MovementSpeed; - } + return glm::length2(direction) > 0.0f ? glm::normalize(direction) : direction; +} - velocity.y = m_Rigidbody->GetLinearVelocity().y; - m_Rigidbody->SetLinearVelocity(velocity); - - auto* world = m_Rigidbody->GetPhysicsWorld(); - if (world != nullptr) - { - PhysicsTransform xform = world->GetBodyTransform(m_Rigidbody->GetBody()); - xform.rotation = glm::quat(glm::vec3(0.0f, m_Yaw, 0.0f)); - world->SetBodyTransform(m_Rigidbody->GetBody(), xform); - } +glm::vec3 FirstPersonController::GetEyePosition() const +{ + const glm::vec3 current = GetTransform().GetWorldPosition(); + const glm::vec3 position = m_HasPreviousPosition + ? glm::mix(m_PreviousPosition, current, Time::GetInstance().FixedAlpha()) + : current; + return position + glm::vec3{0.0f, m_EyeHeight, 0.0f}; } void FirstPersonController::ImGuiInspector() { Component::ImGuiInspector(); - ImGui::DragFloat("Movement Speed", &m_MovementSpeed, 0.1f, 0.0f, 100.0f); + ImGui::DragFloat("Walk Speed", &m_WalkSpeed, 0.1f, 0.0f, 100.0f); + ImGui::DragFloat("Sprint Speed", &m_SprintSpeed, 0.1f, 0.0f, 100.0f); + ImGui::DragFloat("Ground Acceleration", &m_GroundAcceleration, 0.5f, 0.0f, 500.0f); + ImGui::DragFloat("Air Acceleration", &m_AirAcceleration, 0.5f, 0.0f, 500.0f); + ImGui::DragFloat("Jump Height", &m_JumpHeight, 0.01f, 0.0f, 10.0f); + ImGui::DragFloat("Gravity", &m_Gravity, 0.1f, 0.0f, 100.0f); + ImGui::DragFloat("Fall Gravity Multiplier", &m_FallGravityMultiplier, 0.01f, 1.0f, 5.0f); + ImGui::DragFloat("Coyote Time", &m_CoyoteTime, 0.005f, 0.0f, 1.0f); + ImGui::DragFloat("Jump Buffer Time", &m_JumpBufferTime, 0.005f, 0.0f, 1.0f); ImGui::DragFloat("Mouse Sensitivity", &m_MouseSensitivity, 0.0001f, 0.0f, 0.1f); - ImGui::DragFloat("Eye Height", &m_EyeHeight, 0.01f, 0.0f, 5.0f); + ImGui::DragFloat("Eye Height", &m_EyeHeight, 0.01f, -2.0f, 2.0f); + ImGui::Text("Grounded: %s", m_Grounded ? "yes" : "no"); ImGui::Text("Yaw: %.1f deg", glm::degrees(m_Yaw)); ImGui::Text("Pitch: %.1f deg", glm::degrees(m_Pitch)); bool captured = IsMouseCaptured(); @@ -122,7 +235,15 @@ void FirstPersonController::ImGuiInspector() nlohmann::json FirstPersonController::Serialize() const { return { - {"movementSpeed", m_MovementSpeed}, + {"walkSpeed", m_WalkSpeed}, + {"sprintSpeed", m_SprintSpeed}, + {"groundAcceleration", m_GroundAcceleration}, + {"airAcceleration", m_AirAcceleration}, + {"jumpHeight", m_JumpHeight}, + {"gravity", m_Gravity}, + {"fallGravityMultiplier", m_FallGravityMultiplier}, + {"coyoteTime", m_CoyoteTime}, + {"jumpBufferTime", m_JumpBufferTime}, {"mouseSensitivity", m_MouseSensitivity}, {"eyeHeight", m_EyeHeight}, {"yaw", m_Yaw}, @@ -132,9 +253,17 @@ nlohmann::json FirstPersonController::Serialize() const void FirstPersonController::Deserialize(const nlohmann::json& data) { - if (data.contains("movementSpeed")) m_MovementSpeed = data.at("movementSpeed").get(); - if (data.contains("mouseSensitivity")) m_MouseSensitivity = data.at("mouseSensitivity").get(); - if (data.contains("eyeHeight")) m_EyeHeight = data.at("eyeHeight").get(); - if (data.contains("yaw")) m_Yaw = data.at("yaw").get(); - if (data.contains("pitch")) m_Pitch = data.at("pitch").get(); -} \ No newline at end of file + m_WalkSpeed = data.value("walkSpeed", data.value("movementSpeed", m_WalkSpeed)); + m_SprintSpeed = data.value("sprintSpeed", m_SprintSpeed); + m_GroundAcceleration = data.value("groundAcceleration", m_GroundAcceleration); + m_AirAcceleration = data.value("airAcceleration", m_AirAcceleration); + m_JumpHeight = data.value("jumpHeight", m_JumpHeight); + m_Gravity = data.value("gravity", m_Gravity); + m_FallGravityMultiplier = data.value("fallGravityMultiplier", m_FallGravityMultiplier); + m_CoyoteTime = data.value("coyoteTime", m_CoyoteTime); + m_JumpBufferTime = data.value("jumpBufferTime", m_JumpBufferTime); + m_MouseSensitivity = data.value("mouseSensitivity", m_MouseSensitivity); + m_EyeHeight = data.value("eyeHeight", m_EyeHeight); + m_Yaw = data.value("yaw", m_Yaw); + m_Pitch = data.value("pitch", m_Pitch); +} diff --git a/tests/destrum_tests.cpp b/tests/destrum_tests.cpp index 403c32f..c9169ae 100644 --- a/tests/destrum_tests.cpp +++ b/tests/destrum_tests.cpp @@ -26,6 +26,7 @@ #include #include #include +#include #include #include #include @@ -110,6 +111,16 @@ namespace { child->GetTransform().SetParent(&parent->GetTransform(), false); scene.CommitPendingAdditions(); + SceneEnvironment& environment = scene.GetEnvironment(); + environment.ambientColor = {0.2f, 0.3f, 0.4f}; + environment.ambientIntensity = 2.0f; + environment.fogColor = {0.6f, 0.7f, 0.8f}; + environment.fogDensity = 0.05f; + environment.skyboxRotationSpeed = 0.5f; + environment.skyboxRotation = 1.0f; + scene.Update(1.0f); + check(close(environment.skyboxRotation, 1.5f), "scene updates must spin the skybox"); + const auto path = std::filesystem::temp_directory_path() / "destrum_scene_test.json"; check(SceneSerializer::Save(scene, path), "scene save must succeed"); check(SceneSerializer::Load(scene, path), "scene load must succeed"); @@ -121,6 +132,14 @@ namespace { "scene round-trip must preserve hierarchy"); check(close(loadedChild->GetTransform().GetLocalPosition().x, 1.f), "scene round-trip must preserve local transforms"); + const SceneEnvironment& loadedEnvironment = scene.GetEnvironment(); + check(loadedEnvironment.ambientColor == glm::vec3{0.2f, 0.3f, 0.4f} && + close(loadedEnvironment.ambientIntensity, 2.0f) && + loadedEnvironment.fogColor == glm::vec3{0.6f, 0.7f, 0.8f} && + close(loadedEnvironment.fogDensity, 0.05f) && + close(loadedEnvironment.skyboxRotationSpeed, 0.5f) && + close(loadedEnvironment.skyboxRotation, 1.5f), + "scene round-trip must preserve the environment settings"); SceneManager::GetInstance().Destroy(); } @@ -410,6 +429,59 @@ namespace { SceneManager::GetInstance().Destroy(); } + void testFrozenRotationCapsule() + { + Scene& scene = SceneManager::GetInstance().CreateScene("frozen-capsule"); + + GameObject* floor = scene.CreateGameObject("floor"); + floor->GetTransform().SetLocalPosition({0.0f, -1.0f, 0.0f}); + floor->AddComponent(glm::vec3{10.0f, 0.5f, 10.0f}); + floor->AddComponent()->SetType(RigidbodyType::Static); + + GameObject* character = scene.CreateGameObject("character"); + character->AddComponent(0.4f, 1.8f); + auto* body = character->AddComponent(); + body->SetFriction(1.0f); + body->SetFreezeRotation(true); + character->GetTransform().SetWorldPosition({0.0f, 1.0f, 0.0f}); + + GameObject* trigger = scene.CreateGameObject("trigger"); + trigger->GetTransform().SetWorldPosition({2.0f, 0.0f, 0.0f}); + trigger->AddComponent(glm::vec3{2.0f})->SetTrigger(true); + trigger->AddComponent()->SetType(RigidbodyType::Static); + + scene.CommitPendingAdditions(); + scene.GetPhysics().RefreshGameObject(*floor); + scene.GetPhysics().RefreshGameObject(*character); + scene.GetPhysics().RefreshGameObject(*trigger); + + // Sliding with friction would tip a free capsule over + body->SetLinearVelocity({4.0f, 0.0f, 0.0f}); + for (int step = 0; step < 180; ++step) { + scene.FixedUpdate(1.0f / 60.0f); + } + + const glm::vec3 position = character->GetTransform().GetWorldPosition(); + const glm::quat rotation = character->GetTransform().GetWorldRotation(); + check(std::abs(rotation.w) > 0.999f, "a capsule with frozen rotation must stay upright"); + check(std::abs(position.y - (-0.5f + 0.9f)) < 0.05f, "a capsule must rest on top of the floor"); + + // Cast from inside the capsule (and the trigger it is standing in), like a ground check does + PhysicsRaycastHit hit{}; + const bool hitGround = body->GetPhysicsWorld()->Raycast( + position - glm::vec3{0.0f, 0.7f, 0.0f}, glm::vec3{0.0f, -1.0f, 0.0f}, 0.3f, hit, body->GetBody()); + check(hitGround && hit.object == floor && hit.normal.y > 0.9f, + "a ground ray must skip the caster and triggers and hit the floor"); + + PhysicsRaycastHit selfHit{}; + check(body->GetPhysicsWorld()->Raycast( + position - glm::vec3{0.0f, 0.7f, 0.0f}, glm::vec3{0.0f, -1.0f, 0.0f}, 0.3f, selfHit) && + selfHit.object == character, + "a ray that doesn't ignore the caster must hit it"); + + SceneManager::GetInstance().Destroy(); + } + void testAnimatorAssetReferences() { GameObject object{"animated"}; @@ -974,6 +1046,7 @@ int main() testAssetReferenceCacheKeys(); testTriggerZone(); testSceneCurrentCamera(); + testFrozenRotationCapsule(); std::cout << "destrum tests passed\n"; return EXIT_SUCCESS; }