From eec0d8f6f76e792ecdd1d976c5bd4a6064599d5e Mon Sep 17 00:00:00 2001 From: Sergey Lapin Date: Wed, 15 Apr 2026 23:49:24 +0300 Subject: [PATCH] Physics works --- .../editScene/systems/CellGridSystem.cpp | 373 +++++++++++++++--- .../editScene/systems/CellGridSystem.hpp | 26 ++ .../editScene/systems/PhysicsSystem.cpp | 36 +- .../systems/ProceduralMaterialSystem.cpp | 14 + 4 files changed, 388 insertions(+), 61 deletions(-) diff --git a/src/features/editScene/systems/CellGridSystem.cpp b/src/features/editScene/systems/CellGridSystem.cpp index 3d3647f..0603f0b 100644 --- a/src/features/editScene/systems/CellGridSystem.cpp +++ b/src/features/editScene/systems/CellGridSystem.cpp @@ -5,6 +5,8 @@ #include "../components/Transform.hpp" #include "../components/StaticGeometry.hpp" #include "../components/StaticGeometryMember.hpp" +#include "../components/RigidBody.hpp" +#include "../components/PhysicsCollider.hpp" #include #include "../components/ProceduralMaterial.hpp" #include "../components/ProceduralTexture.hpp" @@ -46,6 +48,12 @@ void CellGridSystem::initialize() m_initialized = true; Ogre::LogManager::getSingleton().logMessage( "CellGridSystem initialized"); + + /* + * NOTE: Observers removed because flecs observer registration shifts + * entity IDs, which breaks SceneSerializer's hardcoded ID resolution + * for proceduralMaterialEntityId in Town/District/Lot components. + */ } void CellGridSystem::update() @@ -83,6 +91,7 @@ void CellGridSystem::update() // First check stored texture entity auto it = m_entityMeshes.find(entity.id()); if (it != m_entityMeshes.end() && + it->second.textureEntity.is_valid() && it->second.textureEntity.is_alive() && it->second.textureEntity.has()) { currentTextureEntity = it->second.textureEntity; @@ -90,9 +99,9 @@ void CellGridSystem::update() } // If no stored texture, try to find from parent material - if (!currentTextureEntity.is_alive()) { + if (!currentTextureEntity.is_valid()) { flecs::entity parent = entity.parent(); - while (parent.is_alive()) { + while (parent.is_valid() && parent.is_alive()) { flecs::entity matEntity = flecs::entity::null(); if (parent.has()) { matEntity = parent.get().proceduralMaterialEntity; @@ -102,9 +111,11 @@ void CellGridSystem::update() matEntity = parent.get().proceduralMaterialEntity; } - if (matEntity.is_alive() && matEntity.has()) { + if (matEntity.is_valid() && matEntity.is_valid() && matEntity.is_alive() && + matEntity.has()) { const auto &mat = matEntity.get(); - if (mat.diffuseTextureEntity.is_alive() && + if (mat.diffuseTextureEntity.is_valid() && + mat.diffuseTextureEntity.is_valid() && mat.diffuseTextureEntity.is_alive() && mat.diffuseTextureEntity.has()) { currentTextureEntity = mat.diffuseTextureEntity; const auto &tex = mat.diffuseTextureEntity.get(); @@ -117,7 +128,7 @@ void CellGridSystem::update() } bool textureChanged = false; - if (currentTextureEntity.is_alive()) { + if (currentTextureEntity.is_valid() && currentTextureEntity.is_alive()) { const auto &tex = currentTextureEntity.get(); if (tex.version != currentTextureVersion) { textureChanged = true; @@ -171,6 +182,7 @@ void CellGridSystem::update() if (!needsRebuild) { auto it = m_plazaMeshes.find(entity.id()); if (it != m_plazaMeshes.end() && + it->second.textureEntity.is_valid() && it->second.textureEntity.is_alive() && it->second.textureEntity.has()) { const auto &tex = it->second.textureEntity.get(); @@ -207,6 +219,7 @@ void CellGridSystem::update() if (!needsRebuild) { auto it = m_lotBaseMeshes.find(entity.id()); if (it != m_lotBaseMeshes.end() && + it->second.textureEntity.is_valid() && it->second.textureEntity.is_alive() && it->second.textureEntity.has()) { const auto &tex = it->second.textureEntity.get(); @@ -221,6 +234,15 @@ void CellGridSystem::update() lot.dirty = false; } }); + + // Process any pending physics builds outside of query iteration + for (auto pendingEntity : m_pendingPhysicsBuilds) { + if (pendingEntity.is_valid() && pendingEntity.is_alive()) { + destroyPhysicsColliders(pendingEntity); + buildPhysicsColliders(pendingEntity); + } + } + m_pendingPhysicsBuilds.clear(); } void CellGridSystem::buildCellGrid(flecs::entity entity, @@ -235,6 +257,23 @@ void CellGridSystem::buildCellGrid(flecs::entity entity, // Destroy existing meshes destroyCellGridMeshes(entity); + + // Create or recreate StaticGeometry region early (used for frames, furniture, roofs) + std::string regionName = "FrameRegion_" + std::to_string(entity.id()); + Ogre::StaticGeometry *staticGeom = m_sceneMgr->createStaticGeometry(regionName); + staticGeom->setCastShadows(true); + staticGeom->setRegionDimensions(Ogre::Vector3(1000.0f)); + + MeshData &meshData = m_entityMeshes[entity.id()]; + meshData.frameStaticGeometry = staticGeom; + meshData.framePlacements.clear(); + meshData.furniturePlacements.clear(); + for (auto furnEntity : meshData.isolatedFurnitureEntities) { + if (furnEntity.is_valid() && furnEntity.is_alive()) { + furnEntity.destruct(); + } + } + meshData.isolatedFurnitureEntities.clear(); // Find material - prefer ProceduralMaterial from Town/District/Lot std::string materialName = "Ogre/StandardFloor"; // default @@ -243,16 +282,16 @@ void CellGridSystem::buildCellGrid(flecs::entity entity, // Look for ProceduralMaterial in parent hierarchy (Lot -> District -> Town) flecs::entity parent = entity.parent(); - while (parent.is_alive()) { + while (parent.is_valid() && parent.is_alive()) { if (parent.has()) { auto &lot = parent.get(); - if (lot.proceduralMaterialEntity.is_alive() && + if (lot.proceduralMaterialEntity.is_valid() && lot.proceduralMaterialEntity.is_alive() && lot.proceduralMaterialEntity .has()) { const auto &mat = lot.proceduralMaterialEntity.get< ProceduralMaterialComponent>(); - if (mat.created && !mat.materialName.empty()) { + if (!mat.materialName.empty()) { materialName = mat.materialName; materialEntity = lot.proceduralMaterialEntity; @@ -262,13 +301,13 @@ void CellGridSystem::buildCellGrid(flecs::entity entity, } if (parent.has()) { auto &district = parent.get(); - if (district.proceduralMaterialEntity.is_alive() && + if (district.proceduralMaterialEntity.is_valid() && district.proceduralMaterialEntity.is_alive() && district.proceduralMaterialEntity .has()) { const auto &mat = district.proceduralMaterialEntity.get< ProceduralMaterialComponent>(); - if (mat.created && !mat.materialName.empty()) { + if (!mat.materialName.empty()) { materialName = mat.materialName; materialEntity = district.proceduralMaterialEntity; @@ -280,13 +319,13 @@ void CellGridSystem::buildCellGrid(flecs::entity entity, townEntity = parent; auto &town = parent.get(); // Prefer ProceduralMaterial over generated material - if (town.proceduralMaterialEntity.is_alive() && + if (town.proceduralMaterialEntity.is_valid() && town.proceduralMaterialEntity.is_alive() && town.proceduralMaterialEntity .has()) { const auto &mat = town.proceduralMaterialEntity.get< ProceduralMaterialComponent>(); - if (mat.created && !mat.materialName.empty()) { + if (!mat.materialName.empty()) { materialName = mat.materialName; materialEntity = town.proceduralMaterialEntity; @@ -308,8 +347,6 @@ void CellGridSystem::buildCellGrid(flecs::entity entity, "CellGrid: Using material '" + materialName + "' for entity " + std::to_string(entity.id())); - MeshData &meshData = m_entityMeshes[entity.id()]; - // Build geometry parts Procedural::TriangleBuffer floorTb, ceilingTb, extWallTb, intWallTb, extWindowsTb, intWindowsTb, extDoorsTb, intDoorsTb, roofTopTb, roofSideTb; @@ -408,7 +445,7 @@ void CellGridSystem::buildCellGrid(flecs::entity entity, meshData.entities.push_back(entity3d); } - // Roof top pieces + // Roof top pieces -> attach to SceneNode if (roofTopTb.getVertices().size() >= 3) { meshData.roofMesh = baseName + "_roofTop"; auto mesh = @@ -418,7 +455,7 @@ void CellGridSystem::buildCellGrid(flecs::entity entity, meshData.entities.push_back(entity3d); } - // Roof side pieces (trim/gable ends) + // Roof side pieces (trim/gable ends) -> attach to SceneNode if (roofSideTb.getVertices().size() >= 3) { meshData.roofSideMesh = baseName + "_roofSide"; auto mesh = @@ -512,18 +549,24 @@ void CellGridSystem::buildCellGrid(flecs::entity entity, "CellGrid: Unknown error building furniture"); } - // Build combined static geometry (frames + batched furniture) + // Build combined static geometry (frames + furniture + roofs) auto sgIt = m_entityMeshes.find(entity.id()); if (sgIt != m_entityMeshes.end() && sgIt->second.frameStaticGeometry) { sgIt->second.frameStaticGeometry->build(); } + // Queue physics build to happen outside query iteration + if (std::find(m_pendingPhysicsBuilds.begin(), m_pendingPhysicsBuilds.end(), entity) == + m_pendingPhysicsBuilds.end()) { + m_pendingPhysicsBuilds.push_back(entity); + } + // Store texture dependency for automatic rebuild when texture changes // Get texture entity from material entity - if (materialEntity.is_alive() && + if (materialEntity.is_valid() && materialEntity.is_alive() && materialEntity.has()) { const auto &mat = materialEntity.get(); - if (mat.diffuseTextureEntity.is_alive() && + if (mat.diffuseTextureEntity.is_valid() && mat.diffuseTextureEntity.is_alive() && mat.diffuseTextureEntity.has()) { auto &meshData = m_entityMeshes[entity.id()]; meshData.textureEntity = mat.diffuseTextureEntity; @@ -2365,6 +2408,8 @@ void CellGridSystem::buildFurniture(flecs::entity entity, staticGeom->addEntity(tempEnt, worldPos, worldRot, parentScale); m_sceneMgr->destroyEntity(tempEnt); + meshData.furniturePlacements.push_back( + {def->meshName, worldPos, worldRot}); batchedCount++; } else { Ogre::Entity *ent = @@ -2385,6 +2430,18 @@ void CellGridSystem::buildFurniture(flecs::entity entity, } meshData.furnitureEntities.push_back(ent); + + // Create isolated furniture physics body + flecs::entity furnEntity = m_world.entity(); + furnEntity.child_of(entity); + furnEntity.set( + {fnode, pos, localRot, Ogre::Vector3::UNIT_SCALE}); + furnEntity.set(RigidBodyComponent()); + PhysicsColliderComponent collider; + collider.shapeType = PhysicsColliderComponent::ShapeType::Mesh; + collider.meshName = def->meshName; + furnEntity.set(collider); + meshData.isolatedFurnitureEntities.push_back(furnEntity); } } @@ -2416,6 +2473,15 @@ void CellGridSystem::destroyFurniture(flecs::entity entity) } } it->second.furnitureEntities.clear(); + + // Destroy isolated furniture physics entities + for (auto furnEntity : it->second.isolatedFurnitureEntities) { + if (furnEntity.is_valid() && furnEntity.is_alive()) { + furnEntity.destruct(); + } + } + it->second.isolatedFurnitureEntities.clear(); + it->second.furniturePlacements.clear(); } void CellGridSystem::generateLodForMesh(Ogre::MeshPtr mesh) @@ -2495,6 +2561,9 @@ void CellGridSystem::destroyCellGridMeshes(flecs::entity entity) // Destroy frames destroyFrames(entity); + + // Destroy physics colliders + destroyPhysicsColliders(entity); m_entityMeshes.erase(it); } @@ -2544,6 +2613,172 @@ void CellGridSystem::rebuildCellGrid(flecs::entity entity) entity.get_mut().markDirty(); } +void CellGridSystem::addPhysicsCollider(flecs::entity physicsParent, + const std::string &meshName, + const Ogre::Vector3 &position, + const Ogre::Quaternion &rotation) +{ + flecs::entity colliderEntity = m_world.entity(); + colliderEntity.child_of(physicsParent); + colliderEntity.set({nullptr, position, rotation, + Ogre::Vector3::UNIT_SCALE}); + PhysicsColliderComponent collider; + collider.shapeType = PhysicsColliderComponent::ShapeType::Mesh; + collider.meshName = meshName; + colliderEntity.set(collider); +} + +void CellGridSystem::buildPhysicsColliders(flecs::entity entity) +{ + + // Helper to get world transform from an entity + auto getWorldTransform = [&](flecs::entity e, + Ogre::Vector3 &pos, + Ogre::Quaternion &rot, + Ogre::Vector3 &scale) { + if (e.is_valid() && e.is_alive() && e.has()) { + auto &t = e.get(); + if (t.node) { + pos = t.node->_getDerivedPosition(); + rot = t.node->_getDerivedOrientation(); + scale = t.node->_getDerivedScale(); + } else { + pos = t.position; + rot = t.rotation; + scale = t.scale; + } + } else { + pos = Ogre::Vector3::ZERO; + rot = Ogre::Quaternion::IDENTITY; + scale = Ogre::Vector3::UNIT_SCALE; + } + }; + + // Cell grid meshes, frames, and furniture + auto it = m_entityMeshes.find(entity.id()); + if (it != m_entityMeshes.end()) { + auto &meshData = it->second; + + Ogre::Vector3 parentPos; + Ogre::Quaternion parentRot; + Ogre::Vector3 parentScale; + getWorldTransform(entity, parentPos, parentRot, parentScale); + + // Create a dedicated physics parent entity at world transform + flecs::entity physicsParent = m_world.entity(); + physicsParent.child_of(entity); + physicsParent.set({nullptr, parentPos, parentRot, + parentScale}); + physicsParent.set(RigidBodyComponent()); + meshData.physicsParentEntity = physicsParent; + + // Add colliders for main cell grid meshes (local to parent) + auto addMeshCollider = [&](const std::string &meshName) { + if (!meshName.empty()) { + addPhysicsCollider(physicsParent, meshName, + Ogre::Vector3::ZERO, + Ogre::Quaternion::IDENTITY); + } + }; + addMeshCollider(meshData.floorMesh); + addMeshCollider(meshData.ceilingMesh); + addMeshCollider(meshData.extWallMesh); + addMeshCollider(meshData.intWallMesh); + addMeshCollider(meshData.intWindowsMesh); + addMeshCollider(meshData.roofMesh); + addMeshCollider(meshData.roofSideMesh); + for (const auto &doorMesh : meshData.doorMeshes) + addMeshCollider(doorMesh); + for (const auto &windowMesh : meshData.windowMeshes) + addMeshCollider(windowMesh); + + // Add colliders for frame placements (convert world -> local) + for (const auto &placement : meshData.framePlacements) { + Ogre::Vector3 localPos = + parentRot.Inverse() * + (placement.position - parentPos); + Ogre::Quaternion localRot = + parentRot.Inverse() * placement.rotation; + addPhysicsCollider(physicsParent, placement.meshName, + localPos, localRot); + } + + // Add colliders for batched furniture placements (convert world -> local) + for (const auto &placement : meshData.furniturePlacements) { + Ogre::Vector3 localPos = + parentRot.Inverse() * + (placement.position - parentPos); + Ogre::Quaternion localRot = + parentRot.Inverse() * placement.rotation; + addPhysicsCollider(physicsParent, placement.meshName, + localPos, localRot); + } + } + + // District plaza + auto plazaIt = m_plazaMeshes.find(entity.id()); + if (plazaIt != m_plazaMeshes.end() && !plazaIt->second.meshName.empty()) { + Ogre::Vector3 parentPos; + Ogre::Quaternion parentRot; + Ogre::Vector3 parentScale; + getWorldTransform(entity, parentPos, parentRot, parentScale); + + flecs::entity physicsParent = m_world.entity(); + physicsParent.child_of(entity); + physicsParent.set({nullptr, parentPos, parentRot, + parentScale}); + physicsParent.set(RigidBodyComponent()); + plazaIt->second.physicsParentEntity = physicsParent; + addPhysicsCollider(physicsParent, plazaIt->second.meshName, + Ogre::Vector3::ZERO, Ogre::Quaternion::IDENTITY); + } + + // Lot base + auto lotIt = m_lotBaseMeshes.find(entity.id()); + if (lotIt != m_lotBaseMeshes.end() && !lotIt->second.meshName.empty()) { + Ogre::Vector3 parentPos; + Ogre::Quaternion parentRot; + Ogre::Vector3 parentScale; + getWorldTransform(entity, parentPos, parentRot, parentScale); + + flecs::entity physicsParent = m_world.entity(); + physicsParent.child_of(entity); + physicsParent.set({nullptr, parentPos, parentRot, + parentScale}); + physicsParent.set(RigidBodyComponent()); + lotIt->second.physicsParentEntity = physicsParent; + addPhysicsCollider(physicsParent, lotIt->second.meshName, + Ogre::Vector3::ZERO, Ogre::Quaternion::IDENTITY); + } +} + +void CellGridSystem::destroyPhysicsColliders(flecs::entity entity) +{ + auto it = m_entityMeshes.find(entity.id()); + if (it != m_entityMeshes.end() && + it->second.physicsParentEntity.is_valid() && + it->second.physicsParentEntity.is_alive()) { + it->second.physicsParentEntity.destruct(); + it->second.physicsParentEntity = flecs::entity::null(); + } + + auto plazaIt = m_plazaMeshes.find(entity.id()); + if (plazaIt != m_plazaMeshes.end() && + plazaIt->second.physicsParentEntity.is_valid() && + plazaIt->second.physicsParentEntity.is_alive()) { + plazaIt->second.physicsParentEntity.destruct(); + plazaIt->second.physicsParentEntity = flecs::entity::null(); + } + + auto lotIt = m_lotBaseMeshes.find(entity.id()); + if (lotIt != m_lotBaseMeshes.end() && + lotIt->second.physicsParentEntity.is_valid() && + lotIt->second.physicsParentEntity.is_alive()) { + lotIt->second.physicsParentEntity.destruct(); + lotIt->second.physicsParentEntity = flecs::entity::null(); + } +} + void CellGridSystem::buildDistrictPlaza(flecs::entity entity, DistrictComponent &district) { @@ -2563,6 +2798,7 @@ void CellGridSystem::buildDistrictPlaza(flecs::entity entity, } catch (...) { } } + destroyPhysicsColliders(entity); m_plazaMeshes.erase(it); } @@ -2586,12 +2822,12 @@ void CellGridSystem::buildDistrictPlaza(flecs::entity entity, // Get material from district's procedural material entity std::string materialName = "Ogre/StandardFloor"; // fallback - if (district.proceduralMaterialEntity.is_alive() && + if (district.proceduralMaterialEntity.is_valid() && district.proceduralMaterialEntity.is_alive() && district.proceduralMaterialEntity .has()) { const auto &mat = district.proceduralMaterialEntity .get(); - if (mat.created && !mat.materialName.empty()) { + if (!mat.materialName.empty()) { materialName = mat.materialName; } } @@ -2649,19 +2885,19 @@ void CellGridSystem::buildDistrictPlaza(flecs::entity entity, if (!district.textureRectName.empty()) { // Find the texture entity from the material flecs::entity textureEntity = flecs::entity::null(); - if (district.proceduralMaterialEntity.is_alive() && + if (district.proceduralMaterialEntity.is_valid() && district.proceduralMaterialEntity.is_alive() && district.proceduralMaterialEntity .has()) { const auto &mat = district.proceduralMaterialEntity .get(); // Search for the texture that is referenced by this material - if (mat.diffuseTextureEntity.is_alive()) { + if (mat.diffuseTextureEntity.is_valid() && mat.diffuseTextureEntity.is_alive()) { textureEntity = mat.diffuseTextureEntity; } } - if (textureEntity.is_alive() && + if (textureEntity.is_valid() && textureEntity.is_alive() && textureEntity.has()) { const auto &texture = textureEntity.get(); @@ -2736,10 +2972,10 @@ void CellGridSystem::buildDistrictPlaza(flecs::entity entity, data.meshName = meshName; data.entity = plazzaEntity; // Store texture dependency for automatic rebuild when texture changes - if (district.proceduralMaterialEntity.is_alive() && + if (district.proceduralMaterialEntity.is_valid() && district.proceduralMaterialEntity.is_alive() && district.proceduralMaterialEntity.has()) { const auto &mat = district.proceduralMaterialEntity.get(); - if (mat.diffuseTextureEntity.is_alive() && + if (mat.diffuseTextureEntity.is_valid() && mat.diffuseTextureEntity.is_alive() && mat.diffuseTextureEntity.has()) { data.textureEntity = mat.diffuseTextureEntity; const auto &tex = mat.diffuseTextureEntity.get(); @@ -2748,6 +2984,12 @@ void CellGridSystem::buildDistrictPlaza(flecs::entity entity, } m_plazaMeshes[entity.id()] = data; + // Queue physics rebuild + if (std::find(m_pendingPhysicsBuilds.begin(), m_pendingPhysicsBuilds.end(), entity) == + m_pendingPhysicsBuilds.end()) { + m_pendingPhysicsBuilds.push_back(entity); + } + Ogre::LogManager::getSingleton().logMessage( "CellGrid: Created plaza mesh for district " + std::to_string(entity.id()) + " attached to node '" + @@ -2778,6 +3020,7 @@ void CellGridSystem::buildLotBase(flecs::entity entity, LotComponent &lot) } catch (...) { } } + destroyPhysicsColliders(entity); m_lotBaseMeshes.erase(it); } @@ -2802,11 +3045,11 @@ void CellGridSystem::buildLotBase(flecs::entity entity, LotComponent &lot) // Material hierarchy: Lot -> District -> Town // 1. Check Lot's own material first - if (lot.proceduralMaterialEntity.is_alive() && + if (lot.proceduralMaterialEntity.is_valid() && lot.proceduralMaterialEntity.is_alive() && lot.proceduralMaterialEntity.has()) { const auto &mat = lot.proceduralMaterialEntity .get(); - if (mat.created && !mat.materialName.empty()) { + if (!mat.materialName.empty()) { materialName = mat.materialName; Ogre::LogManager::getSingleton().logMessage( "CellGrid: Using Lot's own material: " + @@ -2816,20 +3059,20 @@ void CellGridSystem::buildLotBase(flecs::entity entity, LotComponent &lot) // 2. Walk up hierarchy for District and Town, and fallback materials flecs::entity parent = entity.parent(); - while (parent.is_alive()) { + while (parent.is_valid() && parent.is_alive()) { if (parent.has()) { districtEntity = parent; auto &district = parent.get(); districtRadius = district.radius; // Use District material if Lot doesn't have one if (materialName == "Ogre/StandardFloor" && - district.proceduralMaterialEntity.is_alive() && + district.proceduralMaterialEntity.is_valid() && district.proceduralMaterialEntity.is_alive() && district.proceduralMaterialEntity .has()) { const auto &mat = district.proceduralMaterialEntity.get< ProceduralMaterialComponent>(); - if (mat.created && !mat.materialName.empty()) { + if (!mat.materialName.empty()) { materialName = mat.materialName; Ogre::LogManager::getSingleton().logMessage( "CellGrid: Using District material: " + @@ -2842,14 +3085,13 @@ void CellGridSystem::buildLotBase(flecs::entity entity, LotComponent &lot) // Check Town's ProceduralMaterial first, then generated material if (materialName == "Ogre/StandardFloor") { auto &town = parent.get(); - if (town.proceduralMaterialEntity.is_alive() && + if (town.proceduralMaterialEntity.is_valid() && town.proceduralMaterialEntity.is_alive() && town.proceduralMaterialEntity.has< ProceduralMaterialComponent>()) { const auto &mat = town.proceduralMaterialEntity.get< ProceduralMaterialComponent>(); - if (mat.created && - !mat.materialName.empty()) { + if (!mat.materialName.empty()) { materialName = mat.materialName; Ogre::LogManager::getSingleton() .logMessage( @@ -2871,7 +3113,7 @@ void CellGridSystem::buildLotBase(flecs::entity entity, LotComponent &lot) parent = parent.parent(); } - if (!districtEntity.is_alive()) { + if (!districtEntity.is_valid() || !districtEntity.is_alive()) { Ogre::LogManager::getSingleton().logMessage( "CellGrid: Lot has no parent District"); return; @@ -2931,7 +3173,7 @@ void CellGridSystem::buildLotBase(flecs::entity entity, LotComponent &lot) flecs::entity materialEntityToUse; // If Lot has its own material, use its texture rect - if (lot.proceduralMaterialEntity.is_alive()) { + if (lot.proceduralMaterialEntity.is_valid() && lot.proceduralMaterialEntity.is_alive()) { textureRectToUse = lot.textureRectName; materialEntityToUse = lot.proceduralMaterialEntity; if (!textureRectToUse.empty()) { @@ -2942,7 +3184,7 @@ void CellGridSystem::buildLotBase(flecs::entity entity, LotComponent &lot) } else { // Lot uses District/Town material - look up hierarchy for texture rect flecs::entity parent = entity.parent(); - while (parent.is_alive()) { + while (parent.is_valid() && parent.is_alive()) { if (parent.has()) { auto &district = parent.get(); @@ -2978,31 +3220,33 @@ void CellGridSystem::buildLotBase(flecs::entity entity, LotComponent &lot) // Apply UV mapping if (!textureRectToUse.empty()) { - if (materialEntityToUse.is_alive() && + if (materialEntityToUse.is_valid() && materialEntityToUse.is_alive() && materialEntityToUse.has()) { const auto &mat = materialEntityToUse .get(); - if (mat.diffuseTextureEntity.is_alive()) { + if (mat.diffuseTextureEntity.is_valid() && mat.diffuseTextureEntity.is_alive()) { textureEntity = mat.diffuseTextureEntity; } } // Fallback to District/Town texture - if (!textureEntity.is_alive()) { + if (!textureEntity.is_valid()) { flecs::entity parent = entity.parent(); - while (parent.is_alive()) { + while (parent.is_valid() && parent.is_alive()) { if (parent.has()) { auto &district = parent.get(); if (district.proceduralMaterialEntity - .is_alive() && + .is_valid() && + district.proceduralMaterialEntity.is_alive() && district.proceduralMaterialEntity.has< ProceduralMaterialComponent>()) { const auto &mat = district.proceduralMaterialEntity .get(); if (mat.diffuseTextureEntity - .is_alive()) { + .is_valid() && + mat.diffuseTextureEntity.is_alive()) { textureEntity = mat.diffuseTextureEntity; break; @@ -3013,14 +3257,16 @@ void CellGridSystem::buildLotBase(flecs::entity entity, LotComponent &lot) auto &town = parent.get(); if (town.proceduralMaterialEntity - .is_alive() && + .is_valid() && + town.proceduralMaterialEntity.is_alive() && town.proceduralMaterialEntity.has< ProceduralMaterialComponent>()) { const auto &mat = town.proceduralMaterialEntity .get(); if (mat.diffuseTextureEntity - .is_alive()) { + .is_valid() && + mat.diffuseTextureEntity.is_alive()) { textureEntity = mat.diffuseTextureEntity; break; @@ -3032,7 +3278,7 @@ void CellGridSystem::buildLotBase(flecs::entity entity, LotComponent &lot) } } - if (textureEntity.is_alive() && + if (textureEntity.is_valid() && textureEntity.is_alive() && textureEntity.has()) { const auto &texture = textureEntity.get(); @@ -3133,7 +3379,7 @@ void CellGridSystem::buildLotBase(flecs::entity entity, LotComponent &lot) data.entity = lotBaseEntity; // Store texture dependency for automatic rebuild when texture changes // textureEntity was determined earlier in the function - if (textureEntity.is_alive() && + if (textureEntity.is_valid() && textureEntity.is_alive() && textureEntity.has()) { data.textureEntity = textureEntity; const auto &tex = textureEntity.get(); @@ -3141,6 +3387,12 @@ void CellGridSystem::buildLotBase(flecs::entity entity, LotComponent &lot) } m_lotBaseMeshes[entity.id()] = data; + // Queue physics rebuild + if (std::find(m_pendingPhysicsBuilds.begin(), m_pendingPhysicsBuilds.end(), entity) == + m_pendingPhysicsBuilds.end()) { + m_pendingPhysicsBuilds.push_back(entity); + } + Ogre::LogManager::getSingleton().logMessage( "CellGrid: Successfully created lot base mesh for entity " + std::to_string(entity.id()) + " with " + @@ -3177,16 +3429,16 @@ void CellGridSystem::applyUVMappingToBuffer(Procedural::TriangleBuffer &tb, // Find texture entity from material flecs::entity textureEntity = flecs::entity::null(); - if (materialEntity.is_alive() && + if (materialEntity.is_valid() && materialEntity.is_alive() && materialEntity.has()) { const auto &mat = materialEntity.get(); - if (mat.diffuseTextureEntity.is_alive()) { + if (mat.diffuseTextureEntity.is_valid() && mat.diffuseTextureEntity.is_alive()) { textureEntity = mat.diffuseTextureEntity; } } - if (!textureEntity.is_alive()) { + if (!textureEntity.is_valid() || !textureEntity.is_alive()) { Ogre::LogManager::getSingleton().logMessage( "CellGrid: WARNING - No texture entity for rect: " + rectName); @@ -3262,17 +3514,17 @@ void CellGridSystem::buildFrames(flecs::entity entity, // Get material entity for UV mapping flecs::entity materialEntity = flecs::entity::null(); flecs::entity parent = entity.parent(); - while (parent.is_alive()) { + while (parent.is_valid() && parent.is_alive()) { if (parent.has()) { auto &lot = parent.get(); - if (lot.proceduralMaterialEntity.is_alive()) { + if (lot.proceduralMaterialEntity.is_valid() && lot.proceduralMaterialEntity.is_alive()) { materialEntity = lot.proceduralMaterialEntity; break; } } if (parent.has()) { auto &district = parent.get(); - if (district.proceduralMaterialEntity.is_alive()) { + if (district.proceduralMaterialEntity.is_valid() && district.proceduralMaterialEntity.is_alive()) { materialEntity = district.proceduralMaterialEntity; break; @@ -3280,7 +3532,7 @@ void CellGridSystem::buildFrames(flecs::entity entity, } if (parent.has()) { auto &town = parent.get(); - if (town.proceduralMaterialEntity.is_alive()) { + if (town.proceduralMaterialEntity.is_valid() && town.proceduralMaterialEntity.is_alive()) { materialEntity = town.proceduralMaterialEntity; break; } @@ -3941,6 +4193,7 @@ void CellGridSystem::destroyFrames(flecs::entity entity) } } it->second.frameEntities.clear(); + it->second.framePlacements.clear(); // Destroy frame meshes auto &meshMgr = Ogre::MeshManager::getSingleton(); @@ -4009,6 +4262,12 @@ void CellGridSystem::placeWindowFramesInStaticGeometry( staticGeom->addEntity(tempEnt, worldPos, worldRot, parentScale); m_sceneMgr->destroyEntity(tempEnt); // StaticGeometry copies the data frameCount++; + + auto it = m_entityMeshes.find(entity.id()); + if (it != m_entityMeshes.end()) { + it->second.framePlacements.push_back( + {mesh->getName(), worldPos, worldRot}); + } }; if (extMesh) { @@ -4099,6 +4358,12 @@ void CellGridSystem::placeDoorFramesInStaticGeometry( staticGeom->addEntity(tempEnt, worldPos, worldRot, parentScale); m_sceneMgr->destroyEntity(tempEnt); // StaticGeometry copies the data frameCount++; + + auto it = m_entityMeshes.find(entity.id()); + if (it != m_entityMeshes.end()) { + it->second.framePlacements.push_back( + {mesh->getName(), worldPos, worldRot}); + } }; for (const auto &cell : grid.cells) { diff --git a/src/features/editScene/systems/CellGridSystem.hpp b/src/features/editScene/systems/CellGridSystem.hpp index 0189011..7e99147 100644 --- a/src/features/editScene/systems/CellGridSystem.hpp +++ b/src/features/editScene/systems/CellGridSystem.hpp @@ -3,6 +3,8 @@ #include #include #include +#include "../components/RigidBody.hpp" +#include "../components/PhysicsCollider.hpp" namespace Procedural { class TriangleBuffer; @@ -38,6 +40,8 @@ private: bool m_initialized = false; int m_meshCount = 0; + std::vector m_pendingPhysicsBuilds; + // Build geometry from cell grid void buildCellGrid(flecs::entity entity, struct CellGridComponent& grid); @@ -100,8 +104,22 @@ private: void destroyCellGridMeshes(struct CellGridComponent& grid); void destroyCellGridMeshes(flecs::entity entity); + // Physics helpers + void addPhysicsCollider(flecs::entity physicsParent, + const std::string& meshName, + const Ogre::Vector3& position, + const Ogre::Quaternion& rotation); + void buildPhysicsColliders(flecs::entity entity); + void destroyPhysicsColliders(flecs::entity entity); + // Mesh storage per entity struct MeshData { + struct ColliderPlacement { + std::string meshName; + Ogre::Vector3 position; + Ogre::Quaternion rotation; + }; + std::string floorMesh; std::string ceilingMesh; std::string extWallMesh; @@ -131,6 +149,12 @@ private: // Furniture entities std::vector furnitureEntities; + + // Physics placement data for compound shape + std::vector framePlacements; + std::vector furniturePlacements; + std::vector isolatedFurnitureEntities; + flecs::entity physicsParentEntity = flecs::entity::null(); }; std::unordered_map m_entityMeshes; @@ -141,6 +165,7 @@ private: // Track texture dependency for automatic rebuild when texture changes flecs::entity textureEntity = flecs::entity::null(); unsigned int textureVersion = 0; + flecs::entity physicsParentEntity = flecs::entity::null(); }; std::unordered_map m_plazaMeshes; @@ -151,6 +176,7 @@ private: // Track texture dependency for automatic rebuild when texture changes flecs::entity textureEntity = flecs::entity::null(); unsigned int textureVersion = 0; + flecs::entity physicsParentEntity = flecs::entity::null(); }; std::unordered_map m_lotBaseMeshes; }; diff --git a/src/features/editScene/systems/PhysicsSystem.cpp b/src/features/editScene/systems/PhysicsSystem.cpp index 89ee2df..65fb596 100644 --- a/src/features/editScene/systems/PhysicsSystem.cpp +++ b/src/features/editScene/systems/PhysicsSystem.cpp @@ -202,6 +202,19 @@ JPH::ShapeRefC EditorPhysicsSystem::buildCompoundShape(flecs::entity rigidBodyEn std::vector positions; std::vector rotations; + // Check if the rigid body entity itself has a collider + if (rigidBodyEntity.has() && + rigidBodyEntity.has()) { + auto& collider = rigidBodyEntity.get_mut(); + auto& transform = rigidBodyEntity.get(); + JPH::ShapeRefC shape = createShape(collider); + if (shape) { + shapes.push_back(shape); + positions.push_back(transform.position); + rotations.push_back(transform.rotation); + } + } + // Collect all collider children rigidBodyEntity.children([&](flecs::entity child) { if (child.has() && @@ -245,14 +258,22 @@ void EditorPhysicsSystem::updateRigidBody(flecs::entity entity, // Check for mesh colliders - they only work with Static bodies bool hasMeshCollider = false; - entity.children([&](flecs::entity child) { - if (child.has()) { - auto& collider = child.get(); - if (collider.shapeType == PhysicsColliderComponent::ShapeType::Mesh) { - hasMeshCollider = true; - } + if (entity.has()) { + 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) { + hasMeshCollider = true; + } + } + }); + } if (hasMeshCollider && rigidBody.bodyType != RigidBodyComponent::BodyType::Static) { Ogre::LogManager::getSingleton().logMessage( @@ -314,6 +335,7 @@ void EditorPhysicsSystem::updateRigidBody(flecs::entity entity, motionType, layer); } + if (rigidBody.bodyID.IsInvalid()) { Ogre::LogManager::getSingleton().logMessage( "Failed to create rigid body"); diff --git a/src/features/editScene/systems/ProceduralMaterialSystem.cpp b/src/features/editScene/systems/ProceduralMaterialSystem.cpp index 337eaa4..2c6f426 100644 --- a/src/features/editScene/systems/ProceduralMaterialSystem.cpp +++ b/src/features/editScene/systems/ProceduralMaterialSystem.cpp @@ -6,6 +6,7 @@ #include #include #include +#include ProceduralMaterialSystem::ProceduralMaterialSystem(flecs::world& world, Ogre::SceneManager* sceneMgr) : m_world(world) @@ -88,6 +89,19 @@ void ProceduralMaterialSystem::createMaterial(flecs::entity entity, ProceduralMa } } + // Register with RTSS to generate shaders (fixes "RenderSystem does not support FixedFunction") + Ogre::RTShader::ShaderGenerator *shaderGen = + Ogre::RTShader::ShaderGenerator::getSingletonPtr(); + if (shaderGen && component.ogreMaterial) { + shaderGen->createShaderBasedTechnique( + *component.ogreMaterial, + Ogre::MaterialManager::DEFAULT_SCHEME_NAME, + Ogre::RTShader::ShaderGenerator::DEFAULT_SCHEME_NAME); + shaderGen->validateMaterial( + Ogre::RTShader::ShaderGenerator::DEFAULT_SCHEME_NAME, + component.materialName); + } + component.created = true; component.dirty = false; component.textureVersion = currentTexVersion;