add basic ah physicsworld

This commit is contained in:
2026-06-23 05:04:17 +02:00
parent 85fbf2c1ff
commit 067caf5fe6
25 changed files with 1115 additions and 152 deletions
+53 -35
View File
@@ -12,10 +12,12 @@
#include <Jolt/Jolt.h>
App::App() {
App::App()
{
}
void App::init(const AppParams &params) {
void App::init(const AppParams& params)
{
m_params = params;
AssetFS::GetInstance().Init(params.exeDir);
@@ -33,7 +35,8 @@ void App::init(const AppParams &params) {
SDL_WINDOW_VULKAN);
SDL_SetWindowResizable(window, SDL_TRUE);
if (!window) {
if (!window)
{
spdlog::error("Failed to create window. SDL Error: {}", SDL_GetError());
std::exit(1);
}
@@ -48,7 +51,8 @@ void App::init(const AppParams &params) {
customInit();
}
void App::run() {
void App::run()
{
Time::GetInstance().Update(); // initialize delta timing
const float fixedDt = static_cast<float>(Time::GetInstance().FixedDeltaTime());
@@ -56,14 +60,16 @@ void App::run() {
float accumulator = 0.0f;
isRunning = true;
while (isRunning) {
while (isRunning)
{
// ---- Update timing ---
Time::GetInstance().Update();
float dt = static_cast<float>(Time::GetInstance().DeltaTime());
if (dt > 0.25f) dt = 0.25f;
if (dt > 0.0f) {
if (dt > 0.0f)
{
float newFPS = 1.0f / dt;
avgFPS = std::lerp(avgFPS, newFPS, 0.1f);
}
@@ -75,16 +81,21 @@ void App::run() {
SDL_Event event;
while (SDL_PollEvent(&event)) {
while (SDL_PollEvent(&event))
{
imguiPass.handleEvent(event);
if (event.type == SDL_QUIT) {
if (event.type == SDL_QUIT)
{
isRunning = false;
break;
}
if (event.type == SDL_WINDOWEVENT) {
switch (event.window.event) {
case SDL_WINDOWEVENT_SIZE_CHANGED:
case SDL_WINDOWEVENT_RESIZED: {
if (event.type == SDL_WINDOWEVENT)
{
switch (event.window.event)
{
case SDL_WINDOWEVENT_SIZE_CHANGED:
case SDL_WINDOWEVENT_RESIZED:
{
resizePending = true;
lastResizeTime = std::chrono::steady_clock::now();
break;
@@ -92,22 +103,24 @@ void App::run() {
}
}
const bool mouseEvent =
event.type == SDL_MOUSEBUTTONDOWN ||
event.type == SDL_MOUSEBUTTONUP ||
event.type == SDL_MOUSEMOTION ||
event.type == SDL_MOUSEWHEEL;
event.type == SDL_MOUSEBUTTONDOWN ||
event.type == SDL_MOUSEBUTTONUP ||
event.type == SDL_MOUSEMOTION ||
event.type == SDL_MOUSEWHEEL;
const bool keyboardEvent =
event.type == SDL_KEYDOWN ||
event.type == SDL_KEYUP ||
event.type == SDL_TEXTINPUT;
event.type == SDL_KEYDOWN ||
event.type == SDL_KEYUP ||
event.type == SDL_TEXTINPUT;
const bool capturedByImgui =
(mouseEvent && imguiPass.wantsMouse()) ||
(keyboardEvent && imguiPass.wantsKeyboard());
(mouseEvent && imguiPass.wantsMouse()) ||
(keyboardEvent && imguiPass.wantsKeyboard());
if (!capturedByImgui) {
if (InputManager::GetInstance().ProcessEvent(event)) {
if (!capturedByImgui)
{
if (InputManager::GetInstance().ProcessEvent(event))
{
isRunning = false;
}
}
@@ -123,13 +136,12 @@ void App::run() {
imguiPass.endFrame();
int steps = 0;
while (accumulator >= fixedDt && steps < maxSteps) {
while (accumulator >= fixedDt && steps < maxSteps)
{
// physics.Update(fixedDt);
SDL_SetWindowTitle(
window,
fmt::format("{} - FPS: {:.2f}", m_params.windowTitle, avgFPS).c_str()
);
customFixedUpdate(fixedDt);
// physics.Step(fixedDt);
// physics.SyncTransforms();
accumulator -= fixedDt;
steps++;
@@ -138,11 +150,13 @@ void App::run() {
const float alpha = accumulator / fixedDt;
if (gfxDevice.needsSwapchainRecreate() || resizePending) {
if (gfxDevice.needsSwapchainRecreate() || resizePending)
{
auto now = std::chrono::steady_clock::now();
if (resizePending &&
now - lastResizeTime < std::chrono::milliseconds(100)) {
now - lastResizeTime < std::chrono::milliseconds(100))
{
continue;
}
@@ -150,7 +164,8 @@ void App::run() {
int h = 0;
SDL_Vulkan_GetDrawableSize(window, &w, &h);
if (w == 0 || h == 0) {
if (w == 0 || h == 0)
{
continue;
}
@@ -166,9 +181,11 @@ void App::run() {
customDraw();
if (frameLimit) {
if (frameLimit)
{
auto sleepTime = Time::GetInstance().SleepDuration();
if (sleepTime.count() > 0) {
if (sleepTime.count() > 0)
{
std::this_thread::sleep_for(sleepTime);
}
}
@@ -177,7 +194,8 @@ void App::run() {
gfxDevice.waitIdle();
}
void App::cleanup() {
void App::cleanup()
{
spdlog::info("Cleaning up");
customCleanup();
}
@@ -0,0 +1 @@
#include <destrum/Components/Physics/BoxCollider.h>
@@ -0,0 +1,45 @@
#include <destrum/Components/Physics/Rigidbody.h>
#include <destrum/Physics/PhysicsWorld.h>
void Rigidbody::AttachPhysicsBody(PhysicsWorld* world, PhysicsBodyHandle body) {
m_World = world;
m_Body = body;
}
void Rigidbody::DetachPhysicsBody() {
m_World = nullptr;
m_Body.Reset();
}
void Rigidbody::AddForce(const glm::vec3& force) {
if (!HasPhysicsBody()) {
return;
}
m_World->AddForce(m_Body, force);
}
void Rigidbody::AddImpulse(const glm::vec3& impulse) {
if (!HasPhysicsBody()) {
return;
}
m_World->AddImpulse(m_Body, impulse);
}
void Rigidbody::SetLinearVelocity(const glm::vec3& velocity) {
if (!HasPhysicsBody()) {
return;
}
m_World->SetLinearVelocity(m_Body, velocity);
}
glm::vec3 Rigidbody::GetLinearVelocity() const {
if (!HasPhysicsBody()) {
return glm::vec3{0.0f};
}
return m_World->GetLinearVelocity(m_Body);
}
@@ -0,0 +1,35 @@
#include <destrum/Physics/PhysicsSceneBridge.h>
#include <destrum/Components/Physics/Rigidbody.h>
#include <destrum/ObjectModel/GameObject.h>
PhysicsSceneBridge::PhysicsSceneBridge(std::unique_ptr<PhysicsWorld> world)
: m_World(std::move(world)) {
}
void PhysicsSceneBridge::RegisterGameObject(GameObject& object) {
if (auto* rb = object.GetComponent<Rigidbody>()) {
if (!rb->HasPhysicsBody()) {
m_World->RegisterRigidbody(*rb);
}
}
}
void PhysicsSceneBridge::UnregisterGameObject(GameObject& object) {
if (auto* rb = object.GetComponent<Rigidbody>()) {
if (rb->HasPhysicsBody()) {
m_World->UnregisterRigidbody(*rb);
}
}
}
void PhysicsSceneBridge::FixedUpdate(float fixedDt) {
if (auto* simple = dynamic_cast<SimplePhysicsWorld*>(m_World.get())) {
simple->SyncKinematicBodiesToPhysics();
simple->Step(fixedDt);
simple->SyncDynamicBodiesToTransforms();
return;
}
m_World->Step(fixedDt);
}
+62
View File
@@ -0,0 +1,62 @@
#include <destrum/Physics/PhysicsWorld.h>
#include <stdexcept>
#include <destrum/Components/Physics/Rigidbody.h>
#include <destrum/Components/Physics/Collider.h>
#include <destrum/ObjectModel/GameObject.h>
#include <destrum/ObjectModel/Transform.h>
#include <destrum/Components/Physics/Rigidbody.h>
void PhysicsWorld::RegisterRigidbody(Rigidbody& rigidbody) {
GameObject* owner = rigidbody.GetGameObject();
Transform& transform = owner->GetTransform();
auto* collider = owner->GetComponent<Collider>();
if (!collider) {
throw std::runtime_error("Rigidbody requires a Collider on the same GameObject for now.");
}
PhysicsBodyDesc desc{};
desc.owner = owner;
desc.transform.position = transform.GetWorldPosition();
desc.transform.rotation = transform.GetWorldRotation();
desc.shape = collider->BuildPhysicsShape();
desc.type = rigidbody.GetType();
desc.mass = rigidbody.GetMass();
desc.useGravity = rigidbody.UsesGravity();
desc.allowSleep = rigidbody.AllowsSleep();
desc.material.friction = rigidbody.GetFriction();
desc.material.restitution = rigidbody.GetRestitution();
PhysicsBodyHandle handle = CreateBody(desc);
rigidbody.AttachPhysicsBody(this, handle);
}
void PhysicsWorld::UnregisterRigidbody(Rigidbody& rigidbody) {
PhysicsBodyHandle handle = rigidbody.GetBody();
if (handle.IsValid()) {
DestroyBody(handle);
}
rigidbody.DetachPhysicsBody();
}
void PhysicsWorld::SyncKinematicBodiesToPhysics() {
// Backend-independent sync is intentionally not possible here because
// PhysicsWorld does not own the list of active rigidbodies.
//
// Use SimplePhysicsWorld as a reference implementation.
//
// If you use Jolt/PhysX/Bullet later, keep a body table in the backend:
// handle -> { GameObject*, Rigidbody* }.
}
void PhysicsWorld::SyncDynamicBodiesToTransforms() {
// Backend-independent sync is intentionally not possible here because
// PhysicsWorld does not own the list of active rigidbodies.
//
// Use SimplePhysicsWorld as a reference implementation.
}
+277
View File
@@ -0,0 +1,277 @@
#include <destrum/Physics/SimplePhysicsWorld.h>
#include <algorithm>
#include <cmath>
#include <limits>
#include <destrum/Components/Physics/Rigidbody.h>
#include <destrum/ObjectModel/GameObject.h>
#include <destrum/ObjectModel/Transform.h>
SimplePhysicsWorld::SimplePhysicsWorld(const glm::vec3& gravity)
: m_Gravity(gravity) {
}
PhysicsBodyHandle SimplePhysicsWorld::CreateBody(const PhysicsBodyDesc& desc) {
BodyRecord record{};
record.alive = true;
record.handle = PhysicsBodyHandle{m_NextId++};
record.desc = desc;
record.previousTransform = desc.transform;
record.currentTransform = desc.transform;
if (desc.owner) {
record.rigidbody = desc.owner->GetComponent<Rigidbody>();
}
m_Bodies.emplace_back(record);
return record.handle;
}
void SimplePhysicsWorld::DestroyBody(PhysicsBodyHandle body) {
if (BodyRecord* record = FindBody(body)) {
record->alive = false;
record->rigidbody = nullptr;
}
}
void SimplePhysicsWorld::SetBodyTransform(PhysicsBodyHandle body, const PhysicsTransform& transform) {
BodyRecord* record = FindBody(body);
if (!record) {
return;
}
record->previousTransform = record->currentTransform;
record->currentTransform = transform;
}
PhysicsTransform SimplePhysicsWorld::GetBodyTransform(PhysicsBodyHandle body) const {
const BodyRecord* record = FindBody(body);
if (!record) {
return {};
}
return record->currentTransform;
}
void SimplePhysicsWorld::SetLinearVelocity(PhysicsBodyHandle body, const glm::vec3& velocity) {
BodyRecord* record = FindBody(body);
if (!record) {
return;
}
record->linearVelocity = velocity;
}
glm::vec3 SimplePhysicsWorld::GetLinearVelocity(PhysicsBodyHandle body) const {
const BodyRecord* record = FindBody(body);
if (!record) {
return glm::vec3{0.0f};
}
return record->linearVelocity;
}
void SimplePhysicsWorld::AddForce(PhysicsBodyHandle body, const glm::vec3& force) {
BodyRecord* record = FindBody(body);
if (!record || record->desc.type != RigidbodyType::Dynamic) {
return;
}
record->accumulatedForce += force;
}
void SimplePhysicsWorld::AddImpulse(PhysicsBodyHandle body, const glm::vec3& impulse) {
BodyRecord* record = FindBody(body);
if (!record || record->desc.type != RigidbodyType::Dynamic) {
return;
}
const float invMass = record->desc.mass > 0.0f ? 1.0f / record->desc.mass : 0.0f;
record->linearVelocity += impulse * invMass;
}
void SimplePhysicsWorld::Step(float fixedDt) {
if (fixedDt <= 0.0f) {
return;
}
for (BodyRecord& body : m_Bodies) {
if (!body.alive) {
continue;
}
body.previousTransform = body.currentTransform;
if (body.desc.type != RigidbodyType::Dynamic) {
continue;
}
const float invMass = body.desc.mass > 0.0f ? 1.0f / body.desc.mass : 0.0f;
glm::vec3 acceleration{0.0f};
if (body.desc.useGravity) {
acceleration += m_Gravity;
}
acceleration += body.accumulatedForce * invMass;
// Semi-implicit Euler.
body.linearVelocity += acceleration * fixedDt;
body.currentTransform.position += body.linearVelocity * fixedDt;
body.accumulatedForce = glm::vec3{0.0f};
}
// Compact dead records occasionally.
m_Bodies.erase(
std::remove_if(m_Bodies.begin(), m_Bodies.end(),
[](const BodyRecord& body) { return !body.alive; }),
m_Bodies.end());
}
void SimplePhysicsWorld::SyncKinematicBodiesToPhysics() {
for (BodyRecord& body : m_Bodies) {
if (!body.alive || body.desc.type != RigidbodyType::Kinematic || !body.desc.owner) {
continue;
}
Transform& transform = body.desc.owner->GetTransform();
body.previousTransform = body.currentTransform;
body.currentTransform.position = transform.GetWorldPosition();
body.currentTransform.rotation = transform.GetWorldRotation();
}
}
void SimplePhysicsWorld::SyncDynamicBodiesToTransforms() {
for (BodyRecord& body : m_Bodies) {
if (!body.alive || body.desc.type != RigidbodyType::Dynamic || !body.desc.owner) {
continue;
}
Transform& transform = body.desc.owner->GetTransform();
transform.SetWorldPosition(body.currentTransform.position);
transform.SetWorldRotation(body.currentTransform.rotation);
}
}
bool SimplePhysicsWorld::Raycast(const glm::vec3& origin,
const glm::vec3& direction,
float maxDistance,
PhysicsRaycastHit& hit) const {
if (maxDistance <= 0.0f) {
return false;
}
const float len = glm::length(direction);
if (len <= 0.00001f) {
return false;
}
const glm::vec3 dir = direction / len;
bool found = false;
float bestDistance = std::numeric_limits<float>::max();
for (const BodyRecord& body : m_Bodies) {
if (!body.alive || body.desc.shape.type == PhysicsShapeType::None) {
continue;
}
float distance = 0.0f;
const float radius = GetApproxBoundingRadius(body);
const glm::vec3 center = body.currentTransform.position + body.desc.shape.centerOffset;
if (RaySphere(origin, dir, center, radius, maxDistance, distance)) {
if (distance < bestDistance) {
bestDistance = distance;
found = true;
hit.object = body.desc.owner;
hit.body = body.handle;
hit.distance = distance;
hit.point = origin + dir * distance;
const glm::vec3 n = hit.point - center;
hit.normal = glm::length(n) > 0.00001f ? glm::normalize(n) : glm::vec3{0.0f, 1.0f, 0.0f};
}
}
}
return found;
}
SimplePhysicsWorld::BodyRecord* SimplePhysicsWorld::FindBody(PhysicsBodyHandle body) {
for (BodyRecord& record : m_Bodies) {
if (record.alive && record.handle == body) {
return &record;
}
}
return nullptr;
}
const SimplePhysicsWorld::BodyRecord* SimplePhysicsWorld::FindBody(PhysicsBodyHandle body) const {
for (const BodyRecord& record : m_Bodies) {
if (record.alive && record.handle == body) {
return &record;
}
}
return nullptr;
}
float SimplePhysicsWorld::GetApproxBoundingRadius(const BodyRecord& body) const {
const PhysicsShapeDesc& shape = body.desc.shape;
switch (shape.type) {
case PhysicsShapeType::Box:
return glm::length(shape.halfExtents);
case PhysicsShapeType::Sphere:
return shape.radius;
case PhysicsShapeType::Capsule:
return shape.height * 0.5f;
case PhysicsShapeType::None:
default:
return 0.0f;
}
}
bool SimplePhysicsWorld::RaySphere(const glm::vec3& origin,
const glm::vec3& dirNormalized,
const glm::vec3& center,
float radius,
float maxDistance,
float& outDistance) {
const glm::vec3 oc = origin - center;
const float a = glm::dot(dirNormalized, dirNormalized);
const float b = 2.0f * glm::dot(oc, dirNormalized);
const float c = glm::dot(oc, oc) - radius * radius;
const float discriminant = b * b - 4.0f * a * c;
if (discriminant < 0.0f) {
return false;
}
const float sqrtDisc = std::sqrt(discriminant);
const float t0 = (-b - sqrtDisc) / (2.0f * a);
const float t1 = (-b + sqrtDisc) / (2.0f * a);
float t = t0;
if (t < 0.0f) {
t = t1;
}
if (t < 0.0f || t > maxDistance) {
return false;
}
outDistance = t;
return true;
}
+2 -1
View File
@@ -57,12 +57,13 @@ void Scene::Update() {
}
}
void Scene::FixedUpdate() {
void Scene::FixedUpdate(float dt) {
for (const auto& object: m_objects) {
if (object->IsActiveInHierarchy()) {
object->FixedUpdate();
}
}
m_Physics.FixedUpdate(dt);
}
void Scene::LateUpdate() {
+2 -2
View File
@@ -8,8 +8,8 @@ void SceneManager::Update() {
m_scenes[m_ActiveSceneIndex]->Update();
}
void SceneManager::FixedUpdate() {
m_scenes[m_ActiveSceneIndex]->FixedUpdate();
void SceneManager::FixedUpdate(float dt) {
m_scenes[m_ActiveSceneIndex]->FixedUpdate(dt);
}
void SceneManager::LateUpdate() {