feat: add moving platform support + fix jolt

This commit is contained in:
2026-10-18 13:34:30 +02:00
parent 65a7310b56
commit 7223e9ce07
15 changed files with 291 additions and 34 deletions
@@ -35,7 +35,7 @@ public:
void Step(float fixedDt) override; void Step(float fixedDt) override;
void SyncKinematicBodiesToPhysics() override; void SyncKinematicBodiesToPhysics(float fixedDt) override;
void SyncDynamicBodiesToTransforms() override; void SyncDynamicBodiesToTransforms() override;
PhysicsBodyHandle CreateBody(const PhysicsBodyDesc& desc) override; PhysicsBodyHandle CreateBody(const PhysicsBodyDesc& desc) override;
@@ -29,9 +29,9 @@ public:
virtual void Step(float fixedDt) = 0; virtual void Step(float fixedDt) = 0;
// - Kinematic: Transform -> physics body before Step() // - Kinematic: moved towards its Transform over the step, so it has a velocity
// - Dynamic: physics body -> Transform after Step() // - Dynamic: physics body -> Transform after Step()
virtual void SyncKinematicBodiesToPhysics(); virtual void SyncKinematicBodiesToPhysics(float fixedDt);
virtual void SyncDynamicBodiesToTransforms(); virtual void SyncDynamicBodiesToTransforms();
virtual PhysicsBodyHandle CreateBody(const PhysicsBodyDesc& desc) = 0; virtual PhysicsBodyHandle CreateBody(const PhysicsBodyDesc& desc) = 0;
@@ -49,7 +49,7 @@ public:
PhysicsRaycastHit& hit, PhysicsRaycastHit& hit,
PhysicsBodyHandle ignoreBody = {}) const override; PhysicsBodyHandle ignoreBody = {}) const override;
void SyncKinematicBodiesToPhysics(); void SyncKinematicBodiesToPhysics(float fixedDt) override;
void SyncDynamicBodiesToTransforms(); void SyncDynamicBodiesToTransforms();
[[nodiscard]] const glm::vec3& GetGravity() const { return m_Gravity; } [[nodiscard]] const glm::vec3& GetGravity() const { return m_Gravity; }
+5 -5
View File
@@ -548,7 +548,7 @@ public:
return events; return events;
} }
void SyncKinematicBodiesToPhysics() void SyncKinematicBodiesToPhysics(float fixedDt)
{ {
BodyInterface& bodyInterface = m_PhysicsSystem.GetBodyInterface(); BodyInterface& bodyInterface = m_PhysicsSystem.GetBodyInterface();
@@ -562,11 +562,11 @@ public:
Transform& transform = record.desc.owner->GetTransform(); Transform& transform = record.desc.owner->GetTransform();
bodyInterface.SetPositionAndRotation( bodyInterface.MoveKinematic(
record.bodyID, record.bodyID,
ToJoltRVec3(transform.GetWorldPosition()), ToJoltRVec3(transform.GetWorldPosition()),
ToJoltQuat(transform.GetWorldRotation()), ToJoltQuat(transform.GetWorldRotation()),
EActivation::Activate); fixedDt);
} }
} }
@@ -819,9 +819,9 @@ void JoltPhysicsWorld::Step(float fixedDt)
m_Impl->Step(fixedDt); m_Impl->Step(fixedDt);
} }
void JoltPhysicsWorld::SyncKinematicBodiesToPhysics() void JoltPhysicsWorld::SyncKinematicBodiesToPhysics(float fixedDt)
{ {
m_Impl->SyncKinematicBodiesToPhysics(); m_Impl->SyncKinematicBodiesToPhysics(fixedDt);
} }
void JoltPhysicsWorld::SyncDynamicBodiesToTransforms() void JoltPhysicsWorld::SyncDynamicBodiesToTransforms()
+1 -1
View File
@@ -35,7 +35,7 @@ void PhysicsSceneBridge::RefreshGameObject(GameObject& object) {
void PhysicsSceneBridge::FixedUpdate(float fixedDt) { void PhysicsSceneBridge::FixedUpdate(float fixedDt) {
if (!m_World) return; if (!m_World) return;
m_World->SyncKinematicBodiesToPhysics(); m_World->SyncKinematicBodiesToPhysics(fixedDt);
m_World->Step(fixedDt); m_World->Step(fixedDt);
for (const auto& event : m_World->ConsumeTriggerEvents()) { for (const auto& event : m_World->ConsumeTriggerEvents()) {
+1 -1
View File
@@ -115,7 +115,7 @@ void PhysicsWorld::UnregisterRigidbody(Rigidbody& rigidbody) {
rigidbody.DetachPhysicsBody(); rigidbody.DetachPhysicsBody();
} }
void PhysicsWorld::SyncKinematicBodiesToPhysics() { void PhysicsWorld::SyncKinematicBodiesToPhysics(float) {
// Backend-independent sync is intentionally not possible here because // Backend-independent sync is intentionally not possible here because
// PhysicsWorld does not own the list of active rigidbodies. // PhysicsWorld does not own the list of active rigidbodies.
} }
+4 -1
View File
@@ -134,7 +134,7 @@ void SimplePhysicsWorld::Step(float fixedDt) {
m_Bodies.end()); m_Bodies.end());
} }
void SimplePhysicsWorld::SyncKinematicBodiesToPhysics() { void SimplePhysicsWorld::SyncKinematicBodiesToPhysics(float fixedDt) {
for (BodyRecord& body : m_Bodies) { for (BodyRecord& body : m_Bodies) {
if (!body.alive || body.desc.type != RigidbodyType::Kinematic || !body.desc.owner || if (!body.alive || body.desc.type != RigidbodyType::Kinematic || !body.desc.owner ||
body.desc.owner->IsBeingDestroyed() || !body.desc.owner->IsActiveInHierarchy()) { body.desc.owner->IsBeingDestroyed() || !body.desc.owner->IsActiveInHierarchy()) {
@@ -146,6 +146,9 @@ void SimplePhysicsWorld::SyncKinematicBodiesToPhysics() {
body.previousTransform = body.currentTransform; body.previousTransform = body.currentTransform;
body.currentTransform.position = transform.GetWorldPosition(); body.currentTransform.position = transform.GetWorldPosition();
body.currentTransform.rotation = transform.GetWorldRotation(); body.currentTransform.rotation = transform.GetWorldRotation();
body.linearVelocity = fixedDt > 0.0f
? (body.currentTransform.position - body.previousTransform.position) / fixedDt
: glm::vec3{0.0f};
} }
} }
+1
View File
@@ -18,6 +18,7 @@ set(GAME_SRC
src/components/Interactable.cpp src/components/Interactable.cpp
src/components/InteractableButton.cpp src/components/InteractableButton.cpp
src/components/LightToggle.cpp src/components/LightToggle.cpp
src/components/MovingPlatform.cpp
src/components/PrintComponent.cpp src/components/PrintComponent.cpp
src/components/TriggerLoggerComponent.cpp src/components/TriggerLoggerComponent.cpp
) )
@@ -1,6 +1,8 @@
#ifndef LIGHTKEEPER_FIRSTPERSONCONTROLLER_H #ifndef LIGHTKEEPER_FIRSTPERSONCONTROLLER_H
#define LIGHTKEEPER_FIRSTPERSONCONTROLLER_H #define LIGHTKEEPER_FIRSTPERSONCONTROLLER_H
#include <optional>
#include <glm/glm.hpp> #include <glm/glm.hpp>
#include <destrum/Input/InputManager.h> #include <destrum/Input/InputManager.h>
#include <destrum/ObjectModel/Component.h> #include <destrum/ObjectModel/Component.h>
@@ -45,7 +47,12 @@ public:
void SetPossessed(bool possessed); void SetPossessed(bool possessed);
private: private:
[[nodiscard]] bool CheckGrounded() const; struct GroundContact {
glm::vec3 normal;
glm::vec3 velocity;
};
[[nodiscard]] std::optional<GroundContact> FindGround() const;
[[nodiscard]] glm::vec3 GetWishDirection() const; [[nodiscard]] glm::vec3 GetWishDirection() const;
[[nodiscard]] Interactable* FindFocusedInteractable() const; [[nodiscard]] Interactable* FindFocusedInteractable() const;
@@ -67,11 +74,14 @@ private:
float m_CoyoteTime{0.1f}; float m_CoyoteTime{0.1f};
float m_JumpBufferTime{0.12f}; float m_JumpBufferTime{0.12f};
float m_InteractDistance{3.0f}; float m_InteractDistance{3.0f};
float m_MaxSlopeAngle{glm::radians(50.0f)};
float m_GroundStickSpeed{2.0f};
Interactable* m_FocusedInteractable{nullptr}; Interactable* m_FocusedInteractable{nullptr};
bool m_Possessed{false}; bool m_Possessed{false};
bool m_Grounded{false}; bool m_Grounded{false};
glm::vec3 m_GroundNormal{0.0f, 1.0f, 0.0f};
bool m_JumpHeld{false}; bool m_JumpHeld{false};
float m_CoyoteTimer{0.0f}; float m_CoyoteTimer{0.0f};
float m_JumpBufferTimer{0.0f}; float m_JumpBufferTimer{0.0f};
@@ -5,6 +5,7 @@
#include <components/FirstPersonController.h> #include <components/FirstPersonController.h>
#include <components/InteractableButton.h> #include <components/InteractableButton.h>
#include <components/LightToggle.h> #include <components/LightToggle.h>
#include <components/MovingPlatform.h>
#include <components/PrintComponent.h> #include <components/PrintComponent.h>
#include <components/TriggerLoggerComponent.h> #include <components/TriggerLoggerComponent.h>
@@ -13,6 +14,7 @@
X(FirstPersonController) \ X(FirstPersonController) \
X(InteractableButton) \ X(InteractableButton) \
X(LightToggle) \ X(LightToggle) \
X(MovingPlatform) \
X(PrintComponent) \ X(PrintComponent) \
X(TriggerLoggerComponent) X(TriggerLoggerComponent)
@@ -0,0 +1,38 @@
#ifndef LIGHTKEEPER_MOVINGPLATFORM_H
#define LIGHTKEEPER_MOVINGPLATFORM_H
#include <destrum/ObjectModel/Component.h>
#include <glm/vec3.hpp>
class MovingPlatform final : public Component {
public:
explicit MovingPlatform(GameObject& parent);
std::string GetTypeName() const override { return "MovingPlatform"; }
void Start() override;
void Update(float dt) override;
nlohmann::json Serialize() const override;
void Deserialize(const nlohmann::json& data) override;
void ImGuiInspector() override;
void SetTravelOffset(const glm::vec3& offset) { m_TravelOffset = offset; }
void SetTravelDuration(float seconds) { m_TravelDuration = seconds; }
void SetPauseDuration(float seconds) { m_PauseDuration = seconds; }
private:
[[nodiscard]] float GetTravelProgress() const;
[[nodiscard]] glm::vec3 GetOffsetAt(float progress) const;
glm::vec3 m_TravelOffset{8.0f, 0.0f, 0.0f};
float m_TravelDuration{4.0f};
float m_PauseDuration{1.0f};
float m_ElapsedTime{0.0f};
glm::vec3 m_StartPosition{0.0f};
bool m_Started{false};
};
#endif // LIGHTKEEPER_MOVINGPLATFORM_H
@@ -27,6 +27,16 @@ namespace {
} }
return current + delta / distance * maxDelta; return current + delta / distance * maxDelta;
} }
glm::vec3 ProjectOntoSlope(const glm::vec3& vector, const glm::vec3& slopeNormal)
{
const glm::vec3 projected = vector - slopeNormal * glm::dot(vector, slopeNormal);
const float projectedLength = glm::length(projected);
if (projectedLength < 1e-6f) {
return glm::vec3{0.0f};
}
return projected * (glm::length(vector) / projectedLength);
}
} }
FirstPersonController::FirstPersonController(GameObject& parent) FirstPersonController::FirstPersonController(GameObject& parent)
@@ -130,22 +140,38 @@ void FirstPersonController::FixedUpdate(float fixedDt)
m_PreviousPosition = GetTransform().GetWorldPosition(); m_PreviousPosition = GetTransform().GetWorldPosition();
m_HasPreviousPosition = true; m_HasPreviousPosition = true;
glm::vec3 velocity = m_Rigidbody->GetLinearVelocity(); const std::optional<GroundContact> ground = FindGround();
const glm::vec3 groundVelocity = ground ? ground->velocity : glm::vec3{0.0f};
m_Grounded = velocity.y <= 0.1f && CheckGrounded(); // Movement happens relative to whatever is underneath, so moving platforms carry the player
glm::vec3 velocity = m_Rigidbody->GetLinearVelocity() - groundVelocity;
m_Grounded = ground.has_value() && glm::dot(velocity, ground->normal) <= 0.1f;
m_GroundNormal = m_Grounded ? ground->normal : glm::vec3{0.0f, 1.0f, 0.0f};
m_CoyoteTimer = m_Grounded ? m_CoyoteTime : std::max(m_CoyoteTimer - fixedDt, 0.0f); m_CoyoteTimer = m_Grounded ? m_CoyoteTime : std::max(m_CoyoteTimer - fixedDt, 0.0f);
const auto& input = InputManager::GetInstance(); const auto& input = InputManager::GetInstance();
const bool sprinting = m_Possessed && input.IsKeyDown(SDL_SCANCODE_LSHIFT); const bool sprinting = m_Possessed && input.IsKeyDown(SDL_SCANCODE_LSHIFT);
const glm::vec3 wishVelocity = GetWishDirection() * (sprinting ? m_SprintSpeed : m_WalkSpeed); const glm::vec3 wishVelocity = GetWishDirection() * (sprinting ? m_SprintSpeed : m_WalkSpeed);
const float acceleration = m_Grounded ? m_GroundAcceleration : m_AirAcceleration; if (m_Grounded)
const glm::vec3 horizontal = MoveTowards( {
glm::vec3{velocity.x, 0.0f, velocity.z}, const glm::vec3 velocityAlongSlope = velocity - m_GroundNormal * glm::dot(velocity, m_GroundNormal);
wishVelocity, velocity = MoveTowards(
acceleration * fixedDt); velocityAlongSlope,
velocity.x = horizontal.x; ProjectOntoSlope(wishVelocity, m_GroundNormal),
velocity.z = horizontal.z; m_GroundAcceleration * fixedDt);
velocity -= m_GroundNormal * m_GroundStickSpeed;
}
else
{
const glm::vec3 horizontal = MoveTowards(
glm::vec3{velocity.x, 0.0f, velocity.z},
wishVelocity,
m_AirAcceleration * fixedDt);
velocity.x = horizontal.x;
velocity.z = horizontal.z;
}
if (m_JumpBufferTimer > 0.0f && m_CoyoteTimer > 0.0f) if (m_JumpBufferTimer > 0.0f && m_CoyoteTimer > 0.0f)
{ {
@@ -155,22 +181,25 @@ void FirstPersonController::FixedUpdate(float fixedDt)
m_Grounded = false; m_Grounded = false;
} }
// Falling faster than rising, and letting go of jump early cuts the jump short if (!m_Grounded)
float gravity = m_Gravity;
if (velocity.y < 0.0f || !m_JumpHeld)
{ {
gravity *= m_FallGravityMultiplier; // 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;
} }
velocity.y -= gravity * fixedDt;
m_Rigidbody->SetLinearVelocity(velocity); m_Rigidbody->SetLinearVelocity(velocity + groundVelocity);
} }
bool FirstPersonController::CheckGrounded() const std::optional<FirstPersonController::GroundContact> FirstPersonController::FindGround() const
{ {
const auto* world = m_Rigidbody->GetPhysicsWorld(); const auto* world = m_Rigidbody->GetPhysicsWorld();
if (world == nullptr || m_Capsule == nullptr) if (world == nullptr || m_Capsule == nullptr)
return false; return std::nullopt;
const glm::vec3 center = GetTransform().GetWorldPosition() + m_Capsule->GetCenterOffset(); const glm::vec3 center = GetTransform().GetWorldPosition() + m_Capsule->GetCenterOffset();
const float radius = m_Capsule->GetRadius(); const float radius = m_Capsule->GetRadius();
@@ -187,6 +216,7 @@ bool FirstPersonController::CheckGrounded() const
constexpr float startHeight = 0.2f; constexpr float startHeight = 0.2f;
constexpr float probeDistance = 0.1f; constexpr float probeDistance = 0.1f;
const float minWalkableNormalY = std::cos(m_MaxSlopeAngle);
for (const glm::vec2& offset : offsets) for (const glm::vec2& offset : offsets)
{ {
const glm::vec3 origin{center.x + offset.x, bottom + startHeight, center.z + offset.y}; const glm::vec3 origin{center.x + offset.x, bottom + startHeight, center.z + offset.y};
@@ -194,12 +224,15 @@ bool FirstPersonController::CheckGrounded() const
PhysicsRaycastHit hit{}; PhysicsRaycastHit hit{};
if (world->Raycast(origin, glm::vec3{0.0f, -1.0f, 0.0f}, startHeight + probeDistance, hit, if (world->Raycast(origin, glm::vec3{0.0f, -1.0f, 0.0f}, startHeight + probeDistance, hit,
m_Rigidbody->GetBody()) && m_Rigidbody->GetBody()) &&
hit.normal.y > 0.6f) hit.normal.y >= minWalkableNormalY)
{ {
return true; return GroundContact{
.normal = glm::normalize(hit.normal),
.velocity = hit.body.IsValid() ? world->GetLinearVelocity(hit.body) : glm::vec3{0.0f},
};
} }
} }
return false; return std::nullopt;
} }
glm::vec3 FirstPersonController::GetWishDirection() const glm::vec3 FirstPersonController::GetWishDirection() const
@@ -243,9 +276,12 @@ void FirstPersonController::ImGuiInspector()
ImGui::DragFloat("Coyote Time", &m_CoyoteTime, 0.005f, 0.0f, 1.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("Jump Buffer Time", &m_JumpBufferTime, 0.005f, 0.0f, 1.0f);
ImGui::DragFloat("Interact Distance", &m_InteractDistance, 0.05f, 0.0f, 20.0f); ImGui::DragFloat("Interact Distance", &m_InteractDistance, 0.05f, 0.0f, 20.0f);
ImGui::SliderAngle("Max Slope Angle", &m_MaxSlopeAngle, 0.0f, 89.0f);
ImGui::DragFloat("Ground Stick Speed", &m_GroundStickSpeed, 0.05f, 0.0f, 20.0f);
ImGui::DragFloat("Mouse Sensitivity", &m_MouseSensitivity, 0.0001f, 0.0f, 0.1f); ImGui::DragFloat("Mouse Sensitivity", &m_MouseSensitivity, 0.0001f, 0.0f, 0.1f);
ImGui::DragFloat("Eye Height", &m_EyeHeight, 0.01f, -2.0f, 2.0f); ImGui::DragFloat("Eye Height", &m_EyeHeight, 0.01f, -2.0f, 2.0f);
ImGui::Text("Grounded: %s", m_Grounded ? "yes" : "no"); ImGui::Text("Grounded: %s", m_Grounded ? "yes" : "no");
ImGui::Text("Ground slope: %.1f deg", glm::degrees(std::acos(glm::clamp(m_GroundNormal.y, -1.0f, 1.0f))));
ImGui::Text("Yaw: %.1f deg", glm::degrees(m_Yaw)); ImGui::Text("Yaw: %.1f deg", glm::degrees(m_Yaw));
ImGui::Text("Pitch: %.1f deg", glm::degrees(m_Pitch)); ImGui::Text("Pitch: %.1f deg", glm::degrees(m_Pitch));
if (bool possessed = m_Possessed; ImGui::Checkbox("Possessed", &possessed)) { if (bool possessed = m_Possessed; ImGui::Checkbox("Possessed", &possessed)) {
@@ -266,6 +302,8 @@ nlohmann::json FirstPersonController::Serialize() const
{"coyoteTime", m_CoyoteTime}, {"coyoteTime", m_CoyoteTime},
{"jumpBufferTime", m_JumpBufferTime}, {"jumpBufferTime", m_JumpBufferTime},
{"interactDistance", m_InteractDistance}, {"interactDistance", m_InteractDistance},
{"maxSlopeAngle", m_MaxSlopeAngle},
{"groundStickSpeed", m_GroundStickSpeed},
{"mouseSensitivity", m_MouseSensitivity}, {"mouseSensitivity", m_MouseSensitivity},
{"eyeHeight", m_EyeHeight}, {"eyeHeight", m_EyeHeight},
{"yaw", m_Yaw}, {"yaw", m_Yaw},
@@ -285,6 +323,8 @@ void FirstPersonController::Deserialize(const nlohmann::json& data)
m_CoyoteTime = data.value("coyoteTime", m_CoyoteTime); m_CoyoteTime = data.value("coyoteTime", m_CoyoteTime);
m_JumpBufferTime = data.value("jumpBufferTime", m_JumpBufferTime); m_JumpBufferTime = data.value("jumpBufferTime", m_JumpBufferTime);
m_InteractDistance = data.value("interactDistance", m_InteractDistance); m_InteractDistance = data.value("interactDistance", m_InteractDistance);
m_MaxSlopeAngle = data.value("maxSlopeAngle", m_MaxSlopeAngle);
m_GroundStickSpeed = data.value("groundStickSpeed", m_GroundStickSpeed);
m_MouseSensitivity = data.value("mouseSensitivity", m_MouseSensitivity); m_MouseSensitivity = data.value("mouseSensitivity", m_MouseSensitivity);
m_EyeHeight = data.value("eyeHeight", m_EyeHeight); m_EyeHeight = data.value("eyeHeight", m_EyeHeight);
m_Yaw = data.value("yaw", m_Yaw); m_Yaw = data.value("yaw", m_Yaw);
@@ -0,0 +1,79 @@
#include <components/MovingPlatform.h>
#include <algorithm>
#include <cmath>
#include <destrum/ObjectModel/GameObject.h>
#include <imgui.h>
MovingPlatform::MovingPlatform(GameObject& parent)
: Component(parent, "MovingPlatform")
{
}
void MovingPlatform::Start()
{
const glm::vec3 currentPosition = GetTransform().GetLocalPosition();
m_StartPosition = currentPosition - GetOffsetAt(GetTravelProgress());
m_Started = true;
}
void MovingPlatform::Update(float dt)
{
if (!m_Started) {
return;
}
m_ElapsedTime += dt;
GetTransform().SetLocalPosition(m_StartPosition + GetOffsetAt(GetTravelProgress()));
}
float MovingPlatform::GetTravelProgress() const
{
const float travelDuration = std::max(m_TravelDuration, 0.01f);
const float pauseDuration = std::max(m_PauseDuration, 0.0f);
const float legDuration = travelDuration + pauseDuration;
const float timeInCycle = std::fmod(m_ElapsedTime, legDuration * 2.0f);
const bool returning = timeInCycle >= legDuration;
const float timeInLeg = returning ? timeInCycle - legDuration : timeInCycle;
const float legProgress = std::clamp((timeInLeg - pauseDuration) / travelDuration, 0.0f, 1.0f);
return returning ? 1.0f - legProgress : legProgress;
}
glm::vec3 MovingPlatform::GetOffsetAt(float progress) const
{
const float eased = progress * progress * (3.0f - 2.0f * progress);
return m_TravelOffset * eased;
}
nlohmann::json MovingPlatform::Serialize() const
{
return {
{"travelOffset", {m_TravelOffset.x, m_TravelOffset.y, m_TravelOffset.z}},
{"travelDuration", m_TravelDuration},
{"pauseDuration", m_PauseDuration},
{"elapsedTime", m_ElapsedTime},
};
}
void MovingPlatform::Deserialize(const nlohmann::json& data)
{
if (data.contains("travelOffset")) {
const auto& offset = data.at("travelOffset");
m_TravelOffset = {offset.at(0).get<float>(), offset.at(1).get<float>(), offset.at(2).get<float>()};
}
m_TravelDuration = std::max(data.value("travelDuration", m_TravelDuration), 0.01f);
m_PauseDuration = std::max(data.value("pauseDuration", m_PauseDuration), 0.0f);
m_ElapsedTime = std::max(data.value("elapsedTime", m_ElapsedTime), 0.0f);
}
void MovingPlatform::ImGuiInspector()
{
ImGui::Text("Progress: %.0f%%", GetTravelProgress() * 100.0f);
ImGui::DragFloat3("Travel Offset", &m_TravelOffset.x, 0.05f);
ImGui::DragFloat("Travel Duration", &m_TravelDuration, 0.05f, 0.01f, 60.0f, "%.2f s");
ImGui::DragFloat("Pause Duration", &m_PauseDuration, 0.05f, 0.0f, 60.0f, "%.2f s");
}
+42 -1
View File
@@ -19,6 +19,7 @@
#include <components/Door.h> #include <components/Door.h>
#include <components/InteractableButton.h> #include <components/InteractableButton.h>
#include <components/LightToggle.h> #include <components/LightToggle.h>
#include <components/MovingPlatform.h>
#include <components/TriggerLoggerComponent.h> #include <components/TriggerLoggerComponent.h>
#include <glm/gtc/constants.hpp> #include <glm/gtc/constants.hpp>
@@ -26,6 +27,7 @@
#include <spdlog/spdlog.h> #include <spdlog/spdlog.h>
#include <cmath>
#include <memory> #include <memory>
#include <optional> #include <optional>
#include <string> #include <string>
@@ -124,10 +126,12 @@ namespace {
GameObject* CreateBox(Scene& scene, GfxDevice& gfxDevice, RenderResources& resources, GameObject* CreateBox(Scene& scene, GfxDevice& gfxDevice, RenderResources& resources,
const std::string& name, const glm::vec3& position, const glm::vec3& size, const std::string& name, const glm::vec3& position, const glm::vec3& size,
MaterialID materialID, RigidbodyType type) MaterialID materialID, RigidbodyType type,
const glm::quat& rotation = glm::quat{1.0f, 0.0f, 0.0f, 0.0f})
{ {
GameObject* box = scene.CreateGameObject(name); GameObject* box = scene.CreateGameObject(name);
box->GetTransform().SetWorldPosition(position); box->GetTransform().SetWorldPosition(position);
box->GetTransform().SetWorldRotation(rotation);
auto* boxMeshComp = box->AddComponent<MeshRendererComponent>(); auto* boxMeshComp = box->AddComponent<MeshRendererComponent>();
boxMeshComp->SetMeshID(resources.meshes().addMesh(gfxDevice, PrimitiveMeshes::MakeBox(size))); boxMeshComp->SetMeshID(resources.meshes().addMesh(gfxDevice, PrimitiveMeshes::MakeBox(size)));
@@ -221,6 +225,42 @@ namespace {
button->AddComponent<InteractableButton>()->AddTarget(target); button->AddComponent<InteractableButton>()->AddTarget(target);
} }
void CreateMovingPlatform(Scene& scene, GfxDevice& gfxDevice, RenderResources& resources)
{
const MaterialID rampMaterial = AddPrototypeMaterial(gfxDevice, resources, "Green", 0.6f);
const MaterialID platformMaterial = AddPrototypeMaterial(gfxDevice, resources, "Purple", 0.5f);
constexpr float laneZ = 2.0f;
constexpr float platformHeight = 1.0f;
constexpr float rampStartX = 9.0f;
constexpr float rampLength = 3.0f;
constexpr float rampThickness = 0.3f;
constexpr float travelDistance = 8.0f;
const glm::vec3 platformSize{3.0f, 0.3f, 3.0f};
const float platformTop = GroundHeight + platformHeight;
const glm::quat rampRotation =
glm::angleAxis(std::atan2(platformHeight, rampLength), glm::vec3{0.0f, 0.0f, 1.0f});
const glm::vec3 rampTopCenter{rampStartX + rampLength * 0.5f, GroundHeight + platformHeight * 0.5f, laneZ};
const glm::vec3 rampCenter = rampTopCenter - rampRotation * glm::vec3{0.0f, rampThickness * 0.5f, 0.0f};
const float rampSlopeLength = glm::length(glm::vec2{rampLength, platformHeight});
CreateBox(scene, gfxDevice, resources, "PlatformRamp", rampCenter,
{rampSlopeLength, rampThickness, platformSize.z}, rampMaterial, RigidbodyType::Static,
rampRotation);
const float platformStartX = rampStartX + rampLength + platformSize.x * 0.5f;
GameObject* platform = CreateBox(scene, gfxDevice, resources, "MovingPlatform",
{platformStartX, platformTop - platformSize.y * 0.5f, laneZ},
platformSize, platformMaterial, RigidbodyType::Kinematic);
platform->AddComponent<MovingPlatform>()->SetTravelOffset({travelDistance, 0.0f, 0.0f});
const float landingStartX = platformStartX + travelDistance + platformSize.x * 0.5f;
CreateBox(scene, gfxDevice, resources, "PlatformLanding",
{landingStartX + 1.0f, GroundHeight + platformHeight * 0.5f, laneZ},
{2.0f, platformHeight, platformSize.z}, rampMaterial, RigidbodyType::Static);
}
struct WallOpening { struct WallOpening {
float offset; float offset;
float width; float width;
@@ -467,6 +507,7 @@ MainSceneObjects BuildMainScene(Scene& scene, GfxDevice& gfxDevice, RenderResour
const GameObject* door = CreateDoorway(scene, gfxDevice, resources); const GameObject* door = CreateDoorway(scene, gfxDevice, resources);
CreateButton(scene, gfxDevice, resources, *door); CreateButton(scene, gfxDevice, resources, *door);
CreateHouse(scene, gfxDevice, resources); CreateHouse(scene, gfxDevice, resources);
CreateMovingPlatform(scene, gfxDevice, resources);
CreatePlayer(scene, objects); CreatePlayer(scene, objects);
LoadSpawnBallResources(gfxDevice, resources, objects); LoadSpawnBallResources(gfxDevice, resources, objects);
+43
View File
@@ -540,6 +540,48 @@ namespace {
"a frustum entirely behind the camera must not overlap"); "a frustum entirely behind the camera must not overlap");
} }
void testKinematicBodiesMoveWithVelocity()
{
constexpr float fixedDt = 1.0f / 60.0f;
Scene& scene = SceneManager::GetInstance().CreateScene("kinematic-velocity");
GameObject* platform = scene.CreateGameObject("platform");
platform->AddComponent<BoxCollider>(glm::vec3{2.0f, 0.25f, 2.0f});
auto* platformBody = platform->AddComponent<Rigidbody>();
platformBody->SetType(RigidbodyType::Kinematic);
GameObject* crate = scene.CreateGameObject("crate");
crate->GetTransform().SetWorldPosition({0.0f, 0.76f, 0.0f});
crate->AddComponent<BoxCollider>(glm::vec3{0.5f});
auto* crateBody = crate->AddComponent<Rigidbody>();
crateBody->SetFriction(1.0f);
crateBody->SetAllowSleep(false);
scene.CommitPendingAdditions();
scene.GetPhysics().RefreshGameObject(*platform);
scene.GetPhysics().RefreshGameObject(*crate);
for (int step = 0; step < 30; ++step) {
scene.FixedUpdate(fixedDt);
}
check(glm::length(platformBody->GetLinearVelocity()) < 0.001f,
"a kinematic body that is not moved must have no velocity");
constexpr float platformSpeed = 2.0f;
for (int step = 0; step < 60; ++step) {
platform->GetTransform().SetWorldPosition(
platform->GetTransform().GetWorldPosition() + glm::vec3{platformSpeed * fixedDt, 0.0f, 0.0f});
scene.FixedUpdate(fixedDt);
}
check(close(platformBody->GetLinearVelocity().x, platformSpeed),
"a kinematic body moved through its transform must get the matching velocity");
check(crate->GetTransform().GetWorldPosition().x > 1.5f,
"a dynamic body resting on a moving kinematic platform must be carried along");
SceneManager::GetInstance().Destroy();
}
void testRenderer2DShapes() void testRenderer2DShapes()
{ {
auto& r2d = Renderer2D::GetInstance(); auto& r2d = Renderer2D::GetInstance();
@@ -1335,6 +1377,7 @@ int main()
testColliderCenterOffset(); testColliderCenterOffset();
testCapsuleColliderDebugLines(); testCapsuleColliderDebugLines();
testFrustumFromViewProj(); testFrustumFromViewProj();
testKinematicBodiesMoveWithVelocity();
testRenderer2DShapes(); testRenderer2DShapes();
testFontAtlasAndLayout(); testFontAtlasAndLayout();
std::cout << "destrum tests passed\n"; std::cout << "destrum tests passed\n";