feat: add moving platform support + fix jolt
This commit is contained in:
@@ -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; }
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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()) {
|
||||||
|
|||||||
@@ -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.
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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");
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
|
||||||
|
|||||||
@@ -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";
|
||||||
|
|||||||
Reference in New Issue
Block a user