diff --git a/src/features/editScene/systems/EditorUISystem.cpp b/src/features/editScene/systems/EditorUISystem.cpp index 089f21f..ba8e11b 100644 --- a/src/features/editScene/systems/EditorUISystem.cpp +++ b/src/features/editScene/systems/EditorUISystem.cpp @@ -103,7 +103,8 @@ bool EditorUISystem::onMousePressed(const Ogre::Ray &mouseRay) if (physics->raycastQuery(start, end, hitPos, hitBody)) { m_cursor3D->setPosition(hitPos); - m_cursorPlaceMode = false; // disable after placement + m_cursorPlaceMode = + false; // disable after placement return true; } } @@ -337,13 +338,13 @@ void EditorUISystem::renderHierarchyWindow() ImGui::EndMenu(); } - // Tools menu - if (ImGui::BeginMenu("Tools")) { - if (ImGui::MenuItem("3D Cursor")) { - m_showCursorPanel = true; - } - ImGui::EndMenu(); + // Tools menu + if (ImGui::BeginMenu("Tools")) { + if (ImGui::MenuItem("3D Cursor")) { + m_showCursorPanel = true; } + ImGui::EndMenu(); + } // Entity menu if (ImGui::BeginMenu("Entity")) { @@ -382,6 +383,23 @@ void EditorUISystem::renderHierarchyWindow() "When enabled, child entities' SceneNodes are parented to their parent's SceneNode, inheriting transforms. When disabled, SceneNodes are created at root level."); } + // Physics stepping toggle + if (m_physicsSystem) { + bool physicsEnabled = + m_physicsSystem + ->isPhysicsEnabled(); + if (ImGui::Checkbox("Physics Stepping", + &physicsEnabled)) { + m_physicsSystem + ->setPhysicsEnabled( + physicsEnabled); + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip( + "Enable/disable physics simulation stepping. Disable to edit prefabs with physics (e.g. characters) without them falling or moving."); + } + } + // Physics debug draw toggle if (m_physicsSystem) { bool debugDraw = @@ -1552,8 +1570,7 @@ void EditorUISystem::showCreatePrefabDialog(flecs::entity entity) m_prefabNameBuffer[0] != '\0') { std::string prefabPath = PrefabSystem::getPrefabsDirectory() + - "/" + m_prefabNameBuffer + - ".json"; + "/" + m_prefabNameBuffer + ".json"; PrefabSystem prefabSys(m_world, m_sceneMgr); if (prefabSys.savePrefab(entity, prefabPath)) { // Convert source entity to prefab instance @@ -1583,10 +1600,9 @@ void EditorUISystem::renderPrefabBrowser() if (!m_showPrefabBrowser) return; - ImGui::SetNextWindowPos( - ImVec2(LEFT_PANEL_WIDTH, 300), ImGuiCond_FirstUseEver); - ImGui::SetNextWindowSize(ImVec2(250, 400), - ImGuiCond_FirstUseEver); + ImGui::SetNextWindowPos(ImVec2(LEFT_PANEL_WIDTH, 300), + ImGuiCond_FirstUseEver); + ImGui::SetNextWindowSize(ImVec2(250, 400), ImGuiCond_FirstUseEver); ImGuiWindowFlags flags = ImGuiWindowFlags_NoCollapse; if (ImGui::Begin("Prefab Browser", &m_showPrefabBrowser, flags)) { @@ -1603,7 +1619,8 @@ void EditorUISystem::renderPrefabBrowser() entry.path().extension() == ".json") { m_prefabFiles.push_back( - entry.path().filename() + entry.path() + .filename() .string()); } } @@ -1636,11 +1653,10 @@ void EditorUISystem::renderPrefabBrowser() std::string editId = "Edit##" + file; if (ImGui::Button(editId.c_str())) { - SceneSerializer serializer(m_world, - m_sceneMgr); + SceneSerializer serializer(m_world, m_sceneMgr); flecs::entity prefabRoot = - serializer.loadPrefabForEdit( - path, this); + serializer.loadPrefabForEdit(path, + this); if (prefabRoot.is_alive()) { setSelectedEntity(prefabRoot); } @@ -1649,10 +1665,8 @@ void EditorUISystem::renderPrefabBrowser() std::string instId = "Inst##" + file; if (ImGui::Button(instId.c_str())) { - PrefabSystem prefabSys(m_world, - m_sceneMgr); - flecs::entity parent = - flecs::entity::null(); + PrefabSystem prefabSys(m_world, m_sceneMgr); + flecs::entity parent = flecs::entity::null(); Ogre::Vector3 pos(0, 0, 0); if (m_prefabUseCursor && m_cursor3D && @@ -1669,22 +1683,25 @@ void EditorUISystem::renderPrefabBrowser() ->getPhysicsWrapper(); if (physics) { Ogre::Vector3 start = - pos + Ogre::Vector3( - 0, - m_prefabRaycastMargin, - 0); + pos + + Ogre::Vector3( + 0, + m_prefabRaycastMargin, + 0); Ogre::Vector3 end = - pos + Ogre::Vector3( - 0, - -1000.0f, - 0); + pos + + Ogre::Vector3( + 0, + -1000.0f, + 0); Ogre::Vector3 hitPos; JPH::BodyID hitBody; if (physics->raycastQuery( start, end, hitPos, hitBody)) { - pos = hitPos + Ogre::Vector3( + pos = hitPos + + Ogre::Vector3( 0, m_prefabRaycastMargin, 0); @@ -1695,8 +1712,8 @@ void EditorUISystem::renderPrefabBrowser() // If not at root, parent under selected entity if (!m_prefabInstAtRoot && m_selectedEntity.is_alive() && - m_selectedEntity.has< - TransformComponent>()) { + m_selectedEntity + .has()) { parent = m_selectedEntity; // Convert world pos to local auto &pt = m_selectedEntity.get< @@ -1708,8 +1725,8 @@ void EditorUISystem::renderPrefabBrowser() } } else if (!m_prefabInstAtRoot && m_selectedEntity.is_alive() && - m_selectedEntity.has< - TransformComponent>()) { + m_selectedEntity + .has()) { parent = m_selectedEntity; // Local offset from parent pos = Ogre::Vector3(2, 0, 0); @@ -1737,10 +1754,9 @@ void EditorUISystem::renderCursorPanel() if (!m_cursor3D) return; - ImGui::SetNextWindowPos( - ImVec2(LEFT_PANEL_WIDTH, 100), ImGuiCond_FirstUseEver); - ImGui::SetNextWindowSize(ImVec2(280, 350), - ImGuiCond_FirstUseEver); + ImGui::SetNextWindowPos(ImVec2(LEFT_PANEL_WIDTH, 100), + ImGuiCond_FirstUseEver); + ImGui::SetNextWindowSize(ImVec2(280, 350), ImGuiCond_FirstUseEver); ImGuiWindowFlags flags = ImGuiWindowFlags_NoCollapse; if (ImGui::Begin("3D Cursor", &m_showCursorPanel, flags)) { @@ -1767,8 +1783,8 @@ void EditorUISystem::renderCursorPanel() Ogre::Vector3 pos = m_cursor3D->getPosition(); float posArr[3] = { pos.x, pos.y, pos.z }; if (ImGui::DragFloat3("Position", posArr, 0.01f)) { - m_cursor3D->setPosition(Ogre::Vector3( - posArr[0], posArr[1], posArr[2])); + m_cursor3D->setPosition( + Ogre::Vector3(posArr[0], posArr[1], posArr[2])); } // Rotation editor (Euler angles) @@ -1800,7 +1816,8 @@ void EditorUISystem::renderCursorPanel() if (m_selectedEntity.is_alive() && m_selectedEntity.has()) { m_cursor3D->snapToTransform( - m_selectedEntity.get()); + m_selectedEntity + .get()); } } if (ImGui::IsItemHovered()) { @@ -1815,12 +1832,14 @@ void EditorUISystem::renderCursorPanel() if (m_selectedEntity.is_alive() && m_selectedEntity.has()) { auto &transform = - m_selectedEntity.get_mut(); + m_selectedEntity + .get_mut(); m_cursor3D->applyToTransform(transform); if (m_selectedEntity.has< StaticGeometryMemberComponent>()) { - m_selectedEntity.get_mut< - StaticGeometryMemberComponent>() + m_selectedEntity + .get_mut< + StaticGeometryMemberComponent>() .markDirty(); } } diff --git a/src/features/editScene/systems/PhysicsSystem.cpp b/src/features/editScene/systems/PhysicsSystem.cpp index 65fb596..55f529e 100644 --- a/src/features/editScene/systems/PhysicsSystem.cpp +++ b/src/features/editScene/systems/PhysicsSystem.cpp @@ -2,11 +2,12 @@ #include "../components/Transform.hpp" #include -EditorPhysicsSystem::EditorPhysicsSystem(flecs::world& world, - Ogre::SceneManager* sceneMgr) +EditorPhysicsSystem::EditorPhysicsSystem(flecs::world &world, + Ogre::SceneManager *sceneMgr) : m_world(world) , m_sceneMgr(sceneMgr) - , m_rigidBodyQuery(world.query()) + , m_rigidBodyQuery( + world.query()) { } @@ -14,38 +15,47 @@ EditorPhysicsSystem::~EditorPhysicsSystem() = default; void EditorPhysicsSystem::initialize() { - if (m_initialized) return; + if (m_initialized) + return; // Create physics wrapper - Ogre::SceneNode* cameraNode = m_sceneMgr->getRootSceneNode()->createChildSceneNode("PhysicsCameraNode"); - m_physics = std::make_unique(m_sceneMgr, cameraNode); - + Ogre::SceneNode *cameraNode = + m_sceneMgr->getRootSceneNode()->createChildSceneNode( + "PhysicsCameraNode"); + m_physics = + std::make_unique(m_sceneMgr, cameraNode); + m_initialized = true; - Ogre::LogManager::getSingleton().logMessage("Physics system initialized"); + Ogre::LogManager::getSingleton().logMessage( + "Physics system initialized"); } void EditorPhysicsSystem::update(float deltaTime) { - if (!m_initialized || !m_physics) return; + if (!m_initialized || !m_physics) + return; // Sync bodies before simulation syncBodies(); - // Step physics - m_physics->update(deltaTime); - + // Step physics (skip if physics stepping is disabled) + if (m_physicsEnabled) { + m_physics->update(deltaTime); + } + // Update SceneNodes from physics for dynamic bodies updateDynamicBodies(); } void EditorPhysicsSystem::syncBodies() { - if (!m_initialized || !m_physics) return; + if (!m_initialized || !m_physics) + return; // Process all rigid bodies m_rigidBodyQuery.each([&](flecs::entity entity, - RigidBodyComponent& rigidBody, - TransformComponent& transform) { + RigidBodyComponent &rigidBody, + TransformComponent &transform) { // Handle enabled/disabled state if (!rigidBody.enabled) { // Body is disabled - remove from physics if it exists @@ -54,7 +64,7 @@ void EditorPhysicsSystem::syncBodies() } return; // Skip rest of processing for disabled bodies } - + // Body is enabled if (rigidBody.bodyDirty || !rigidBody.bodyCreated) { // Create or recreate the body @@ -63,30 +73,30 @@ void EditorPhysicsSystem::syncBodies() } 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; + 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; } } }); @@ -94,24 +104,29 @@ void EditorPhysicsSystem::syncBodies() void EditorPhysicsSystem::updateDynamicBodies() { - if (!m_initialized || !m_physics) return; + 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) + RigidBodyComponent &rigidBody, + TransformComponent &transform) { + if (!rigidBody.enabled || !rigidBody.bodyCreated || + !transform.node) return; - - if (rigidBody.bodyType == RigidBodyComponent::BodyType::Dynamic) { + + 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); - + 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; @@ -119,7 +134,8 @@ void EditorPhysicsSystem::updateDynamicBodies() }); } -JPH::ShapeRefC EditorPhysicsSystem::createShape(PhysicsColliderComponent& collider) +JPH::ShapeRefC +EditorPhysicsSystem::createShape(PhysicsColliderComponent &collider) { if (!collider.shapeDirty && collider.shape) { return collider.shape; @@ -138,29 +154,33 @@ JPH::ShapeRefC EditorPhysicsSystem::createShape(PhysicsColliderComponent& collid } case PhysicsColliderComponent::ShapeType::Capsule: { result = m_physics->createCapsuleShape(collider.halfHeight, - collider.radius); + collider.radius); break; } case PhysicsColliderComponent::ShapeType::Cylinder: { result = m_physics->createCylinderShape(collider.halfHeight, - collider.radius); + collider.radius); break; } case PhysicsColliderComponent::ShapeType::Mesh: { if (!collider.meshName.empty()) { try { - result = m_physics->createMeshShape(collider.meshName); + result = m_physics->createMeshShape( + collider.meshName); if (!result) { Ogre::LogManager::getSingleton().logMessage( - "Mesh shape creation returned null for: " + collider.meshName); + "Mesh shape creation returned null for: " + + collider.meshName); } - } catch (const Ogre::Exception& e) { + } catch (const Ogre::Exception &e) { Ogre::LogManager::getSingleton().logMessage( - "Exception creating mesh shape: " + collider.meshName + - " - " + e.getDescription()); + "Exception creating mesh shape: " + + collider.meshName + " - " + + e.getDescription()); } catch (...) { Ogre::LogManager::getSingleton().logMessage( - "Unknown exception creating mesh shape: " + collider.meshName); + "Unknown exception creating mesh shape: " + + collider.meshName); } } break; @@ -168,7 +188,8 @@ JPH::ShapeRefC EditorPhysicsSystem::createShape(PhysicsColliderComponent& collid case PhysicsColliderComponent::ShapeType::ConvexHull: { if (!collider.meshName.empty()) { try { - result = m_physics->createConvexHullShape(collider.meshName); + result = m_physics->createConvexHullShape( + collider.meshName); } catch (...) { Ogre::LogManager::getSingleton().logMessage( "Failed to create convex hull shape: " + @@ -181,7 +202,8 @@ JPH::ShapeRefC EditorPhysicsSystem::createShape(PhysicsColliderComponent& collid // If no shape created, default to a small box if (!result) { - result = m_physics->createBoxShape(Ogre::Vector3(0.1f, 0.1f, 0.1f)); + result = m_physics->createBoxShape( + Ogre::Vector3(0.1f, 0.1f, 0.1f)); } // Apply offset if any @@ -196,7 +218,8 @@ JPH::ShapeRefC EditorPhysicsSystem::createShape(PhysicsColliderComponent& collid return result; } -JPH::ShapeRefC EditorPhysicsSystem::buildCompoundShape(flecs::entity rigidBodyEntity) +JPH::ShapeRefC +EditorPhysicsSystem::buildCompoundShape(flecs::entity rigidBodyEntity) { std::vector shapes; std::vector positions; @@ -205,8 +228,9 @@ JPH::ShapeRefC EditorPhysicsSystem::buildCompoundShape(flecs::entity rigidBodyEn // Check if the rigid body entity itself has a collider if (rigidBodyEntity.has() && rigidBodyEntity.has()) { - auto& collider = rigidBodyEntity.get_mut(); - auto& transform = rigidBodyEntity.get(); + auto &collider = + rigidBodyEntity.get_mut(); + auto &transform = rigidBodyEntity.get(); JPH::ShapeRefC shape = createShape(collider); if (shape) { shapes.push_back(shape); @@ -219,8 +243,9 @@ JPH::ShapeRefC EditorPhysicsSystem::buildCompoundShape(flecs::entity rigidBodyEn rigidBodyEntity.children([&](flecs::entity child) { if (child.has() && child.has()) { - auto& collider = child.get_mut(); - auto& transform = child.get(); + auto &collider = + child.get_mut(); + auto &transform = child.get(); JPH::ShapeRefC shape = createShape(collider); if (shape) { @@ -233,23 +258,24 @@ JPH::ShapeRefC EditorPhysicsSystem::buildCompoundShape(flecs::entity rigidBodyEn if (shapes.empty()) { // No colliders, use default box - return m_physics->createBoxShape(Ogre::Vector3(0.5f, 0.5f, 0.5f)); + return m_physics->createBoxShape( + Ogre::Vector3(0.5f, 0.5f, 0.5f)); } if (shapes.size() == 1) { // Single shape - use rotated translated shape - return m_physics->createRotatedTranslatedShape(positions[0], - rotations[0], - shapes[0]); + return m_physics->createRotatedTranslatedShape( + positions[0], rotations[0], shapes[0]); } // Multiple shapes - create static compound - return m_physics->createStaticCompoundShape(shapes, positions, rotations); + return m_physics->createStaticCompoundShape(shapes, positions, + rotations); } void EditorPhysicsSystem::updateRigidBody(flecs::entity entity, - RigidBodyComponent& rigidBody, - TransformComponent& transform) + RigidBodyComponent &rigidBody, + TransformComponent &transform) { // Remove existing body if any if (rigidBody.bodyCreated) { @@ -259,35 +285,43 @@ void EditorPhysicsSystem::updateRigidBody(flecs::entity entity, // Check for mesh colliders - they only work with Static bodies bool hasMeshCollider = false; if (entity.has()) { - auto& collider = entity.get(); - if (collider.shapeType == PhysicsColliderComponent::ShapeType::Mesh) { + auto &collider = entity.get(); + if (collider.shapeType == + PhysicsColliderComponent::ShapeType::Mesh) { hasMeshCollider = true; } } if (!hasMeshCollider) { entity.children([&](flecs::entity child) { if (child.has()) { - auto& collider = child.get(); - if (collider.shapeType == PhysicsColliderComponent::ShapeType::Mesh) { + auto &collider = + child.get(); + if (collider.shapeType == + PhysicsColliderComponent::ShapeType::Mesh) { hasMeshCollider = true; } } }); } - - if (hasMeshCollider && rigidBody.bodyType != RigidBodyComponent::BodyType::Static) { + + if (hasMeshCollider && + rigidBody.bodyType != RigidBodyComponent::BodyType::Static) { Ogre::LogManager::getSingleton().logMessage( "ERROR: Mesh colliders can only be used with Static bodies. " - "Body type is " + - std::string(rigidBody.bodyType == RigidBodyComponent::BodyType::Dynamic ? "Dynamic" : "Kinematic") + + "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; @@ -296,7 +330,8 @@ void EditorPhysicsSystem::updateRigidBody(flecs::entity entity, // Build collision shape JPH::ShapeRefC shape = buildCompoundShape(entity); - if (!shape) return; + if (!shape) + return; // Determine motion type and layer JPH::EMotionType motionType; @@ -320,22 +355,24 @@ void EditorPhysicsSystem::updateRigidBody(flecs::entity entity, // 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; + 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, startPosition, startRotation, - motionType, layer); + rigidBody.bodyID = m_physics->createSensor( + shape, startPosition, startRotation, motionType, layer); } else { rigidBody.bodyID = m_physics->createBody(shape, rigidBody.mass, - startPosition, startRotation, - motionType, layer); + startPosition, + startRotation, + motionType, layer); } - if (rigidBody.bodyID.IsInvalid()) { Ogre::LogManager::getSingleton().logMessage( "Failed to create rigid body"); @@ -344,7 +381,7 @@ void EditorPhysicsSystem::updateRigidBody(flecs::entity entity, // Set properties m_physics->setFriction(rigidBody.bodyID, rigidBody.friction); - + // Add to physics world m_physics->addBody(rigidBody.bodyID, JPH::EActivation::Activate); @@ -354,9 +391,10 @@ void EditorPhysicsSystem::updateRigidBody(flecs::entity entity, Ogre::LogManager::getSingleton().logMessage("Rigid body created"); } -void EditorPhysicsSystem::removeRigidBody(RigidBodyComponent& rigidBody) +void EditorPhysicsSystem::removeRigidBody(RigidBodyComponent &rigidBody) { - if (!rigidBody.bodyCreated || rigidBody.bodyID.IsInvalid()) return; + if (!rigidBody.bodyCreated || rigidBody.bodyID.IsInvalid()) + return; m_physics->removeBody(rigidBody.bodyID); m_physics->destroyBody(rigidBody.bodyID); diff --git a/src/features/editScene/systems/PhysicsSystem.hpp b/src/features/editScene/systems/PhysicsSystem.hpp index 538f57b..8752cf7 100644 --- a/src/features/editScene/systems/PhysicsSystem.hpp +++ b/src/features/editScene/systems/PhysicsSystem.hpp @@ -30,6 +30,16 @@ public: // Update SceneNodes from physics for dynamic bodies void updateDynamicBodies(); + // Enable/disable physics stepping + void setPhysicsEnabled(bool enable) + { + m_physicsEnabled = enable; + } + bool isPhysicsEnabled() const + { + return m_physicsEnabled; + } + // Enable/disable debug drawing void setDebugDraw(bool enable); bool isDebugDrawEnabled() const @@ -69,6 +79,7 @@ private: std::unique_ptr m_physics; bool m_initialized = false; bool m_debugDraw = false; + bool m_physicsEnabled = true; // Query for entities with RigidBody flecs::query m_rigidBodyQuery;