Physics works

This commit is contained in:
2026-04-15 23:49:24 +03:00
parent c2a1db5a65
commit eec0d8f6f7
4 changed files with 388 additions and 61 deletions

View File

@@ -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 <OgreStaticGeometry.h>
#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<ProceduralTextureComponent>()) {
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<LotComponent>()) {
matEntity = parent.get<LotComponent>().proceduralMaterialEntity;
@@ -102,9 +111,11 @@ void CellGridSystem::update()
matEntity = parent.get<TownComponent>().proceduralMaterialEntity;
}
if (matEntity.is_alive() && matEntity.has<ProceduralMaterialComponent>()) {
if (matEntity.is_valid() && matEntity.is_valid() && matEntity.is_alive() &&
matEntity.has<ProceduralMaterialComponent>()) {
const auto &mat = matEntity.get<ProceduralMaterialComponent>();
if (mat.diffuseTextureEntity.is_alive() &&
if (mat.diffuseTextureEntity.is_valid() &&
mat.diffuseTextureEntity.is_valid() && mat.diffuseTextureEntity.is_alive() &&
mat.diffuseTextureEntity.has<ProceduralTextureComponent>()) {
currentTextureEntity = mat.diffuseTextureEntity;
const auto &tex = mat.diffuseTextureEntity.get<ProceduralTextureComponent>();
@@ -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<ProceduralTextureComponent>();
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<ProceduralTextureComponent>()) {
const auto &tex = it->second.textureEntity.get<ProceduralTextureComponent>();
@@ -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<ProceduralTextureComponent>()) {
const auto &tex = it->second.textureEntity.get<ProceduralTextureComponent>();
@@ -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<LotComponent>()) {
auto &lot = parent.get<LotComponent>();
if (lot.proceduralMaterialEntity.is_alive() &&
if (lot.proceduralMaterialEntity.is_valid() && lot.proceduralMaterialEntity.is_alive() &&
lot.proceduralMaterialEntity
.has<ProceduralMaterialComponent>()) {
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<DistrictComponent>()) {
auto &district = parent.get<DistrictComponent>();
if (district.proceduralMaterialEntity.is_alive() &&
if (district.proceduralMaterialEntity.is_valid() && district.proceduralMaterialEntity.is_alive() &&
district.proceduralMaterialEntity
.has<ProceduralMaterialComponent>()) {
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<TownComponent>();
// Prefer ProceduralMaterial over generated material
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;
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<ProceduralMaterialComponent>()) {
const auto &mat = materialEntity.get<ProceduralMaterialComponent>();
if (mat.diffuseTextureEntity.is_alive() &&
if (mat.diffuseTextureEntity.is_valid() && mat.diffuseTextureEntity.is_alive() &&
mat.diffuseTextureEntity.has<ProceduralTextureComponent>()) {
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<TransformComponent>(
{fnode, pos, localRot, Ogre::Vector3::UNIT_SCALE});
furnEntity.set<RigidBodyComponent>(RigidBodyComponent());
PhysicsColliderComponent collider;
collider.shapeType = PhysicsColliderComponent::ShapeType::Mesh;
collider.meshName = def->meshName;
furnEntity.set<PhysicsColliderComponent>(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<CellGridComponent>().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<TransformComponent>({nullptr, position, rotation,
Ogre::Vector3::UNIT_SCALE});
PhysicsColliderComponent collider;
collider.shapeType = PhysicsColliderComponent::ShapeType::Mesh;
collider.meshName = meshName;
colliderEntity.set<PhysicsColliderComponent>(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<TransformComponent>()) {
auto &t = e.get<TransformComponent>();
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<TransformComponent>({nullptr, parentPos, parentRot,
parentScale});
physicsParent.set<RigidBodyComponent>(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<TransformComponent>({nullptr, parentPos, parentRot,
parentScale});
physicsParent.set<RigidBodyComponent>(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<TransformComponent>({nullptr, parentPos, parentRot,
parentScale});
physicsParent.set<RigidBodyComponent>(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<ProceduralMaterialComponent>()) {
const auto &mat = district.proceduralMaterialEntity
.get<ProceduralMaterialComponent>();
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<ProceduralMaterialComponent>()) {
const auto &mat =
district.proceduralMaterialEntity
.get<ProceduralMaterialComponent>();
// 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<ProceduralTextureComponent>()) {
const auto &texture =
textureEntity.get<ProceduralTextureComponent>();
@@ -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<ProceduralMaterialComponent>()) {
const auto &mat = district.proceduralMaterialEntity.get<ProceduralMaterialComponent>();
if (mat.diffuseTextureEntity.is_alive() &&
if (mat.diffuseTextureEntity.is_valid() && mat.diffuseTextureEntity.is_alive() &&
mat.diffuseTextureEntity.has<ProceduralTextureComponent>()) {
data.textureEntity = mat.diffuseTextureEntity;
const auto &tex = mat.diffuseTextureEntity.get<ProceduralTextureComponent>();
@@ -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<ProceduralMaterialComponent>()) {
const auto &mat = lot.proceduralMaterialEntity
.get<ProceduralMaterialComponent>();
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<DistrictComponent>()) {
districtEntity = parent;
auto &district = parent.get<DistrictComponent>();
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<ProceduralMaterialComponent>()) {
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<TownComponent>();
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<DistrictComponent>()) {
auto &district =
parent.get<DistrictComponent>();
@@ -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<ProceduralMaterialComponent>()) {
const auto &mat =
materialEntityToUse
.get<ProceduralMaterialComponent>();
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<DistrictComponent>()) {
auto &district =
parent.get<DistrictComponent>();
if (district.proceduralMaterialEntity
.is_alive() &&
.is_valid() &&
district.proceduralMaterialEntity.is_alive() &&
district.proceduralMaterialEntity.has<
ProceduralMaterialComponent>()) {
const auto &mat =
district.proceduralMaterialEntity
.get<ProceduralMaterialComponent>();
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<TownComponent>();
if (town.proceduralMaterialEntity
.is_alive() &&
.is_valid() &&
town.proceduralMaterialEntity.is_alive() &&
town.proceduralMaterialEntity.has<
ProceduralMaterialComponent>()) {
const auto &mat =
town.proceduralMaterialEntity
.get<ProceduralMaterialComponent>();
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<ProceduralTextureComponent>()) {
const auto &texture =
textureEntity.get<ProceduralTextureComponent>();
@@ -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<ProceduralTextureComponent>()) {
data.textureEntity = textureEntity;
const auto &tex = textureEntity.get<ProceduralTextureComponent>();
@@ -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<ProceduralMaterialComponent>()) {
const auto &mat =
materialEntity.get<ProceduralMaterialComponent>();
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<LotComponent>()) {
auto &lot = parent.get<LotComponent>();
if (lot.proceduralMaterialEntity.is_alive()) {
if (lot.proceduralMaterialEntity.is_valid() && lot.proceduralMaterialEntity.is_alive()) {
materialEntity = lot.proceduralMaterialEntity;
break;
}
}
if (parent.has<DistrictComponent>()) {
auto &district = parent.get<DistrictComponent>();
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<TownComponent>()) {
auto &town = parent.get<TownComponent>();
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) {

View File

@@ -3,6 +3,8 @@
#include <flecs.h>
#include <memory>
#include <Ogre.h>
#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<flecs::entity> 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<Ogre::Entity*> furnitureEntities;
// Physics placement data for compound shape
std::vector<ColliderPlacement> framePlacements;
std::vector<ColliderPlacement> furniturePlacements;
std::vector<flecs::entity> isolatedFurnitureEntities;
flecs::entity physicsParentEntity = flecs::entity::null();
};
std::unordered_map<uint64_t, MeshData> 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<uint64_t, PlazaData> 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<uint64_t, LotBaseData> m_lotBaseMeshes;
};

View File

@@ -202,6 +202,19 @@ JPH::ShapeRefC EditorPhysicsSystem::buildCompoundShape(flecs::entity rigidBodyEn
std::vector<Ogre::Vector3> positions;
std::vector<Ogre::Quaternion> rotations;
// Check if the rigid body entity itself has a collider
if (rigidBodyEntity.has<PhysicsColliderComponent>() &&
rigidBodyEntity.has<TransformComponent>()) {
auto& collider = rigidBodyEntity.get_mut<PhysicsColliderComponent>();
auto& transform = rigidBodyEntity.get<TransformComponent>();
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<PhysicsColliderComponent>() &&
@@ -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<PhysicsColliderComponent>()) {
auto& collider = child.get<PhysicsColliderComponent>();
if (collider.shapeType == PhysicsColliderComponent::ShapeType::Mesh) {
hasMeshCollider = true;
}
if (entity.has<PhysicsColliderComponent>()) {
auto& collider = entity.get<PhysicsColliderComponent>();
if (collider.shapeType == PhysicsColliderComponent::ShapeType::Mesh) {
hasMeshCollider = true;
}
});
}
if (!hasMeshCollider) {
entity.children([&](flecs::entity child) {
if (child.has<PhysicsColliderComponent>()) {
auto& collider = child.get<PhysicsColliderComponent>();
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");

View File

@@ -6,6 +6,7 @@
#include <OgrePass.h>
#include <OgreTextureUnitState.h>
#include <OgreLogManager.h>
#include <OgreRTShaderSystem.h>
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;