diff --git a/src/features/editScene/CMakeLists.txt b/src/features/editScene/CMakeLists.txt index 8bde44a..c838e45 100644 --- a/src/features/editScene/CMakeLists.txt +++ b/src/features/editScene/CMakeLists.txt @@ -30,6 +30,7 @@ set(EDITSCENE_SOURCES systems/ProceduralMaterialSystem.cpp systems/ProceduralMeshSystem.cpp systems/CellGridSystem.cpp + systems/NormalDebugSystem.cpp systems/RoomLayoutSystem.cpp systems/FurnitureLibrary.cpp systems/StartupMenuSystem.cpp @@ -149,6 +150,7 @@ set(EDITSCENE_HEADERS systems/PlayerControllerSystem.hpp systems/EditorUISystem.hpp systems/CellGridSystem.hpp + systems/NormalDebugSystem.hpp systems/RoomLayoutSystem.hpp systems/FurnitureLibrary.hpp systems/ProceduralMaterialSystem.hpp diff --git a/src/features/editScene/EditorApp.cpp b/src/features/editScene/EditorApp.cpp index 4541ba3..babc2ee 100644 --- a/src/features/editScene/EditorApp.cpp +++ b/src/features/editScene/EditorApp.cpp @@ -19,6 +19,7 @@ #include "systems/NavMeshSystem.hpp" #include "systems/CharacterSystem.hpp" #include "systems/CellGridSystem.hpp" +#include "systems/NormalDebugSystem.hpp" #include "systems/RoomLayoutSystem.hpp" #include "systems/StartupMenuSystem.hpp" #include "systems/PlayerControllerSystem.hpp" @@ -243,12 +244,12 @@ void EditorApp::setup() m_world, m_physicsSystem->getPhysicsWrapper()); m_buoyancySystem->initialize(); - m_sunSystem = std::make_unique(m_world, m_sceneMgr); - m_skyboxSystem = - std::make_unique(m_world, m_sceneMgr); - m_waterPlaneSystem = - std::make_unique(m_world, - m_sceneMgr); + m_sunSystem = + std::make_unique(m_world, m_sceneMgr); + m_skyboxSystem = std::make_unique( + m_world, m_sceneMgr); + m_waterPlaneSystem = std::make_unique( + m_world, m_sceneMgr); // Apply debug setting if it was set before system creation if (m_debugBuoyancy) { @@ -305,12 +306,11 @@ void EditorApp::setup() m_world, m_sceneMgr); m_animationTreeSystem->initialize(); m_behaviorTreeSystem = std::make_unique( - m_world, m_sceneMgr, - m_animationTreeSystem.get()); + m_world, m_sceneMgr, m_animationTreeSystem.get()); // Setup NavMesh system - m_navMeshSystem = std::make_unique( - m_world, m_sceneMgr); + m_navMeshSystem = + std::make_unique(m_world, m_sceneMgr); // Setup Character physics system m_characterSystem = @@ -324,8 +324,17 @@ void EditorApp::setup() // Wire CellGridSystem into NavMeshSystem so it can collect // batched frame/furniture geometry from StaticGeometry. - m_navMeshSystem->setCellGridSystem( - m_cellGridSystem.get()); + m_navMeshSystem->setCellGridSystem(m_cellGridSystem.get()); + + // Setup NormalDebug system (disabled by default) + m_normalDebugSystem = std::make_unique( + m_world, m_sceneMgr, m_cellGridSystem.get()); + + // Wire NormalDebugSystem into UI for toggle + if (m_uiSystem) { + m_uiSystem->setNormalDebugSystem( + m_normalDebugSystem.get()); + } // Setup RoomLayout system m_roomLayoutSystem = @@ -694,8 +703,7 @@ bool EditorApp::frameRenderingQueued(const Ogre::FrameEvent &evt) if (m_animationTreeSystem) { m_animationTreeSystem->update(evt.timeSinceLastFrame); if (m_behaviorTreeSystem) - m_behaviorTreeSystem->update( - evt.timeSinceLastFrame); + m_behaviorTreeSystem->update(evt.timeSinceLastFrame); } if (m_proceduralMeshSystem) { m_proceduralMeshSystem->update(); @@ -709,6 +717,11 @@ bool EditorApp::frameRenderingQueued(const Ogre::FrameEvent &evt) m_cellGridSystem->update(); } + /* --- Normal debug visualization (after geometry is built) --- */ + if (m_normalDebugSystem) { + m_normalDebugSystem->update(); + } + /* --- NavMesh builds after static geometry is ready --- */ if (m_navMeshSystem) { m_navMeshSystem->update(evt.timeSinceLastFrame); diff --git a/src/features/editScene/EditorApp.hpp b/src/features/editScene/EditorApp.hpp index 579e763..a67e70b 100644 --- a/src/features/editScene/EditorApp.hpp +++ b/src/features/editScene/EditorApp.hpp @@ -34,6 +34,7 @@ class BuoyancySystem; class EditorSunSystem; class EditorSkyboxSystem; class EditorWaterPlaneSystem; +class NormalDebugSystem; class EditorApp; /** @@ -217,6 +218,7 @@ private: std::unique_ptr m_navMeshSystem; std::unique_ptr m_characterSystem; std::unique_ptr m_cellGridSystem; + std::unique_ptr m_normalDebugSystem; std::unique_ptr m_roomLayoutSystem; // Game systems diff --git a/src/features/editScene/resources.cfg b/src/features/editScene/resources.cfg index 20cb5c5..db3d330 100644 --- a/src/features/editScene/resources.cfg +++ b/src/features/editScene/resources.cfg @@ -11,6 +11,7 @@ FileSystem=resources/buildings/parts/pier FileSystem=resources/buildings/parts/furniture FileSystem=resources/vehicles FileSystem=resources/fonts +FileSystem=resources/debug [Popular] FileSystem=resources/materials/programs diff --git a/src/features/editScene/systems/CellGridSystem.hpp b/src/features/editScene/systems/CellGridSystem.hpp index 8b40180..4177f29 100644 --- a/src/features/editScene/systems/CellGridSystem.hpp +++ b/src/features/editScene/systems/CellGridSystem.hpp @@ -6,186 +6,241 @@ #include "../components/RigidBody.hpp" #include "../components/PhysicsCollider.hpp" -namespace Procedural { - class TriangleBuffer; +namespace Procedural +{ +class TriangleBuffer; } -namespace Ogre { - class StaticGeometry; +namespace Ogre +{ +class StaticGeometry; } class CellGridSystem { public: - CellGridSystem(flecs::world& world, Ogre::SceneManager* sceneMgr); - ~CellGridSystem(); - - void initialize(); - void update(); - - // Force rebuild of a specific cell grid - void rebuildCellGrid(flecs::entity entity); - - // Create/Update town material - void updateTownMaterial(flecs::entity townEntity); - - // Expose frame/furniture placements for navmesh generation - struct PlacementInfo { - std::string meshName; - Ogre::Vector3 position; - Ogre::Quaternion rotation; - }; - std::vector getFramePlacements(flecs::entity entity) const; - std::vector getFurniturePlacements(flecs::entity entity) const; - + CellGridSystem(flecs::world &world, Ogre::SceneManager *sceneMgr); + ~CellGridSystem(); + + void initialize(); + void update(); + + // Force rebuild of a specific cell grid + void rebuildCellGrid(flecs::entity entity); + + // Create/Update town material + void updateTownMaterial(flecs::entity townEntity); + + // Expose frame/furniture placements for navmesh generation + struct PlacementInfo { + std::string meshName; + Ogre::Vector3 position; + Ogre::Quaternion rotation; + }; + std::vector + getFramePlacements(flecs::entity entity) const; + std::vector + getFurniturePlacements(flecs::entity entity) const; + + // Plaza mesh storage per district entity + struct PlazaData { + std::string meshName; + Ogre::Entity *entity = nullptr; + // 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(); + }; + + // Lot base mesh storage per lot entity + struct LotBaseData { + std::string meshName; + Ogre::Entity *entity = nullptr; + // 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(); + }; + + // Expose plaza and lot base mesh data for debug visualization + const std::unordered_map &getPlazaMeshes() const + { + return m_plazaMeshes; + } + const std::unordered_map & + getLotBaseMeshes() const + { + return m_lotBaseMeshes; + } + private: - flecs::world& m_world; - Ogre::SceneManager* m_sceneMgr; - - flecs::query m_cellGridQuery; - flecs::query m_townQuery; - flecs::query m_districtQuery; - flecs::query m_lotQuery; - - 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); - - // Create triangle buffers for different parts - void buildFloorsAndCeilings(const struct CellGridComponent& grid, Procedural::TriangleBuffer& floorTb, Procedural::TriangleBuffer& ceilingTb); - void buildWalls(const struct CellGridComponent& grid, - Procedural::TriangleBuffer& extWallTb, - Procedural::TriangleBuffer& intWallTb, - Procedural::TriangleBuffer& intWindowsTb); - void buildDoors(const struct CellGridComponent& grid, Procedural::TriangleBuffer& extDoorsTb, Procedural::TriangleBuffer& intDoorsTb); - void buildWindows(const struct CellGridComponent& grid, Procedural::TriangleBuffer& extWindowsTb, Procedural::TriangleBuffer& intWindowsTb); - void buildCorners(const struct CellGridComponent& grid, Procedural::TriangleBuffer& extWallTb); - void buildInternalCorners(const struct CellGridComponent& grid, Procedural::TriangleBuffer& intWallTb); - - // Build roofs - void buildRoofs(flecs::entity lotEntity, const struct CellGridComponent& grid, - Procedural::TriangleBuffer& roofTopTb, Procedural::TriangleBuffer& roofSideTb); - - // Build plaza for district - void buildDistrictPlaza(flecs::entity entity, struct DistrictComponent& district); - - // Build lot base geometry - void buildLotBase(flecs::entity entity, struct LotComponent& lot); - - // Build furniture meshes - void buildFurniture(flecs::entity entity, const struct CellGridComponent& grid); - void destroyFurniture(flecs::entity entity); - - // Build window and door frames (3D frame meshes) - void buildFrames(flecs::entity entity, const struct CellGridComponent& grid, const std::string& materialName); - void createWindowFrameMeshes(const struct CellGridComponent& grid, const std::string& materialName, - const std::string& meshPrefix, flecs::entity materialEntity); - void createDoorFrameMeshes(const struct CellGridComponent& grid, const std::string& materialName, - const std::string& meshPrefix, flecs::entity materialEntity); - void placeWindowFrames(flecs::entity entity, const struct CellGridComponent& grid, Ogre::SceneNode* parentNode, std::vector& frameEntities); - void placeDoorFrames(flecs::entity entity, const struct CellGridComponent& grid, Ogre::SceneNode* parentNode, std::vector& frameEntities); - void destroyFrames(flecs::entity entity); - - // StaticGeometry versions (batched for better performance) - void placeWindowFramesInStaticGeometry(flecs::entity entity, const struct CellGridComponent& grid, - const Ogre::Vector3& parentPos, const Ogre::Quaternion& parentRot, const Ogre::Vector3& parentScale, - Ogre::StaticGeometry* staticGeom, int& frameCount); - void placeDoorFramesInStaticGeometry(flecs::entity entity, const struct CellGridComponent& grid, - const Ogre::Vector3& parentPos, const Ogre::Quaternion& parentRot, const Ogre::Vector3& parentScale, - Ogre::StaticGeometry* staticGeom, int& frameCount); - - // Convert triangle buffer to mesh - Ogre::MeshPtr convertToMesh(const std::string& name, Procedural::TriangleBuffer& tb, const std::string& materialName); - - // Generate LOD levels for a mesh (shared settings for all procedural editor meshes) - void generateLodForMesh(Ogre::MeshPtr mesh); - - // Apply UV mapping from color rects - void applyUVMapping(Procedural::TriangleBuffer& tb, flecs::entity entity, const std::string& rectName); - - // Apply UV mapping using texture coordinates directly - void applyUVMappingToBuffer(Procedural::TriangleBuffer& tb, const std::string& rectName, flecs::entity materialEntity); - - // Destroy existing mesh - 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; - std::string intWallMesh; - std::string intWindowsMesh; - std::vector doorMeshes; - std::vector windowMeshes; - std::string roofMesh; - std::string roofSideMesh; - std::vector entities; - - // Track texture dependency for automatic rebuild when texture changes - flecs::entity textureEntity = flecs::entity::null(); - unsigned int textureVersion = 0; - - // Frame meshes (unique per CellGrid for cellSize/cellHeight adaptation) - std::string externalWindowFrameMesh; - std::string internalWindowFrameMesh; - std::string externalDoorFrameMesh; - std::string internalDoorFrameMesh; - - // Frame entities attached to the SceneNode (so they move with transform) - std::vector frameEntities; - - // StaticGeometry for frames (batches all frames into single draw call) - Ogre::StaticGeometry* frameStaticGeometry = nullptr; - - // 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; - - // Plaza mesh storage per district entity - struct PlazaData { - std::string meshName; - Ogre::Entity* entity = nullptr; - // 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; - - // Lot base mesh storage per lot entity - struct LotBaseData { - std::string meshName; - Ogre::Entity* entity = nullptr; - // 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; + flecs::world &m_world; + Ogre::SceneManager *m_sceneMgr; + + flecs::query m_cellGridQuery; + flecs::query m_townQuery; + flecs::query m_districtQuery; + flecs::query m_lotQuery; + + 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); + + // Create triangle buffers for different parts + void buildFloorsAndCeilings(const struct CellGridComponent &grid, + Procedural::TriangleBuffer &floorTb, + Procedural::TriangleBuffer &ceilingTb); + void buildWalls(const struct CellGridComponent &grid, + Procedural::TriangleBuffer &extWallTb, + Procedural::TriangleBuffer &intWallTb, + Procedural::TriangleBuffer &intWindowsTb); + void buildDoors(const struct CellGridComponent &grid, + Procedural::TriangleBuffer &extDoorsTb, + Procedural::TriangleBuffer &intDoorsTb); + void buildWindows(const struct CellGridComponent &grid, + Procedural::TriangleBuffer &extWindowsTb, + Procedural::TriangleBuffer &intWindowsTb); + void buildCorners(const struct CellGridComponent &grid, + Procedural::TriangleBuffer &extWallTb); + void buildInternalCorners(const struct CellGridComponent &grid, + Procedural::TriangleBuffer &intWallTb); + + // Build roofs + void buildRoofs(flecs::entity lotEntity, + const struct CellGridComponent &grid, + Procedural::TriangleBuffer &roofTopTb, + Procedural::TriangleBuffer &roofSideTb); + + // Build plaza for district + void buildDistrictPlaza(flecs::entity entity, + struct DistrictComponent &district); + + // Build lot base geometry + void buildLotBase(flecs::entity entity, struct LotComponent &lot); + + // Build furniture meshes + void buildFurniture(flecs::entity entity, + const struct CellGridComponent &grid); + void destroyFurniture(flecs::entity entity); + + // Build window and door frames (3D frame meshes) + void buildFrames(flecs::entity entity, + const struct CellGridComponent &grid, + const std::string &materialName); + void createWindowFrameMeshes(const struct CellGridComponent &grid, + const std::string &materialName, + const std::string &meshPrefix, + flecs::entity materialEntity); + void createDoorFrameMeshes(const struct CellGridComponent &grid, + const std::string &materialName, + const std::string &meshPrefix, + flecs::entity materialEntity); + void placeWindowFrames(flecs::entity entity, + const struct CellGridComponent &grid, + Ogre::SceneNode *parentNode, + std::vector &frameEntities); + void placeDoorFrames(flecs::entity entity, + const struct CellGridComponent &grid, + Ogre::SceneNode *parentNode, + std::vector &frameEntities); + void destroyFrames(flecs::entity entity); + + // StaticGeometry versions (batched for better performance) + void placeWindowFramesInStaticGeometry( + flecs::entity entity, const struct CellGridComponent &grid, + const Ogre::Vector3 &parentPos, + const Ogre::Quaternion &parentRot, + const Ogre::Vector3 &parentScale, + Ogre::StaticGeometry *staticGeom, int &frameCount); + void placeDoorFramesInStaticGeometry( + flecs::entity entity, const struct CellGridComponent &grid, + const Ogre::Vector3 &parentPos, + const Ogre::Quaternion &parentRot, + const Ogre::Vector3 &parentScale, + Ogre::StaticGeometry *staticGeom, int &frameCount); + + // Convert triangle buffer to mesh + Ogre::MeshPtr convertToMesh(const std::string &name, + Procedural::TriangleBuffer &tb, + const std::string &materialName); + + // Generate LOD levels for a mesh (shared settings for all procedural editor meshes) + void generateLodForMesh(Ogre::MeshPtr mesh); + + // Apply UV mapping from color rects + void applyUVMapping(Procedural::TriangleBuffer &tb, + flecs::entity entity, const std::string &rectName); + + // Apply UV mapping using texture coordinates directly + void applyUVMappingToBuffer(Procedural::TriangleBuffer &tb, + const std::string &rectName, + flecs::entity materialEntity); + + // Destroy existing mesh + 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; + std::string intWallMesh; + std::string intWindowsMesh; + std::vector doorMeshes; + std::vector windowMeshes; + std::string roofMesh; + std::string roofSideMesh; + std::vector entities; + + // Track texture dependency for automatic rebuild when texture changes + flecs::entity textureEntity = flecs::entity::null(); + unsigned int textureVersion = 0; + + // Frame meshes (unique per CellGrid for cellSize/cellHeight adaptation) + std::string externalWindowFrameMesh; + std::string internalWindowFrameMesh; + std::string externalDoorFrameMesh; + std::string internalDoorFrameMesh; + + // Frame entities attached to the SceneNode (so they move with transform) + std::vector frameEntities; + + // StaticGeometry for frames (batches all frames into single draw call) + Ogre::StaticGeometry *frameStaticGeometry = nullptr; + + // 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; + + // Plaza mesh storage per district entity + std::unordered_map m_plazaMeshes; + + // Lot base mesh storage per lot entity + std::unordered_map m_lotBaseMeshes; }; diff --git a/src/features/editScene/systems/EditorUISystem.cpp b/src/features/editScene/systems/EditorUISystem.cpp index 7e30a10..12688e1 100644 --- a/src/features/editScene/systems/EditorUISystem.cpp +++ b/src/features/editScene/systems/EditorUISystem.cpp @@ -41,6 +41,7 @@ #include "PhysicsSystem.hpp" #include "BuoyancySystem.hpp" #include "NavMeshSystem.hpp" +#include "NormalDebugSystem.hpp" #include #include #include @@ -322,6 +323,22 @@ void EditorUISystem::renderHierarchyWindow() } } + // Normal debug visualization toggle + if (m_normalDebugSystem) { + bool normalDebug = + m_normalDebugSystem->isEnabled(); + if (ImGui::Checkbox( + "Show Polygon Normals", + &normalDebug)) { + m_normalDebugSystem->setEnabled( + normalDebug); + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip( + "Show vertex normals for plaza and lot base geometry"); + } + } + ImGui::EndMenu(); } ImGui::EndMenuBar(); @@ -753,7 +770,8 @@ void EditorUISystem::renderComponentList(flecs::entity entity) // Render AnimationTreeTemplate if present if (entity.has()) { auto &templ = entity.get_mut(); - m_componentRegistry.render(entity, templ); + m_componentRegistry.render(entity, + templ); componentCount++; } @@ -860,7 +878,8 @@ void EditorUISystem::renderComponentList(flecs::entity entity) auto &nav = entity.get_mut(); if (m_componentRegistry.render(entity, nav)) { if (nav.debugDraw && NavMeshSystem::getInstance()) - NavMeshSystem::getInstance()->setDebugDraw(entity, nav.debugDraw); + NavMeshSystem::getInstance()->setDebugDraw( + entity, nav.debugDraw); } componentCount++; } diff --git a/src/features/editScene/systems/EditorUISystem.hpp b/src/features/editScene/systems/EditorUISystem.hpp index 5ed0945..8e6066e 100644 --- a/src/features/editScene/systems/EditorUISystem.hpp +++ b/src/features/editScene/systems/EditorUISystem.hpp @@ -14,6 +14,7 @@ // Forward declarations class EditorPhysicsSystem; class BuoyancySystem; +class NormalDebugSystem; namespace Ogre { @@ -124,6 +125,14 @@ public: m_buoyancySystem = buoyancy; } + /** + * Set normal debug system for toggle + */ + void setNormalDebugSystem(NormalDebugSystem *normalDebug) + { + m_normalDebugSystem = normalDebug; + } + /** * Enable/disable editor UI rendering */ @@ -215,6 +224,9 @@ private: // Buoyancy system reference (for configuration) BuoyancySystem *m_buoyancySystem = nullptr; + // Normal debug system reference (for toggle) + NormalDebugSystem *m_normalDebugSystem = nullptr; + // Editor UI enabled flag bool m_editorUIEnabled = true; diff --git a/src/features/editScene/systems/NormalDebugSystem.cpp b/src/features/editScene/systems/NormalDebugSystem.cpp new file mode 100644 index 0000000..d59cd65 --- /dev/null +++ b/src/features/editScene/systems/NormalDebugSystem.cpp @@ -0,0 +1,290 @@ +#include "NormalDebugSystem.hpp" +#include "CellGridSystem.hpp" +#include "../components/CellGrid.hpp" +#include "../components/Transform.hpp" +#include +#include +#include +#include +#include +#include +#include + +// Normal line length in world units +static const float NORMAL_LINE_LENGTH = 0.5f; + +NormalDebugSystem::NormalDebugSystem(flecs::world &world, + Ogre::SceneManager *sceneMgr, + CellGridSystem *cellGridSystem) + : m_world(world) + , m_sceneMgr(sceneMgr) + , m_cellGridSystem(cellGridSystem) +{ + // Create ManualObject for normal lines + m_manualObj = m_sceneMgr->createManualObject("NormalDebugLines"); + m_manualObj->setDynamic(true); + + // Create scene node for the debug visualization + m_node = m_sceneMgr->getRootSceneNode()->createChildSceneNode( + "NormalDebugNode"); + m_node->attachObject(m_manualObj); + m_node->setVisible(false); // Disabled by default +} + +NormalDebugSystem::~NormalDebugSystem() +{ + if (m_node) { + m_node->detachAllObjects(); + m_sceneMgr->destroySceneNode(m_node); + m_node = nullptr; + } + if (m_manualObj) { + m_sceneMgr->destroyManualObject(m_manualObj); + m_manualObj = nullptr; + } +} + +void NormalDebugSystem::setEnabled(bool enabled) +{ + m_enabled = enabled; + if (m_node) { + m_node->setVisible(enabled); + } + if (enabled) { + rebuildAll(); + } else { + // Clear the manual object + if (m_manualObj) { + m_manualObj->clear(); + } + } +} + +void NormalDebugSystem::update() +{ + if (!m_enabled || !m_manualObj || !m_cellGridSystem) { + return; + } + + // Check if plaza/lot entities have changed by comparing cached IDs + bool needsRebuild = false; + + // Get current plaza entities from CellGridSystem + const auto &plazaMeshes = m_cellGridSystem->getPlazaMeshes(); + std::vector currentPlazaEntities; + for (const auto &pair : plazaMeshes) { + currentPlazaEntities.push_back(pair.first); + } + if (currentPlazaEntities != m_cachedPlazaEntities) { + needsRebuild = true; + m_cachedPlazaEntities = currentPlazaEntities; + } + + // Get current lot base entities from CellGridSystem + const auto &lotBaseMeshes = m_cellGridSystem->getLotBaseMeshes(); + std::vector currentLotEntities; + for (const auto &pair : lotBaseMeshes) { + currentLotEntities.push_back(pair.first); + } + if (currentLotEntities != m_cachedLotEntities) { + needsRebuild = true; + m_cachedLotEntities = currentLotEntities; + } + + if (needsRebuild) { + rebuildAll(); + } +} + +void NormalDebugSystem::rebuildAll() +{ + if (!m_manualObj || !m_cellGridSystem) { + return; + } + + m_manualObj->clear(); + + const auto &plazaMeshes = m_cellGridSystem->getPlazaMeshes(); + const auto &lotBaseMeshes = m_cellGridSystem->getLotBaseMeshes(); + + // Count total normals to draw + size_t totalNormals = 0; + for (const auto &pair : plazaMeshes) { + if (pair.second.entity) { + Ogre::MeshPtr mesh = pair.second.entity->getMesh(); + if (mesh.isNull()) + continue; + for (unsigned int i = 0; i < mesh->getNumSubMeshes(); + ++i) { + Ogre::SubMesh *sub = mesh->getSubMesh(i); + if (sub && sub->vertexData) { + totalNormals += + sub->vertexData->vertexCount; + } + } + } + } + for (const auto &pair : lotBaseMeshes) { + if (pair.second.entity) { + Ogre::MeshPtr mesh = pair.second.entity->getMesh(); + if (mesh.isNull()) + continue; + for (unsigned int i = 0; i < mesh->getNumSubMeshes(); + ++i) { + Ogre::SubMesh *sub = mesh->getSubMesh(i); + if (sub && sub->vertexData) { + totalNormals += + sub->vertexData->vertexCount; + } + } + } + } + + if (totalNormals == 0) { + Ogre::LogManager::getSingleton().logMessage( + "NormalDebug: No plaza or lot meshes found"); + return; + } + + // Begin drawing lines (2 vertices per normal) + m_manualObj->begin("Debug/NormalOverlay", + Ogre::RenderOperation::OT_LINE_LIST); + + // Draw plaza normals + for (const auto &pair : plazaMeshes) { + if (!pair.second.entity) + continue; + + // Find the scene node for this plaza entity + Ogre::SceneNode *parentNode = nullptr; + flecs::entity districtEntity = m_world.entity(pair.first); + if (districtEntity.is_alive() && + districtEntity.has()) { + const auto &transform = + districtEntity.get(); + parentNode = transform.node; + } + + drawMeshNormals(pair.second.entity, parentNode, m_manualObj); + } + + // Draw lot base normals + for (const auto &pair : lotBaseMeshes) { + if (!pair.second.entity) + continue; + + // Find the scene node for this lot entity + Ogre::SceneNode *parentNode = nullptr; + flecs::entity lotEntity = m_world.entity(pair.first); + if (lotEntity.is_alive() && + lotEntity.has()) { + const auto &transform = + lotEntity.get(); + parentNode = transform.node; + } + + drawMeshNormals(pair.second.entity, parentNode, m_manualObj); + } + + m_manualObj->end(); + + Ogre::LogManager::getSingleton().logMessage( + "NormalDebug: Drew " + + Ogre::StringConverter::toString(totalNormals) + + " normal lines"); +} + +void NormalDebugSystem::drawMeshNormals(Ogre::Entity *entity, + Ogre::SceneNode *parentNode, + Ogre::ManualObject *manualObj) +{ + if (!entity || !manualObj) { + return; + } + + Ogre::MeshPtr mesh = entity->getMesh(); + if (mesh.isNull()) { + return; + } + + // Get world transform from parent node + Ogre::Vector3 worldPos(0, 0, 0); + Ogre::Quaternion worldRot = Ogre::Quaternion::IDENTITY; + Ogre::Vector3 worldScale(1, 1, 1); + if (parentNode) { + worldPos = parentNode->_getDerivedPosition(); + worldRot = parentNode->_getDerivedOrientation(); + worldScale = parentNode->_getDerivedScale(); + } + + for (unsigned int i = 0; i < mesh->getNumSubMeshes(); ++i) { + Ogre::SubMesh *subMesh = mesh->getSubMesh(i); + if (!subMesh || !subMesh->vertexData) { + continue; + } + + Ogre::VertexData *vertexData = subMesh->vertexData; + const Ogre::VertexElement *posElem = + vertexData->vertexDeclaration->findElementBySemantic( + Ogre::VES_POSITION); + const Ogre::VertexElement *normElem = + vertexData->vertexDeclaration->findElementBySemantic( + Ogre::VES_NORMAL); + + if (!posElem || !normElem) { + continue; + } + + // Get vertex buffer + Ogre::HardwareVertexBufferSharedPtr vbuf = + vertexData->vertexBufferBinding->getBuffer(0); + unsigned char *vertexDataPtr = static_cast( + vbuf->lock(Ogre::HardwareBuffer::HBL_READ_ONLY)); + + float *pFloat; + Ogre::Vector3 pos; + Ogre::Vector3 normal; + + for (size_t v = 0; v < vertexData->vertexCount; ++v) { + // Read position + posElem->baseVertexPointerToElement(vertexDataPtr, + &pFloat); + pos.x = *pFloat++; + pos.y = *pFloat++; + pos.z = *pFloat; + + // Read normal + normElem->baseVertexPointerToElement(vertexDataPtr, + &pFloat); + normal.x = *pFloat++; + normal.y = *pFloat++; + normal.z = *pFloat; + + // Transform position and normal to world space + Ogre::Vector3 worldVertex = + worldPos + (worldRot * (pos * worldScale)); + Ogre::Vector3 worldNormal = worldRot * normal; + worldNormal.normalise(); + + // Calculate end point of normal line + Ogre::Vector3 endPoint = + worldVertex + worldNormal * NORMAL_LINE_LENGTH; + + // Colour based on normal direction for visual clarity + // Red = X, Green = Y, Blue = Z + Ogre::ColourValue colour(std::abs(worldNormal.x), + std::abs(worldNormal.y), + std::abs(worldNormal.z), 1.0f); + + // Draw line from vertex along normal + manualObj->colour(colour); + manualObj->position(worldVertex); + manualObj->colour(colour); + manualObj->position(endPoint); + + vertexDataPtr += vbuf->getVertexSize(); + } + + vbuf->unlock(); + } +} diff --git a/src/features/editScene/systems/NormalDebugSystem.hpp b/src/features/editScene/systems/NormalDebugSystem.hpp new file mode 100644 index 0000000..6f1bfde --- /dev/null +++ b/src/features/editScene/systems/NormalDebugSystem.hpp @@ -0,0 +1,71 @@ +#ifndef NORMAL_DEBUG_SYSTEM_HPP +#define NORMAL_DEBUG_SYSTEM_HPP + +#include +#include +#include +#include + +class CellGridSystem; + +/** + * Debug visualization system for polygon normals. + * + * Draws normal lines for plaza (DistrictComponent) and lot base + * (LotComponent) geometry using ManualObject with a see-through + * overlay material (Debug/NormalOverlay). + * + * Disabled by default. Toggle via EditorUISystem Settings menu. + */ +class NormalDebugSystem { +public: + NormalDebugSystem(flecs::world &world, Ogre::SceneManager *sceneMgr, + CellGridSystem *cellGridSystem); + ~NormalDebugSystem(); + + /** + * Update normal visualization each frame. + * Rebuilds ManualObject if mesh data has changed. + */ + void update(); + + /** + * Enable/disable normal visualization. + */ + void setEnabled(bool enabled); + bool isEnabled() const + { + return m_enabled; + } + +private: + /** + * Rebuild all normal visualizations from scratch. + */ + void rebuildAll(); + + /** + * Draw normals for a single mesh entity. + * @param entity The Ogre::Entity whose mesh normals to visualize. + * @param parentNode The scene node the entity is attached to (for world-space transform). + * @param manualObj The ManualObject to draw into (must be in begin/end scope). + */ + void drawMeshNormals(Ogre::Entity *entity, Ogre::SceneNode *parentNode, + Ogre::ManualObject *manualObj); + + flecs::world &m_world; + Ogre::SceneManager *m_sceneMgr; + CellGridSystem *m_cellGridSystem; + + bool m_enabled = false; + + // ManualObject for normal lines + Ogre::ManualObject *m_manualObj = nullptr; + Ogre::SceneNode *m_node = nullptr; + + // Cached entity IDs to detect changes + std::vector m_cachedPlazaEntities; + std::vector m_cachedLotEntities; +}; + +#endif // NORMAL_DEBUG_SYSTEM_HPP