diff --git a/src/features/editScene/components/RigidBody.hpp b/src/features/editScene/components/RigidBody.hpp index 24f2956..5219e6a 100644 --- a/src/features/editScene/components/RigidBody.hpp +++ b/src/features/editScene/components/RigidBody.hpp @@ -29,6 +29,9 @@ struct RigidBodyComponent { float restitution = 0.0f; // Bounciness bool isSensor = false; // Detects collisions but doesn't respond + // Enable/disable physics body + bool enabled = true; // If false, body is removed from physics world + // The Jolt body ID JPH::BodyID bodyID; bool bodyCreated = false; diff --git a/src/features/editScene/systems/PhysicsSystem.cpp b/src/features/editScene/systems/PhysicsSystem.cpp index b69deb7..89ee2df 100644 --- a/src/features/editScene/systems/PhysicsSystem.cpp +++ b/src/features/editScene/systems/PhysicsSystem.cpp @@ -33,6 +33,9 @@ void EditorPhysicsSystem::update(float deltaTime) // Step physics m_physics->update(deltaTime); + + // Update SceneNodes from physics for dynamic bodies + updateDynamicBodies(); } void EditorPhysicsSystem::syncBodies() @@ -43,8 +46,75 @@ void EditorPhysicsSystem::syncBodies() m_rigidBodyQuery.each([&](flecs::entity entity, RigidBodyComponent& rigidBody, TransformComponent& transform) { + // Handle enabled/disabled state + if (!rigidBody.enabled) { + // Body is disabled - remove from physics if it exists + if (rigidBody.bodyCreated) { + removeRigidBody(rigidBody); + } + return; // Skip rest of processing for disabled bodies + } + + // Body is enabled if (rigidBody.bodyDirty || !rigidBody.bodyCreated) { + // Create or recreate the body + // When transitioning from disabled to enabled, this will sync SceneNode -> physics updateRigidBody(entity, rigidBody, transform); + } else if (rigidBody.bodyCreated && transform.node) { + // Body exists and is enabled - handle per-body-type updates + switch (rigidBody.bodyType) { + case RigidBodyComponent::BodyType::Dynamic: + // Dynamic: Physics controls position, SceneNode is updated after physics step + // Nothing to do here - updateDynamicBodies handles SceneNode update + break; + + case RigidBodyComponent::BodyType::Static: + // Static: SceneNode controls position + // Sync SceneNode -> physics when transform changes + m_physics->setPositionAndRotation( + rigidBody.bodyID, + transform.node->_getDerivedPosition(), + transform.node->_getDerivedOrientation(), + false); + break; + + case RigidBodyComponent::BodyType::Kinematic: + // Kinematic: SceneNode controls position (moved by code, not forces) + // Sync SceneNode -> physics every frame + m_physics->setPositionAndRotation( + rigidBody.bodyID, + transform.node->_getDerivedPosition(), + transform.node->_getDerivedOrientation(), + false); + break; + } + } + }); +} + +void EditorPhysicsSystem::updateDynamicBodies() +{ + if (!m_initialized || !m_physics) return; + + // Update SceneNode positions from physics for dynamic bodies + m_rigidBodyQuery.each([&](flecs::entity entity, + RigidBodyComponent& rigidBody, + TransformComponent& transform) { + if (!rigidBody.enabled || !rigidBody.bodyCreated || !transform.node) + return; + + if (rigidBody.bodyType == RigidBodyComponent::BodyType::Dynamic) { + // Get position and rotation from physics body + Ogre::Vector3 position = m_physics->getPosition(rigidBody.bodyID); + Ogre::Quaternion rotation = m_physics->getRotation(rigidBody.bodyID); + + // Update SceneNode + transform.node->_setDerivedPosition(position); + transform.node->_setDerivedOrientation(rotation); + + // Also update the Transform component to match + transform.position = position; + transform.rotation = rotation; } }); } @@ -73,7 +143,7 @@ JPH::ShapeRefC EditorPhysicsSystem::createShape(PhysicsColliderComponent& collid } case PhysicsColliderComponent::ShapeType::Cylinder: { result = m_physics->createCylinderShape(collider.halfHeight, - collider.radius); + collider.radius); break; } case PhysicsColliderComponent::ShapeType::Mesh: { @@ -173,7 +243,7 @@ void EditorPhysicsSystem::updateRigidBody(flecs::entity entity, removeRigidBody(rigidBody); } - // Check for mesh colliders on dynamic bodies (not supported by Jolt) + // Check for mesh colliders - they only work with Static bodies bool hasMeshCollider = false; entity.children([&](flecs::entity child) { if (child.has()) { @@ -184,10 +254,23 @@ void EditorPhysicsSystem::updateRigidBody(flecs::entity entity, } }); - if (hasMeshCollider && rigidBody.bodyType == RigidBodyComponent::BodyType::Dynamic) { + if (hasMeshCollider && rigidBody.bodyType != RigidBodyComponent::BodyType::Static) { Ogre::LogManager::getSingleton().logMessage( - "WARNING: Mesh colliders can only be used with Static or Kinematic bodies, not Dynamic." - " The body will be created but may not behave correctly."); + "ERROR: Mesh colliders can only be used with Static bodies. " + "Body type is " + + std::string(rigidBody.bodyType == RigidBodyComponent::BodyType::Dynamic ? "Dynamic" : "Kinematic") + + ". Please use Convex Hull collider instead, or set body type to Static. " + "Physics body will be removed until configuration is valid."); + + // Remove existing body if any + if (rigidBody.bodyCreated) { + removeRigidBody(rigidBody); + } + + // Mark as not created but not dirty (we don't want to retry every frame) + rigidBody.bodyCreated = false; + rigidBody.bodyDirty = false; + return; } // Build collision shape @@ -214,14 +297,21 @@ void EditorPhysicsSystem::updateRigidBody(flecs::entity entity, break; } - // Create body + // Create body at the current SceneNode position + // This ensures that when enabling a body, it starts at the current visual position + Ogre::Vector3 startPosition = transform.node ? + transform.node->_getDerivedPosition() : transform.position; + Ogre::Quaternion startRotation = transform.node ? + transform.node->_getDerivedOrientation() : transform.rotation; + + // Create body or sensor if (rigidBody.isSensor) { - rigidBody.bodyID = m_physics->createSensor(shape, transform.node, + rigidBody.bodyID = m_physics->createSensor(shape, startPosition, startRotation, motionType, layer); } else { rigidBody.bodyID = m_physics->createBody(shape, rigidBody.mass, - transform.node, - motionType, layer); + startPosition, startRotation, + motionType, layer); } if (rigidBody.bodyID.IsInvalid()) { diff --git a/src/features/editScene/systems/PhysicsSystem.hpp b/src/features/editScene/systems/PhysicsSystem.hpp index 317fdb7..8cd1d8a 100644 --- a/src/features/editScene/systems/PhysicsSystem.hpp +++ b/src/features/editScene/systems/PhysicsSystem.hpp @@ -26,6 +26,9 @@ public: // Process component changes and create/update bodies void syncBodies(); + + // Update SceneNodes from physics for dynamic bodies + void updateDynamicBodies(); // Enable/disable debug drawing void setDebugDraw(bool enable); diff --git a/src/features/editScene/systems/SceneSerializer.cpp b/src/features/editScene/systems/SceneSerializer.cpp index 759de39..6845866 100644 --- a/src/features/editScene/systems/SceneSerializer.cpp +++ b/src/features/editScene/systems/SceneSerializer.cpp @@ -271,6 +271,7 @@ nlohmann::json SceneSerializer::serializeRigidBody(flecs::entity entity) json["friction"] = rb.friction; json["restitution"] = rb.restitution; json["isSensor"] = rb.isSensor; + json["enabled"] = rb.enabled; return json; } @@ -510,6 +511,7 @@ void SceneSerializer::deserializeRigidBody(flecs::entity entity, const nlohmann: rb.friction = json.value("friction", 0.5f); rb.restitution = json.value("restitution", 0.0f); rb.isSensor = json.value("isSensor", false); + rb.enabled = json.value("enabled", true); // Mark as dirty so physics system will create the body rb.bodyDirty = true; diff --git a/src/features/editScene/ui/RigidBodyEditor.cpp b/src/features/editScene/ui/RigidBodyEditor.cpp index c8e5466..25c6e3e 100644 --- a/src/features/editScene/ui/RigidBodyEditor.cpp +++ b/src/features/editScene/ui/RigidBodyEditor.cpp @@ -10,6 +10,19 @@ bool RigidBodyEditor::renderComponent(flecs::entity entity, ImGuiTreeNodeFlags_DefaultOpen)) { ImGui::Indent(); + // Enabled checkbox - controls if body is in physics world + if (ImGui::Checkbox("Enabled", &rigidBody.enabled)) { + rigidBody.markDirty(); + modified = true; + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip( + "If unchecked, the body is removed from the physics world. " + "When checked again, it will be re-added at the current position."); + } + + ImGui::Separator(); + // Body type selector const char *bodyTypes[] = { "Static", "Dynamic", "Kinematic" }; int currentType = static_cast(rigidBody.bodyType);