#include "CellGridSystem.hpp"
#include "FurnitureLibrary.hpp"
#include "../components/CellGrid.hpp"
#include "../components/TriangleBuffer.hpp"
#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"
#include "../components/GeneratedPhysicsTag.hpp"
#include <OgreSceneManager.h>
#include <OgreMeshManager.h>
#include <OgreEntity.h>
#include <OgreSubMesh.h>
#include <OgreLogManager.h>
#include <OgreMaterialManager.h>
#include <OgreTechnique.h>
#include <OgrePass.h>
#include <OgreTextureManager.h>
#include <OgreRTShaderSystem.h>
#include <OgreMeshLodGenerator.h>
#include <ProceduralTriangleBuffer.h>
#include <ProceduralBoxGenerator.h>
#include <ProceduralPlaneGenerator.h>
#include <ProceduralCylinderGenerator.h>
#include <ProceduralConeGenerator.h>
#include <ProceduralLathe.h>
#include <ProceduralShape.h>

CellGridSystem::CellGridSystem(flecs::world &world,
			       Ogre::SceneManager *sceneMgr)
	: m_world(world)
	, m_sceneMgr(sceneMgr)
	, m_cellGridQuery(world.query<CellGridComponent>())
	, m_townQuery(world.query<TownComponent>())
	, m_districtQuery(world.query<DistrictComponent>())
	, m_lotQuery(world.query<LotComponent>())
{
}

CellGridSystem::~CellGridSystem() = default;

void CellGridSystem::initialize()
{
	if (m_initialized)
		return;
	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()
{
	if (!m_initialized)
		return;

	// Process dirty cell grids
	m_cellGridQuery.each(
		[&](flecs::entity entity, CellGridComponent &grid) {
			// Check if grid itself is dirty
			bool needsRebuild = grid.dirty;
			
			// Check for dirty roofs (roof children will mark themselves dirty)
			// and mark parent grid dirty to trigger rebuild
			if (!needsRebuild) {
				entity.children([&](flecs::entity child) {
					if (child.has<RoofComponent>()) {
						const auto &roof = child.get<RoofComponent>();
						if (roof.dirty) {
							needsRebuild = true;
							return false; // Stop iteration
						}
					}
					return true; // Continue iteration
				});
			}
			
			// Check if texture has changed (new color rects, etc.)
			// We check this even if needsRebuild is already true, to ensure we capture
			// the latest texture version for tracking
			flecs::entity currentTextureEntity = flecs::entity::null();
			unsigned int currentTextureVersion = 0;
			
			// 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;
				currentTextureVersion = it->second.textureVersion;
			}
			
			// If no stored texture, try to find from parent material
			if (!currentTextureEntity.is_valid()) {
				flecs::entity parent = entity.parent();
				while (parent.is_valid() && parent.is_alive()) {
					flecs::entity matEntity = flecs::entity::null();
					if (parent.has<LotComponent>()) {
						matEntity = parent.get<LotComponent>().proceduralMaterialEntity;
					} else if (parent.has<DistrictComponent>()) {
						matEntity = parent.get<DistrictComponent>().proceduralMaterialEntity;
					} else if (parent.has<TownComponent>()) {
						matEntity = parent.get<TownComponent>().proceduralMaterialEntity;
					}
					
					if (matEntity.is_valid() && matEntity.is_valid() && matEntity.is_alive() &&
					    matEntity.has<ProceduralMaterialComponent>()) {
						const auto &mat = matEntity.get<ProceduralMaterialComponent>();
						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>();
							currentTextureVersion = tex.version;
							break;
						}
					}
					parent = parent.parent();
				}
			}
			
			bool textureChanged = false;
			if (currentTextureEntity.is_valid() && currentTextureEntity.is_alive()) {
				const auto &tex = currentTextureEntity.get<ProceduralTextureComponent>();
				if (tex.version != currentTextureVersion) {
					textureChanged = true;
					needsRebuild = true;
				}
			}
			
			// If we need to rebuild, mark all roof children as dirty
			// This is needed because buildRoofs() only builds dirty roofs,
			// and the rebuild will destroy existing roof meshes
			if (needsRebuild) {
				entity.children([&](flecs::entity child) {
					if (child.has<RoofComponent>()) {
						child.get_mut<RoofComponent>().markDirty();
					}
					return true;
				});
			}
			
			if (needsRebuild) {
				buildCellGrid(entity, grid);
				grid.dirty = false;
				// Clear dirty flag on all roof children
				entity.children([&](flecs::entity child) {
					if (child.has<RoofComponent>()) {
						auto &roof = child.get_mut<RoofComponent>();
						roof.dirty = false;
					}
					return true; // Continue iteration
				});
			}
		});

	// Update town materials
	m_townQuery.each([&](flecs::entity entity, TownComponent &town) {
		if (town.materialDirty) {
			updateTownMaterial(entity);
			town.materialDirty = false;
		}
	});

	// Process dirty districts (for plaza generation)
	int districtCount = 0;
	int dirtyDistrictCount = 0;
	m_districtQuery.each([&](flecs::entity entity,
				 DistrictComponent &district) {
		districtCount++;
		bool needsRebuild = district.dirty;
		
		// Check if texture has changed
		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>();
				if (tex.version != it->second.textureVersion) {
					needsRebuild = true;
				}
			}
		}
		
		if (needsRebuild) {
			dirtyDistrictCount++;
			Ogre::LogManager::getSingleton().logMessage(
				"CellGrid: Processing dirty district " +
				std::to_string(entity.id()) + " isPlaza=" +
				(district.isPlaza ? "true" : "false"));
			buildDistrictPlaza(entity, district);
			district.dirty = false;
		}
	});

	static int frameCount = 0;
	if (++frameCount % 60 == 0) {
		Ogre::LogManager::getSingleton().logMessage(
			"CellGrid: update() - " +
			std::to_string(districtCount) + " districts, " +
			std::to_string(dirtyDistrictCount) + " dirty");
	}

	// Process dirty lots (for base geometry)
	m_lotQuery.each([&](flecs::entity entity, LotComponent &lot) {
		bool needsRebuild = lot.dirty;
		
		// Check if texture has changed
		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>();
				if (tex.version != it->second.textureVersion) {
					needsRebuild = true;
				}
			}
		}
		
		if (needsRebuild) {
			buildLotBase(entity, lot);
			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,
				   CellGridComponent &grid)
{
	Ogre::LogManager::getSingleton().logMessage(
		"CellGrid: buildCellGrid for entity " +
		std::to_string(entity.id()) +
		" cells=" + std::to_string(grid.cells.size()) +
		" cellSize=" + std::to_string(grid.cellSize) +
		" cellHeight=" + std::to_string(grid.cellHeight));

	// 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
	flecs::entity townEntity = flecs::entity::null();
	flecs::entity materialEntity = flecs::entity::null();

	// Look for ProceduralMaterial in parent hierarchy (Lot -> District -> Town)
	flecs::entity parent = entity.parent();
	while (parent.is_valid() && parent.is_alive()) {
		if (parent.has<LotComponent>()) {
			auto &lot = parent.get<LotComponent>();
			if (lot.proceduralMaterialEntity.is_valid() && lot.proceduralMaterialEntity.is_alive() &&
			    lot.proceduralMaterialEntity
				    .has<ProceduralMaterialComponent>()) {
				const auto &mat =
					lot.proceduralMaterialEntity.get<
						ProceduralMaterialComponent>();
				if (!mat.materialName.empty()) {
					materialName = mat.materialName;
					materialEntity =
						lot.proceduralMaterialEntity;
					break;
				}
			}
		}
		if (parent.has<DistrictComponent>()) {
			auto &district = parent.get<DistrictComponent>();
			if (district.proceduralMaterialEntity.is_valid() && district.proceduralMaterialEntity.is_alive() &&
			    district.proceduralMaterialEntity
				    .has<ProceduralMaterialComponent>()) {
				const auto &mat =
					district.proceduralMaterialEntity.get<
						ProceduralMaterialComponent>();
				if (!mat.materialName.empty()) {
					materialName = mat.materialName;
					materialEntity =
						district.proceduralMaterialEntity;
					break;
				}
			}
		}
		if (parent.has<TownComponent>()) {
			townEntity = parent;
			auto &town = parent.get<TownComponent>();
			// Prefer ProceduralMaterial over generated material
			if (town.proceduralMaterialEntity.is_valid() && town.proceduralMaterialEntity.is_alive() &&
			    town.proceduralMaterialEntity
				    .has<ProceduralMaterialComponent>()) {
				const auto &mat =
					town.proceduralMaterialEntity.get<
						ProceduralMaterialComponent>();
				if (!mat.materialName.empty()) {
					materialName = mat.materialName;
					materialEntity =
						town.proceduralMaterialEntity;
					break;
				}
			}
			// Fall back to generated material
			materialName =
				town.materialName.empty() ?
					"TownMaterial_" +
						std::to_string(parent.id()) :
					town.materialName;
			break;
		}
		parent = parent.parent();
	}

	Ogre::LogManager::getSingleton().logMessage(
		"CellGrid: Using material '" + materialName + "' for entity " +
		std::to_string(entity.id()));

	// Build geometry parts
	Procedural::TriangleBuffer floorTb, ceilingTb, extWallTb, intWallTb,
		extWindowsTb, intWindowsTb, extDoorsTb, intDoorsTb, roofTopTb, roofSideTb;

	buildFloorsAndCeilings(grid, floorTb, ceilingTb);
	buildWalls(grid, extWallTb, intWallTb, intWindowsTb);
	buildCorners(grid, extWallTb);
	buildInternalCorners(grid, intWallTb);
	buildDoors(grid, extDoorsTb, intDoorsTb);
	buildWindows(grid, extWindowsTb, intWindowsTb);
	buildRoofs(entity, grid, roofTopTb, roofSideTb);

	// Apply UV mapping for each part (use rect if specified, otherwise default)
	Ogre::LogManager::getSingleton().logMessage(
		"CellGrid: UV mapping - floor: " + grid.floorRectName +
		" ceiling: " + grid.ceilingRectName +
		" extWall: " + grid.extWallRectName);
	applyUVMappingToBuffer(floorTb, grid.floorRectName, materialEntity);
	applyUVMappingToBuffer(ceilingTb, grid.ceilingRectName, materialEntity);
	applyUVMappingToBuffer(extWallTb, grid.extWallRectName, materialEntity);
	applyUVMappingToBuffer(intWallTb, grid.intWallRectName, materialEntity);
	applyUVMappingToBuffer(extWindowsTb, grid.extWallRectName,
			       materialEntity);
	applyUVMappingToBuffer(intWindowsTb, grid.intWallRectName,
			       materialEntity);
	applyUVMappingToBuffer(extDoorsTb, grid.extWallRectName,
			       materialEntity);
	applyUVMappingToBuffer(intDoorsTb, grid.intWallRectName,
			       materialEntity);
	applyUVMappingToBuffer(roofTopTb, grid.roofTopRectName, materialEntity);
	applyUVMappingToBuffer(roofSideTb, grid.roofSideRectName, materialEntity);

	// Generate unique base name
	std::string baseName = "CellGrid_" + std::to_string(entity.id()) + "_" +
			       std::to_string(m_meshCount++);

	// Convert to meshes and create entities
	if (!entity.has<TransformComponent>()) {
		Ogre::LogManager::getSingleton().logMessage(
			"CellGrid: No TransformComponent for entity " +
			std::to_string(entity.id()));
		return;
	}
	auto &transform = entity.get_mut<TransformComponent>();
	if (!transform.node) {
		Ogre::LogManager::getSingleton().logMessage(
			"CellGrid: No SceneNode for entity " +
			std::to_string(entity.id()));
		return;
	}

	// Create meshes for each part
	if (floorTb.getVertices().size() >= 3) {
		meshData.floorMesh = baseName + "_floor";
		auto mesh = convertToMesh(meshData.floorMesh, floorTb,
					  materialName);
		auto entity3d = m_sceneMgr->createEntity(meshData.floorMesh);
		transform.node->attachObject(entity3d);
		meshData.entities.push_back(entity3d);
	}

	if (ceilingTb.getVertices().size() >= 3) {
		meshData.ceilingMesh = baseName + "_ceiling";
		auto mesh = convertToMesh(meshData.ceilingMesh, ceilingTb,
					  materialName);
		auto entity3d = m_sceneMgr->createEntity(meshData.ceilingMesh);
		transform.node->attachObject(entity3d);
		meshData.entities.push_back(entity3d);
	}

	if (extWallTb.getVertices().size() >= 3) {
		meshData.extWallMesh = baseName + "_extWall";
		auto mesh = convertToMesh(meshData.extWallMesh, extWallTb,
					  materialName);
		auto entity3d = m_sceneMgr->createEntity(meshData.extWallMesh);
		transform.node->attachObject(entity3d);
		meshData.entities.push_back(entity3d);
	}

	if (intWallTb.getVertices().size() >= 3) {
		meshData.intWallMesh = baseName + "_intWall";
		auto mesh = convertToMesh(meshData.intWallMesh, intWallTb,
					  materialName);
		auto entity3d = m_sceneMgr->createEntity(meshData.intWallMesh);
		transform.node->attachObject(entity3d);
		meshData.entities.push_back(entity3d);
	}

	if (intWindowsTb.getVertices().size() >= 3) {
		meshData.intWindowsMesh = baseName + "_intWindows";
		auto mesh = convertToMesh(meshData.intWindowsMesh, intWindowsTb,
					  materialName);
		auto entity3d =
			m_sceneMgr->createEntity(meshData.intWindowsMesh);
		transform.node->attachObject(entity3d);
		meshData.entities.push_back(entity3d);
	}

	// Roof top pieces -> attach to SceneNode
	if (roofTopTb.getVertices().size() >= 3) {
		meshData.roofMesh = baseName + "_roofTop";
		auto mesh =
			convertToMesh(meshData.roofMesh, roofTopTb, materialName);
		auto entity3d = m_sceneMgr->createEntity(meshData.roofMesh);
		transform.node->attachObject(entity3d);
		meshData.entities.push_back(entity3d);
	}
	
	// Roof side pieces (trim/gable ends) -> attach to SceneNode
	if (roofSideTb.getVertices().size() >= 3) {
		meshData.roofSideMesh = baseName + "_roofSide";
		auto mesh =
			convertToMesh(meshData.roofSideMesh, roofSideTb, materialName);
		auto entity3d = m_sceneMgr->createEntity(meshData.roofSideMesh);
		transform.node->attachObject(entity3d);
		meshData.entities.push_back(entity3d);
	}

	// External doors
	if (extDoorsTb.getVertices().size() >= 3) {
		meshData.doorMeshes.push_back(baseName + "_extDoors");
		auto mesh = convertToMesh(baseName + "_extDoors", extDoorsTb,
					  materialName);
		auto entity3d =
			m_sceneMgr->createEntity(baseName + "_extDoors");
		transform.node->attachObject(entity3d);
		meshData.entities.push_back(entity3d);
	}

	// Internal doors
	if (intDoorsTb.getVertices().size() >= 3) {
		meshData.doorMeshes.push_back(baseName + "_intDoors");
		auto mesh = convertToMesh(baseName + "_intDoors", intDoorsTb,
					  materialName);
		auto entity3d =
			m_sceneMgr->createEntity(baseName + "_intDoors");
		transform.node->attachObject(entity3d);
		meshData.entities.push_back(entity3d);
	}

	// External windows
	if (extWindowsTb.getVertices().size() >= 3) {
		meshData.windowMeshes.push_back(baseName + "_extWindows");
		auto mesh = convertToMesh(baseName + "_extWindows",
					  extWindowsTb, materialName);
		auto entity3d =
			m_sceneMgr->createEntity(baseName + "_extWindows");
		transform.node->attachObject(entity3d);
		meshData.entities.push_back(entity3d);
	}

	// Internal windows
	if (intWindowsTb.getVertices().size() >= 3) {
		meshData.intWindowsMesh = baseName + "_intWindows";
		auto mesh = convertToMesh(meshData.intWindowsMesh, intWindowsTb,
					  materialName);
		auto entity3d =
			m_sceneMgr->createEntity(meshData.intWindowsMesh);
		transform.node->attachObject(entity3d);
		meshData.entities.push_back(entity3d);
	}

	Ogre::LogManager::getSingleton().logMessage(
		"CellGrid: Built geometry for entity " +
		std::to_string(entity.id()) +
		" floor=" + std::to_string(floorTb.getVertices().size()) +
		" ceiling=" + std::to_string(ceilingTb.getVertices().size()) +
		" extWall=" + std::to_string(extWallTb.getVertices().size()) +
		" intWall=" + std::to_string(intWallTb.getVertices().size()) +
		" intWindows=" +
		std::to_string(intWindowsTb.getVertices().size()) +
		" extWindows=" +
		std::to_string(extWindowsTb.getVertices().size()) +
		" extDoors=" + std::to_string(extDoorsTb.getVertices().size()) +
		" intDoors=" + std::to_string(intDoorsTb.getVertices().size()) +
		" roofTop=" + std::to_string(roofTopTb.getVertices().size()) +
		" roofSide=" + std::to_string(roofSideTb.getVertices().size()));

	// Build window and door frames (creates StaticGeometry region)
	try {
		buildFrames(entity, grid, materialName);
	} catch (const std::exception &e) {
		Ogre::LogManager::getSingleton().logMessage(
			"CellGrid: Error building frames: " +
			std::string(e.what()));
	} catch (...) {
		Ogre::LogManager::getSingleton().logMessage(
			"CellGrid: Unknown error building frames");
	}

	// Build furniture (non-isolated items go into the same StaticGeometry)
	try {
		buildFurniture(entity, grid);
	} catch (const std::exception &e) {
		Ogre::LogManager::getSingleton().logMessage(
			"CellGrid: Error building furniture: " +
			std::string(e.what()));
	} catch (...) {
		Ogre::LogManager::getSingleton().logMessage(
			"CellGrid: Unknown error building 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_valid() && materialEntity.is_alive() && 
	    materialEntity.has<ProceduralMaterialComponent>()) {
		const auto &mat = materialEntity.get<ProceduralMaterialComponent>();
		if (mat.diffuseTextureEntity.is_valid() && mat.diffuseTextureEntity.is_alive() &&
		    mat.diffuseTextureEntity.has<ProceduralTextureComponent>()) {
			auto &meshData = m_entityMeshes[entity.id()];
			meshData.textureEntity = mat.diffuseTextureEntity;
			const auto &tex = mat.diffuseTextureEntity.get<ProceduralTextureComponent>();
			meshData.textureVersion = tex.version;
		}
	}
}

// BitSet structure matching original town.cpp
struct BitSet {
	uint64_t bit;
	float sizeX;
	float sizeY;
	Ogre::Vector3 normal;
	Ogre::Vector3 offset;
	Procedural::TriangleBuffer *tb;
};

// Plane generator matching original town.cpp
// The sizeX/sizeY values from original BitSet are the CORRECT full sizes for PlaneGenerator
// - sizeX = wall height (vertical dimension of the plane)
// - sizeY = wall half-width (horizontal dimension of the plane, gets doubled by PlaneGenerator internally)
static void genPlane(float sizeX, float sizeY, const Ogre::Vector3 &normal,
		     const Ogre::Vector3 &offset,
		     Procedural::TriangleBuffer &tb,
		     const Ogre::Vector3 &origin)
{
	// nsegY is based on the height (sizeX in the original, which is vertical)
	int nsegY = (sizeX < 1.0f) ? 1 : std::max(1, (int)(sizeX / 2.0f));
	Procedural::PlaneGenerator()
		.setNormal(normal)
		.setSizeX(sizeX) // This is the height of the wall
		.setSizeY(
			sizeY) // This is already correct (half-width * 2 = full width)
		.setNumSegX(1)
		.setNumSegY(nsegY)
		.setEnableNormals(true)
		.setPosition(origin + offset)
		.addToTriangleBuffer(tb);
}

void CellGridSystem::buildFloorsAndCeilings(
	const CellGridComponent &grid, Procedural::TriangleBuffer &floorTb,
	Procedural::TriangleBuffer &ceilingTb)
{
	// Original uses 2.0f as half-size for 4.0f cell (cellSize/2)
	// Use same hScale as walls for consistency
	const float hScale = grid.cellSize / 2.0f;
	// Floor Y slightly below internal wall offset to avoid gap
	// Internal wall offset is 0.1f, floor sits slightly below
	const float floorY = 0.2f;
	const float ceilingY = grid.cellHeight - 0.3f;

	for (const auto &cell : grid.cells) {
		Ogre::Vector3 origin = grid.cellToWorld(cell.x, cell.y, cell.z);
		uint64_t flags = cell.flags;

		// Floor: original sizeX=2.0f, sizeY=2.0f (half-sizes), scaled by hScale
		if (flags & CellFlags::Floor) {
			genPlane(2.0f * hScale, 2.0f * hScale,
				 Ogre::Vector3::UNIT_Y,
				 Ogre::Vector3(0, floorY, 0), floorTb, origin);
		}

		// Ceiling: original sizeX=2.0f, sizeY=2.0f (half-sizes), scaled by hScale
		if (flags & CellFlags::Ceiling) {
			genPlane(2.0f * hScale, 2.0f * hScale,
				 Ogre::Vector3::NEGATIVE_UNIT_Y,
				 Ogre::Vector3(0, ceilingY, 0), ceilingTb,
				 origin);
		}
	}
}

void CellGridSystem::buildWalls(const CellGridComponent &grid,
				Procedural::TriangleBuffer &extWallTb,
				Procedural::TriangleBuffer &intWallTb,
				Procedural::TriangleBuffer &intWindowsTb)
{
	// Original tables designed for cellSize=2.0 (2x2 base, 4.0 height)
	// In original: sizeY=2.0f represents FULL cell width (not half!)
	// Scale factor: cellSize / 2.0f (not 4.0f!)
	const float hScale = grid.cellSize / 2.0f; // Horizontal scale
	const float halfCell = 1.0f * hScale; // half of cellSize
	const float cornerWidth = 0.2f; // Fixed corner width, don't scale
	const float solidExtHeight = grid.cellHeight;
	const float solidExtOffset = 0.0f;
	const float solidIntOffset = 0.1f * hScale;
	const float solidIntHeight = grid.cellHeight - 0.3f - solidIntOffset;

	// Tables matching original town.cpp
	// hScale = cellSize / 2.0f (for horizontal dimensions)
	// cornerWidth = 0.2f (fixed, not scaled)
	// Internal wall offset = 0.1f (fixed, matching original)
	const float intWallOffset = 0.1f;
	std::vector<BitSet> bits_solid = {
		// External walls: sizeX=height, sizeY=cellSize (full width = 2.0f * hScale)
		{ CellFlags::WallXNeg,
		  solidExtHeight,
		  2.0f * hScale,
		  Ogre::Vector3::NEGATIVE_UNIT_X,
		  { -1.0f * hScale - cornerWidth,
		    solidExtHeight / 2.0f + solidExtOffset, 0 },
		  &extWallTb },
		{ CellFlags::WallXPos,
		  solidExtHeight,
		  2.0f * hScale,
		  Ogre::Vector3::UNIT_X,
		  { 1.0f * hScale + cornerWidth,
		    solidExtHeight / 2.0f + solidExtOffset, 0 },
		  &extWallTb },
		{ CellFlags::WallZPos,
		  solidExtHeight,
		  2.0f * hScale,
		  Ogre::Vector3::UNIT_Z,
		  { 0, solidExtHeight / 2.0f + solidExtOffset,
		    1.0f * hScale + cornerWidth },
		  &extWallTb },
		{ CellFlags::WallZNeg,
		  solidExtHeight,
		  2.0f * hScale,
		  Ogre::Vector3::NEGATIVE_UNIT_Z,
		  { 0, solidExtHeight / 2.0f + solidExtOffset,
		    -1.0f * hScale - cornerWidth },
		  &extWallTb },
		// Internal walls: sizeY=(2.0f * hScale - 0.2f), offset=+/-(hScale - 0.1f)
		{ CellFlags::IntWallXNeg,
		  solidIntHeight,
		  2.0f * hScale - cornerWidth,
		  Ogre::Vector3::UNIT_X,
		  { -1.0f * hScale + intWallOffset,
		    solidIntHeight / 2.0f + solidIntOffset, 0 },
		  &intWallTb },
		{ CellFlags::IntWallXPos,
		  solidIntHeight,
		  2.0f * hScale - cornerWidth,
		  Ogre::Vector3::NEGATIVE_UNIT_X,
		  { 1.0f * hScale - intWallOffset,
		    solidIntHeight / 2.0f + solidIntOffset, 0 },
		  &intWallTb },
		{ CellFlags::IntWallZPos,
		  solidIntHeight,
		  2.0f * hScale - cornerWidth,
		  Ogre::Vector3::NEGATIVE_UNIT_Z,
		  { 0, solidIntHeight / 2.0f + solidIntOffset,
		    1.0f * hScale - intWallOffset },
		  &intWallTb },
		{ CellFlags::IntWallZNeg,
		  solidIntHeight,
		  2.0f * hScale - cornerWidth,
		  Ogre::Vector3::UNIT_Z,
		  { 0, solidIntHeight / 2.0f + solidIntOffset,
		    -1.0f * hScale + intWallOffset },
		  &intWallTb },
	};

	for (const auto &cell : grid.cells) {
		Ogre::Vector3 origin = grid.cellToWorld(cell.x, cell.y, cell.z);
		uint64_t flags = cell.flags;

		for (const auto &bit : bits_solid) {
			if ((flags & bit.bit) == bit.bit) {
				genPlane(bit.sizeX, bit.sizeY, bit.normal,
					 bit.offset, *bit.tb, origin);
			}
		}
	}
}

std::vector<CellGridSystem::PlacementInfo> CellGridSystem::getFramePlacements(flecs::entity entity) const
{
	std::vector<PlacementInfo> result;
	auto it = m_entityMeshes.find(entity.id());
	if (it == m_entityMeshes.end())
		return result;
	for (const auto &p : it->second.framePlacements) {
		result.push_back({p.meshName, p.position, p.rotation});
	}
	return result;
}

std::vector<CellGridSystem::PlacementInfo> CellGridSystem::getFurniturePlacements(flecs::entity entity) const
{
	std::vector<PlacementInfo> result;
	auto it = m_entityMeshes.find(entity.id());
	if (it == m_entityMeshes.end())
		return result;
	for (const auto &p : it->second.furniturePlacements) {
		result.push_back({p.meshName, p.position, p.rotation});
	}
	return result;
}

void CellGridSystem::buildCorners(const CellGridComponent &grid,
				  Procedural::TriangleBuffer &extWallTb)
{
	// Build external wall corners where walls meet using PlaneGenerator
	// Original tables for cellSize=2.0 (2x2 base), so halfCell=1.0
	const float hScale = grid.cellSize / 2.0f;
	const float halfCell = 1.0f * hScale; // = cellSize / 2
	const float cornerWidth = 0.2f; // Fixed corner width
	const float solidExtHeight = grid.cellHeight;
	const float solidExtOffset = 0.0f;

	// Tables for corners matching original town.cpp bits_ext_corners
	// Format: bit, sizeX (height), sizeY (corner width), normal, offset
	// Corner width is FIXED (0.2f) - don't scale to avoid thin polygons
	// Each corner plane is shifted by cornerWidth/2 toward the corner to close the gap
	const float cornerOffset = cornerWidth / 2.0f;
	std::vector<BitSet> bits_ext_corners = {
		// X- with Z+ corners
		// First corner plane: normal along -X, at X=-(halfCell+cornerWidth), Z=+halfCell+cornerOffset
		{ CellFlags::WallXNeg | CellFlags::WallZPos,
		  solidExtHeight,
		  cornerWidth,
		  Ogre::Vector3::NEGATIVE_UNIT_X,
		  { -halfCell - cornerWidth,
		    solidExtHeight / 2.0f + solidExtOffset,
		    halfCell + cornerOffset },
		  &extWallTb },
		// Second corner plane: normal along +Z, at X=-halfCell-cornerOffset, Z=+(halfCell+cornerWidth)
		{ CellFlags::WallXNeg | CellFlags::WallZPos,
		  solidExtHeight,
		  cornerWidth,
		  Ogre::Vector3::UNIT_Z,
		  { -halfCell - cornerOffset,
		    solidExtHeight / 2.0f + solidExtOffset,
		    halfCell + cornerWidth },
		  &extWallTb },
		// X- with Z- corners
		{ CellFlags::WallXNeg | CellFlags::WallZNeg,
		  solidExtHeight,
		  cornerWidth,
		  Ogre::Vector3::NEGATIVE_UNIT_X,
		  { -halfCell - cornerWidth,
		    solidExtHeight / 2.0f + solidExtOffset,
		    -halfCell - cornerOffset },
		  &extWallTb },
		{ CellFlags::WallXNeg | CellFlags::WallZNeg,
		  solidExtHeight,
		  cornerWidth,
		  Ogre::Vector3::NEGATIVE_UNIT_Z,
		  { -halfCell - cornerOffset,
		    solidExtHeight / 2.0f + solidExtOffset,
		    -halfCell - cornerWidth },
		  &extWallTb },
		// X+ with Z+ corners
		{ CellFlags::WallXPos | CellFlags::WallZPos,
		  solidExtHeight,
		  cornerWidth,
		  Ogre::Vector3::UNIT_X,
		  { halfCell + cornerWidth,
		    solidExtHeight / 2.0f + solidExtOffset,
		    halfCell + cornerOffset },
		  &extWallTb },
		{ CellFlags::WallXPos | CellFlags::WallZPos,
		  solidExtHeight,
		  cornerWidth,
		  Ogre::Vector3::UNIT_Z,
		  { halfCell + cornerOffset,
		    solidExtHeight / 2.0f + solidExtOffset,
		    halfCell + cornerWidth },
		  &extWallTb },
		// X+ with Z- corners
		{ CellFlags::WallXPos | CellFlags::WallZNeg,
		  solidExtHeight,
		  cornerWidth,
		  Ogre::Vector3::UNIT_X,
		  { halfCell + cornerWidth,
		    solidExtHeight / 2.0f + solidExtOffset,
		    -halfCell - cornerOffset },
		  &extWallTb },
		{ CellFlags::WallXPos | CellFlags::WallZNeg,
		  solidExtHeight,
		  cornerWidth,
		  Ogre::Vector3::NEGATIVE_UNIT_Z,
		  { halfCell + cornerOffset,
		    solidExtHeight / 2.0f + solidExtOffset,
		    -halfCell - cornerWidth },
		  &extWallTb },
	};

	for (const auto &cell : grid.cells) {
		Ogre::Vector3 origin = grid.cellToWorld(cell.x, cell.y, cell.z);
		// Use combined masks that include walls, doors, and windows
		uint64_t xNegFlags = cell.flags & CellFlags::AllXNeg;
		uint64_t xPosFlags = cell.flags & CellFlags::AllXPos;
		uint64_t zNegFlags = cell.flags & CellFlags::AllZNeg;
		uint64_t zPosFlags = cell.flags & CellFlags::AllZPos;

		for (const auto &bit : bits_ext_corners) {
			// Check for corner based on which features are present
			bool hasXNeg = (bit.bit & CellFlags::AllXNeg) &&
				       xNegFlags;
			bool hasXPos = (bit.bit & CellFlags::AllXPos) &&
				       xPosFlags;
			bool hasZNeg = (bit.bit & CellFlags::AllZNeg) &&
				       zNegFlags;
			bool hasZPos = (bit.bit & CellFlags::AllZPos) &&
				       zPosFlags;

			// Corner exists if both adjacent sides have features
			bool hasCorner =
				(hasXNeg && hasZPos) || (hasXNeg && hasZNeg) ||
				(hasXPos && hasZPos) || (hasXPos && hasZNeg);

			if (hasCorner) {
				genPlane(bit.sizeX, bit.sizeY, bit.normal,
					 bit.offset, *bit.tb, origin);
			}
		}
	}
}

void CellGridSystem::buildInternalCorners(const CellGridComponent &grid,
					  Procedural::TriangleBuffer &intWallTb)
{
	// Build internal wall corners where internal walls meet
	// Original tables for cellSize=2.0 (2x2 base), so halfCell=1.0
	const float hScale = grid.cellSize / 2.0f;
	const float halfCell = 1.0f * hScale;
	const float cornerWidth = 0.2f; // Fixed corner width
	const float solidIntOffset = 0.1f; // Fixed offset matching floor
	const float solidIntHeight = grid.cellHeight - 0.3f - solidIntOffset;

	// Internal wall width = 2.0f * hScale - 0.2f = full width - corner offset on both sides
	const float intWallWidth = 2.0f * hScale - cornerWidth;

	// bits_int_corners1 - corners extending in positive direction from each wall
	// Format: bit, sizeX (height), sizeY (corner width), normal, offset
	// Normals flipped to match internal wall normals
	std::vector<BitSet> bits_int_corners1 = {
		// IntWallXNeg (flag 14): corner at Z+ side
		{ CellFlags::IntWallXNeg,
		  solidIntHeight,
		  cornerWidth,
		  Ogre::Vector3::UNIT_X,
		  { -halfCell + cornerWidth / 2,
		    solidIntHeight / 2.0f + solidIntOffset,
		    (intWallWidth + cornerWidth) / 2.0f },
		  &intWallTb },
		// IntWallXPos (flag 15): corner at Z+ side
		{ CellFlags::IntWallXPos,
		  solidIntHeight,
		  cornerWidth,
		  Ogre::Vector3::NEGATIVE_UNIT_X,
		  { halfCell - cornerWidth / 2,
		    solidIntHeight / 2.0f + solidIntOffset,
		    (intWallWidth + cornerWidth) / 2.0f },
		  &intWallTb },
		// IntWallZPos (flag 16): corner at X+ side
		{ CellFlags::IntWallZPos,
		  solidIntHeight,
		  cornerWidth,
		  Ogre::Vector3::NEGATIVE_UNIT_Z,
		  { (intWallWidth + cornerWidth) / 2.0f,
		    solidIntHeight / 2.0f + solidIntOffset,
		    halfCell - cornerWidth / 2 },
		  &intWallTb },
		// IntWallZNeg (flag 17): corner at X+ side
		{ CellFlags::IntWallZNeg,
		  solidIntHeight,
		  cornerWidth,
		  Ogre::Vector3::UNIT_Z,
		  { (intWallWidth + cornerWidth) / 2.0f,
		    solidIntHeight / 2.0f + solidIntOffset,
		    -halfCell + cornerWidth / 2 },
		  &intWallTb },
	};

	// bits_int_corners2 - corners extending in negative direction from each wall
	std::vector<BitSet> bits_int_corners2 = {
		// IntWallXNeg (flag 14): corner at Z- side
		{ CellFlags::IntWallXNeg,
		  solidIntHeight,
		  cornerWidth,
		  Ogre::Vector3::UNIT_X,
		  { -halfCell + cornerWidth / 2,
		    solidIntHeight / 2.0f + solidIntOffset,
		    -(intWallWidth + cornerWidth) / 2.0f },
		  &intWallTb },
		// IntWallXPos (flag 15): corner at Z- side
		{ CellFlags::IntWallXPos,
		  solidIntHeight,
		  cornerWidth,
		  Ogre::Vector3::NEGATIVE_UNIT_X,
		  { halfCell - cornerWidth / 2,
		    solidIntHeight / 2.0f + solidIntOffset,
		    -(intWallWidth + cornerWidth) / 2.0f },
		  &intWallTb },
		// IntWallZPos (flag 16): corner at X- side
		{ CellFlags::IntWallZPos,
		  solidIntHeight,
		  cornerWidth,
		  Ogre::Vector3::NEGATIVE_UNIT_Z,
		  { -(intWallWidth + cornerWidth) / 2.0f,
		    solidIntHeight / 2.0f + solidIntOffset,
		    halfCell - cornerWidth / 2 },
		  &intWallTb },
		// IntWallZNeg (flag 17): corner at X- side
		{ CellFlags::IntWallZNeg,
		  solidIntHeight,
		  cornerWidth,
		  Ogre::Vector3::UNIT_Z,
		  { -(intWallWidth + cornerWidth) / 2.0f,
		    solidIntHeight / 2.0f + solidIntOffset,
		    -halfCell + cornerWidth / 2 },
		  &intWallTb },
	};

	for (const auto &cell : grid.cells) {
		Ogre::Vector3 origin = grid.cellToWorld(cell.x, cell.y, cell.z);
		// Use combined masks that include walls, doors, and windows
		uint64_t intXNegFlags = cell.flags & CellFlags::AllIntXNeg;
		uint64_t intXPosFlags = cell.flags & CellFlags::AllIntXPos;
		uint64_t intZNegFlags = cell.flags & CellFlags::AllIntZNeg;
		uint64_t intZPosFlags = cell.flags & CellFlags::AllIntZPos;

		// Process bits_int_corners1 with conflict resolution
		for (const auto &bit : bits_int_corners1) {
			// Check if the corresponding direction has any feature (wall/door/window)
			bool hasFeature = false;
			if ((bit.bit & CellFlags::AllIntXNeg) && intXNegFlags)
				hasFeature = true;
			if ((bit.bit & CellFlags::AllIntXPos) && intXPosFlags)
				hasFeature = true;
			if ((bit.bit & CellFlags::AllIntZPos) && intZPosFlags)
				hasFeature = true;
			if ((bit.bit & CellFlags::AllIntZNeg) && intZNegFlags)
				hasFeature = true;

			if (hasFeature) {
				// Conflict resolution - skip if adjacent wall would overlap
				bool skip = false;
				if ((bit.bit & CellFlags::AllIntXNeg) &&
				    intZPosFlags)
					skip = true;
				if ((bit.bit & CellFlags::AllIntXPos) &&
				    intZPosFlags)
					skip = true;
				if ((bit.bit & CellFlags::AllIntZPos) &&
				    intXPosFlags)
					skip = true;
				if ((bit.bit & CellFlags::AllIntZNeg) &&
				    intXPosFlags)
					skip = true;

				if (!skip) {
					genPlane(bit.sizeX, bit.sizeY,
						 bit.normal, bit.offset,
						 *bit.tb, origin);
				}
			}
		}

		// Process bits_int_corners2 with conflict resolution
		for (const auto &bit : bits_int_corners2) {
			bool hasFeature = false;
			if ((bit.bit & CellFlags::AllIntXNeg) && intXNegFlags)
				hasFeature = true;
			if ((bit.bit & CellFlags::AllIntXPos) && intXPosFlags)
				hasFeature = true;
			if ((bit.bit & CellFlags::AllIntZPos) && intZPosFlags)
				hasFeature = true;
			if ((bit.bit & CellFlags::AllIntZNeg) && intZNegFlags)
				hasFeature = true;

			if (hasFeature) {
				// Conflict resolution
				bool skip = false;
				if ((bit.bit & CellFlags::AllIntXNeg) &&
				    intZNegFlags)
					skip = true;
				if ((bit.bit & CellFlags::AllIntXPos) &&
				    intZNegFlags)
					skip = true;
				if ((bit.bit & CellFlags::AllIntZPos) &&
				    intXNegFlags)
					skip = true;
				if ((bit.bit & CellFlags::AllIntZNeg) &&
				    intXNegFlags)
					skip = true;

				if (!skip) {
					genPlane(bit.sizeX, bit.sizeY,
						 bit.normal, bit.offset,
						 *bit.tb, origin);
				}
			}
		}
	}
}

void CellGridSystem::buildDoors(const CellGridComponent &grid,
				Procedural::TriangleBuffer &extDoorsTb,
				Procedural::TriangleBuffer &intDoorsTb)
{
	// Based on original town.cpp door construction
	// Uses plane-based door frames around the opening
	const float hScale = grid.cellSize / 2.0f;
	const float halfCell = 1.0f * hScale;
	const float cornerWidth = 0.2f;
	const float solidExtOffset = 0.0f;
	const float solidIntOffset = 0.1f;
	const float solidExtHeight = grid.cellHeight;
	const float solidIntHeight = grid.cellHeight - 0.3f - solidIntOffset;

	// Door dimensions (original values scaled)
	const float doorWidth = 1.4f * hScale;
	const float doorHeightBase = 3.0f;

	for (const auto &cell : grid.cells) {
		Ogre::Vector3 origin = grid.cellToWorld(cell.x, cell.y, cell.z);

		// External doors - append directly to extDoorsTb
		if (cell.hasFlag(CellFlags::DoorXNeg) ||
		    cell.hasFlag(CellFlags::DoorXPos) ||
		    cell.hasFlag(CellFlags::DoorZNeg) ||
		    cell.hasFlag(CellFlags::DoorZPos)) {
			float sideWidth = (2.0f * hScale - doorWidth) / 2.0f;
			float moffset = (2.0f * hScale - sideWidth) / 2.0f;
			float doorHeight = doorHeightBase - solidExtOffset;
			float doorTopHeight = solidExtHeight - doorHeight;
			float baseOffsetY = solidExtOffset;

			if (cell.hasFlag(CellFlags::DoorXNeg)) {
				// Left side frame
				genPlane(
					doorHeight, sideWidth,
					Ogre::Vector3::NEGATIVE_UNIT_X,
					Ogre::Vector3(-halfCell - cornerWidth,
						      baseOffsetY +
							      doorHeight / 2.0f,
						      -moffset),
					extDoorsTb, origin);
				// Right side frame
				genPlane(
					doorHeight, sideWidth,
					Ogre::Vector3::NEGATIVE_UNIT_X,
					Ogre::Vector3(-halfCell - cornerWidth,
						      baseOffsetY +
							      doorHeight / 2.0f,
						      moffset),
					extDoorsTb, origin);
				// Top frame
				genPlane(doorTopHeight, 2.0f * hScale,
					 Ogre::Vector3::NEGATIVE_UNIT_X,
					 Ogre::Vector3(
						 -halfCell - cornerWidth,
						 baseOffsetY + doorHeight +
							 doorTopHeight / 2.0f,
						 0),
					 extDoorsTb, origin);
			}
			if (cell.hasFlag(CellFlags::DoorXPos)) {
				genPlane(
					doorHeight, sideWidth,
					Ogre::Vector3::UNIT_X,
					Ogre::Vector3(halfCell + cornerWidth,
						      baseOffsetY +
							      doorHeight / 2.0f,
						      -moffset),
					extDoorsTb, origin);
				genPlane(
					doorHeight, sideWidth,
					Ogre::Vector3::UNIT_X,
					Ogre::Vector3(halfCell + cornerWidth,
						      baseOffsetY +
							      doorHeight / 2.0f,
						      moffset),
					extDoorsTb, origin);
				genPlane(doorTopHeight, 2.0f * hScale,
					 Ogre::Vector3::UNIT_X,
					 Ogre::Vector3(
						 halfCell + cornerWidth,
						 baseOffsetY + doorHeight +
							 doorTopHeight / 2.0f,
						 0),
					 extDoorsTb, origin);
			}
			if (cell.hasFlag(CellFlags::DoorZNeg)) {
				genPlane(
					doorHeight, sideWidth,
					Ogre::Vector3::NEGATIVE_UNIT_Z,
					Ogre::Vector3(-moffset,
						      baseOffsetY +
							      doorHeight / 2.0f,
						      -halfCell - cornerWidth),
					extDoorsTb, origin);
				genPlane(
					doorHeight, sideWidth,
					Ogre::Vector3::NEGATIVE_UNIT_Z,
					Ogre::Vector3(moffset,
						      baseOffsetY +
							      doorHeight / 2.0f,
						      -halfCell - cornerWidth),
					extDoorsTb, origin);
				genPlane(doorTopHeight, 2.0f * hScale,
					 Ogre::Vector3::NEGATIVE_UNIT_Z,
					 Ogre::Vector3(
						 0,
						 baseOffsetY + doorHeight +
							 doorTopHeight / 2.0f,
						 -halfCell - cornerWidth),
					 extDoorsTb, origin);
			}
			if (cell.hasFlag(CellFlags::DoorZPos)) {
				genPlane(
					doorHeight, sideWidth,
					Ogre::Vector3::UNIT_Z,
					Ogre::Vector3(-moffset,
						      baseOffsetY +
							      doorHeight / 2.0f,
						      halfCell + cornerWidth),
					extDoorsTb, origin);
				genPlane(
					doorHeight, sideWidth,
					Ogre::Vector3::UNIT_Z,
					Ogre::Vector3(moffset,
						      baseOffsetY +
							      doorHeight / 2.0f,
						      halfCell + cornerWidth),
					extDoorsTb, origin);
				genPlane(doorTopHeight, 2.0f * hScale,
					 Ogre::Vector3::UNIT_Z,
					 Ogre::Vector3(
						 0,
						 baseOffsetY + doorHeight +
							 doorTopHeight / 2.0f,
						 halfCell + cornerWidth),
					 extDoorsTb, origin);
			}
		}

		// Internal doors - append directly to intDoorsTb
		if (cell.hasFlag(CellFlags::IntDoorXNeg) ||
		    cell.hasFlag(CellFlags::IntDoorXPos) ||
		    cell.hasFlag(CellFlags::IntDoorZNeg) ||
		    cell.hasFlag(CellFlags::IntDoorZPos)) {
			float sideWidth =
				(2.0f * hScale - 0.2f - doorWidth) / 2.0f;
			float moffset =
				(2.0f * hScale - 0.2f - sideWidth) / 2.0f;
			float doorHeight = doorHeightBase - solidIntOffset;
			float doorTopHeight = solidIntHeight - doorHeight;
			float baseOffsetY = solidIntOffset;

			if (cell.hasFlag(CellFlags::IntDoorXNeg)) {
				genPlane(
					doorHeight, sideWidth,
					Ogre::Vector3::UNIT_X,
					Ogre::Vector3(-halfCell + 0.1f,
						      baseOffsetY +
							      doorHeight / 2.0f,
						      -moffset),
					intDoorsTb, origin);
				genPlane(
					doorHeight, sideWidth,
					Ogre::Vector3::UNIT_X,
					Ogre::Vector3(-halfCell + 0.1f,
						      baseOffsetY +
							      doorHeight / 2.0f,
						      moffset),
					intDoorsTb, origin);
				genPlane(doorTopHeight, 2.0f * hScale - 0.2f,
					 Ogre::Vector3::UNIT_X,
					 Ogre::Vector3(
						 -halfCell + 0.1f,
						 baseOffsetY + doorHeight +
							 doorTopHeight / 2.0f,
						 0),
					 intDoorsTb, origin);
			}
			if (cell.hasFlag(CellFlags::IntDoorXPos)) {
				genPlane(
					doorHeight, sideWidth,
					Ogre::Vector3::NEGATIVE_UNIT_X,
					Ogre::Vector3(halfCell - 0.1f,
						      baseOffsetY +
							      doorHeight / 2.0f,
						      -moffset),
					intDoorsTb, origin);
				genPlane(
					doorHeight, sideWidth,
					Ogre::Vector3::NEGATIVE_UNIT_X,
					Ogre::Vector3(halfCell - 0.1f,
						      baseOffsetY +
							      doorHeight / 2.0f,
						      moffset),
					intDoorsTb, origin);
				genPlane(doorTopHeight, 2.0f * hScale - 0.2f,
					 Ogre::Vector3::NEGATIVE_UNIT_X,
					 Ogre::Vector3(
						 halfCell - 0.1f,
						 baseOffsetY + doorHeight +
							 doorTopHeight / 2.0f,
						 0),
					 intDoorsTb, origin);
			}
			if (cell.hasFlag(CellFlags::IntDoorZNeg)) {
				genPlane(
					doorHeight, sideWidth,
					Ogre::Vector3::UNIT_Z,
					Ogre::Vector3(-moffset,
						      baseOffsetY +
							      doorHeight / 2.0f,
						      -halfCell + 0.1f),
					intDoorsTb, origin);
				genPlane(
					doorHeight, sideWidth,
					Ogre::Vector3::UNIT_Z,
					Ogre::Vector3(moffset,
						      baseOffsetY +
							      doorHeight / 2.0f,
						      -halfCell + 0.1f),
					intDoorsTb, origin);
				genPlane(doorTopHeight, 2.0f * hScale - 0.2f,
					 Ogre::Vector3::UNIT_Z,
					 Ogre::Vector3(
						 0,
						 baseOffsetY + doorHeight +
							 doorTopHeight / 2.0f,
						 -halfCell + 0.1f),
					 intDoorsTb, origin);
			}
			if (cell.hasFlag(CellFlags::IntDoorZPos)) {
				genPlane(
					doorHeight, sideWidth,
					Ogre::Vector3::NEGATIVE_UNIT_Z,
					Ogre::Vector3(-moffset,
						      baseOffsetY +
							      doorHeight / 2.0f,
						      halfCell - 0.1f),
					intDoorsTb, origin);
				genPlane(
					doorHeight, sideWidth,
					Ogre::Vector3::NEGATIVE_UNIT_Z,
					Ogre::Vector3(moffset,
						      baseOffsetY +
							      doorHeight / 2.0f,
						      halfCell - 0.1f),
					intDoorsTb, origin);
				genPlane(doorTopHeight, 2.0f * hScale - 0.2f,
					 Ogre::Vector3::NEGATIVE_UNIT_Z,
					 Ogre::Vector3(
						 0,
						 baseOffsetY + doorHeight +
							 doorTopHeight / 2.0f,
						 halfCell - 0.1f),
					 intDoorsTb, origin);
			}
		}
	}
}

void CellGridSystem::buildWindows(const CellGridComponent &grid,
				  Procedural::TriangleBuffer &extWindowsTb,
				  Procedural::TriangleBuffer &intWindowsTb)
{
	// Based on original town.cpp window construction
	// Uses plane-based window frames around the opening
	const float hScale = grid.cellSize / 2.0f;
	const float halfCell = 1.0f * hScale;
	const float cornerWidth = 0.2f;
	const float solidExtOffset = 0.0f;
	const float solidIntOffset = 0.1f;

	// Window dimensions (original values scaled)
	// Original: windowBottomOffset = 0.8f, windowHeight = 2.0f
	// Window Y center = 0.8f + 2.0f/2 = 1.8f for cellHeight=4.0f
	const float windowWidth = 1.6f * hScale;
	const float windowHeight = 2.0f * (grid.cellHeight / 4.0f);
	const float windowBottomOffset = 0.8f * (grid.cellHeight / 4.0f);
	const float windowY = windowBottomOffset + windowHeight / 2.0f;

	for (const auto &cell : grid.cells) {
		Ogre::Vector3 origin = grid.cellToWorld(cell.x, cell.y, cell.z);

		// External windows - append directly to extWindowsTb
		if (cell.hasFlag(CellFlags::WindowXNeg) ||
		    cell.hasFlag(CellFlags::WindowXPos) ||
		    cell.hasFlag(CellFlags::WindowZNeg) ||
		    cell.hasFlag(CellFlags::WindowZPos)) {
			// External cell width = 2.0 * hScale (full cell size)
			float externalCellWidth = 2.0f * hScale;
			float sideWidth =
				(externalCellWidth - windowWidth) / 2.0f;
			// Window frame pieces to match full wall height
			// Bottom piece: from solidExtOffset to window bottom
			float bottomSize = windowBottomOffset - solidExtOffset;
			// Mid piece (window opening height): windowHeight
			// Top piece: remaining height to ceiling (external walls go to full cellHeight)
			float topSize = grid.cellHeight - windowBottomOffset -
					windowHeight;
			// Side pieces span full height: bottom + window + top = cellHeight
			float sideHeight = bottomSize + windowHeight + topSize;
			float offsetY = solidExtOffset;

			if (cell.hasFlag(CellFlags::WindowXNeg)) {
				// Left side - spans full height (bottom + window + top)
				genPlane(sideHeight, sideWidth,
					 Ogre::Vector3::NEGATIVE_UNIT_X,
					 Ogre::Vector3(-halfCell - cornerWidth,
						       offsetY + sideHeight /
									 2.0f,
						       -windowWidth / 2 -
							       sideWidth / 2),
					 extWindowsTb, origin);
				// Right side - spans full height (bottom + window + top)
				genPlane(sideHeight, sideWidth,
					 Ogre::Vector3::NEGATIVE_UNIT_X,
					 Ogre::Vector3(-halfCell - cornerWidth,
						       offsetY + sideHeight /
									 2.0f,
						       windowWidth / 2 +
							       sideWidth / 2),
					 extWindowsTb, origin);
				// Top
				genPlane(topSize, sideWidth,
					 Ogre::Vector3::NEGATIVE_UNIT_X,
					 Ogre::Vector3(
						 -halfCell - cornerWidth,
						 windowY + windowHeight / 2 +
							 topSize / 2,
						 -windowWidth / 2 -
							 sideWidth / 2),
					 extWindowsTb, origin);
				genPlane(topSize, sideWidth,
					 Ogre::Vector3::NEGATIVE_UNIT_X,
					 Ogre::Vector3(
						 -halfCell - cornerWidth,
						 windowY + windowHeight / 2 +
							 topSize / 2,
						 windowWidth / 2 +
							 sideWidth / 2),
					 extWindowsTb, origin);
				// Bottom - Y position includes solidExtOffset
				genPlane(bottomSize, sideWidth,
					 Ogre::Vector3::NEGATIVE_UNIT_X,
					 Ogre::Vector3(-halfCell - cornerWidth,
						       solidExtOffset +
							       bottomSize / 2,
						       -windowWidth / 2 -
							       sideWidth / 2),
					 extWindowsTb, origin);
				genPlane(bottomSize, sideWidth,
					 Ogre::Vector3::NEGATIVE_UNIT_X,
					 Ogre::Vector3(-halfCell - cornerWidth,
						       solidExtOffset +
							       bottomSize / 2,
						       windowWidth / 2 +
							       sideWidth / 2),
					 extWindowsTb, origin);
				// Top center
				genPlane(topSize, windowWidth,
					 Ogre::Vector3::NEGATIVE_UNIT_X,
					 Ogre::Vector3(
						 -halfCell - cornerWidth,
						 windowY + windowHeight / 2 +
							 topSize / 2,
						 0),
					 extWindowsTb, origin);
				// Bottom center - Y position includes solidExtOffset
				genPlane(bottomSize, windowWidth,
					 Ogre::Vector3::NEGATIVE_UNIT_X,
					 Ogre::Vector3(-halfCell - cornerWidth,
						       solidExtOffset +
							       bottomSize / 2,
						       0),
					 extWindowsTb, origin);
			}
			if (cell.hasFlag(CellFlags::WindowXPos)) {
				// Left side - spans full height
				genPlane(sideHeight, sideWidth,
					 Ogre::Vector3::UNIT_X,
					 Ogre::Vector3(halfCell + cornerWidth,
						       offsetY + sideHeight /
									 2.0f,
						       -windowWidth / 2 -
							       sideWidth / 2),
					 extWindowsTb, origin);
				// Right side - spans full height
				genPlane(sideHeight, sideWidth,
					 Ogre::Vector3::UNIT_X,
					 Ogre::Vector3(halfCell + cornerWidth,
						       offsetY + sideHeight /
									 2.0f,
						       windowWidth / 2 +
							       sideWidth / 2),
					 extWindowsTb, origin);
				genPlane(topSize, sideWidth,
					 Ogre::Vector3::UNIT_X,
					 Ogre::Vector3(
						 halfCell + cornerWidth,
						 windowY + windowHeight / 2 +
							 topSize / 2,
						 -windowWidth / 2 -
							 sideWidth / 2),
					 extWindowsTb, origin);
				genPlane(topSize, sideWidth,
					 Ogre::Vector3::UNIT_X,
					 Ogre::Vector3(
						 halfCell + cornerWidth,
						 windowY + windowHeight / 2 +
							 topSize / 2,
						 windowWidth / 2 +
							 sideWidth / 2),
					 extWindowsTb, origin);
				genPlane(bottomSize, sideWidth,
					 Ogre::Vector3::UNIT_X,
					 Ogre::Vector3(halfCell + cornerWidth,
						       solidExtOffset +
							       bottomSize / 2,
						       -windowWidth / 2 -
							       sideWidth / 2),
					 extWindowsTb, origin);
				genPlane(bottomSize, sideWidth,
					 Ogre::Vector3::UNIT_X,
					 Ogre::Vector3(halfCell + cornerWidth,
						       solidExtOffset +
							       bottomSize / 2,
						       windowWidth / 2 +
							       sideWidth / 2),
					 extWindowsTb, origin);
				genPlane(topSize, windowWidth,
					 Ogre::Vector3::UNIT_X,
					 Ogre::Vector3(
						 halfCell + cornerWidth,
						 windowY + windowHeight / 2 +
							 topSize / 2,
						 0),
					 extWindowsTb, origin);
				genPlane(bottomSize, windowWidth,
					 Ogre::Vector3::UNIT_X,
					 Ogre::Vector3(halfCell + cornerWidth,
						       solidExtOffset +
							       bottomSize / 2,
						       0),
					 extWindowsTb, origin);
			}
			if (cell.hasFlag(CellFlags::WindowZNeg)) {
				// Left side - spans full height
				genPlane(sideHeight, sideWidth,
					 Ogre::Vector3::NEGATIVE_UNIT_Z,
					 Ogre::Vector3(-windowWidth / 2 -
							       sideWidth / 2,
						       offsetY + sideHeight /
									 2.0f,
						       -halfCell - cornerWidth),
					 extWindowsTb, origin);
				// Right side - spans full height
				genPlane(sideHeight, sideWidth,
					 Ogre::Vector3::NEGATIVE_UNIT_Z,
					 Ogre::Vector3(windowWidth / 2 +
							       sideWidth / 2,
						       offsetY + sideHeight /
									 2.0f,
						       -halfCell - cornerWidth),
					 extWindowsTb, origin);
				genPlane(topSize, sideWidth,
					 Ogre::Vector3::NEGATIVE_UNIT_Z,
					 Ogre::Vector3(
						 -windowWidth / 2 -
							 sideWidth / 2,
						 windowY + windowHeight / 2 +
							 topSize / 2,
						 -halfCell - cornerWidth),
					 extWindowsTb, origin);
				genPlane(
					topSize, sideWidth,
					Ogre::Vector3::NEGATIVE_UNIT_Z,
					Ogre::Vector3(
						windowWidth / 2 + sideWidth / 2,
						windowY + windowHeight / 2 +
							topSize / 2,
						-halfCell - cornerWidth),
					extWindowsTb, origin);
				genPlane(bottomSize, sideWidth,
					 Ogre::Vector3::NEGATIVE_UNIT_Z,
					 Ogre::Vector3(-windowWidth / 2 -
							       sideWidth / 2,
						       solidExtOffset +
							       bottomSize / 2,
						       -halfCell - cornerWidth),
					 extWindowsTb, origin);
				genPlane(bottomSize, sideWidth,
					 Ogre::Vector3::NEGATIVE_UNIT_Z,
					 Ogre::Vector3(windowWidth / 2 +
							       sideWidth / 2,
						       solidExtOffset +
							       bottomSize / 2,
						       -halfCell - cornerWidth),
					 extWindowsTb, origin);
				genPlane(topSize, windowWidth,
					 Ogre::Vector3::NEGATIVE_UNIT_Z,
					 Ogre::Vector3(
						 0,
						 windowY + windowHeight / 2 +
							 topSize / 2,
						 -halfCell - cornerWidth),
					 extWindowsTb, origin);
				genPlane(bottomSize, windowWidth,
					 Ogre::Vector3::NEGATIVE_UNIT_Z,
					 Ogre::Vector3(0,
						       solidExtOffset +
							       bottomSize / 2,
						       -halfCell - cornerWidth),
					 extWindowsTb, origin);
			}
			if (cell.hasFlag(CellFlags::WindowZPos)) {
				// Left side - spans full height
				genPlane(sideHeight, sideWidth,
					 Ogre::Vector3::UNIT_Z,
					 Ogre::Vector3(-windowWidth / 2 -
							       sideWidth / 2,
						       offsetY + sideHeight /
									 2.0f,
						       halfCell + cornerWidth),
					 extWindowsTb, origin);
				// Right side - spans full height
				genPlane(sideHeight, sideWidth,
					 Ogre::Vector3::UNIT_Z,
					 Ogre::Vector3(windowWidth / 2 +
							       sideWidth / 2,
						       offsetY + sideHeight /
									 2.0f,
						       halfCell + cornerWidth),
					 extWindowsTb, origin);
				genPlane(topSize, sideWidth,
					 Ogre::Vector3::UNIT_Z,
					 Ogre::Vector3(
						 -windowWidth / 2 -
							 sideWidth / 2,
						 windowY + windowHeight / 2 +
							 topSize / 2,
						 halfCell + cornerWidth),
					 extWindowsTb, origin);
				genPlane(
					topSize, sideWidth,
					Ogre::Vector3::UNIT_Z,
					Ogre::Vector3(
						windowWidth / 2 + sideWidth / 2,
						windowY + windowHeight / 2 +
							topSize / 2,
						halfCell + cornerWidth),
					extWindowsTb, origin);
				genPlane(bottomSize, sideWidth,
					 Ogre::Vector3::UNIT_Z,
					 Ogre::Vector3(-windowWidth / 2 -
							       sideWidth / 2,
						       solidExtOffset +
							       bottomSize / 2,
						       halfCell + cornerWidth),
					 extWindowsTb, origin);
				genPlane(bottomSize, sideWidth,
					 Ogre::Vector3::UNIT_Z,
					 Ogre::Vector3(windowWidth / 2 +
							       sideWidth / 2,
						       solidExtOffset +
							       bottomSize / 2,
						       halfCell + cornerWidth),
					 extWindowsTb, origin);
				genPlane(topSize, windowWidth,
					 Ogre::Vector3::UNIT_Z,
					 Ogre::Vector3(
						 0,
						 windowY + windowHeight / 2 +
							 topSize / 2,
						 halfCell + cornerWidth),
					 extWindowsTb, origin);
				genPlane(bottomSize, windowWidth,
					 Ogre::Vector3::UNIT_Z,
					 Ogre::Vector3(0,
						       solidExtOffset +
							       bottomSize / 2,
						       halfCell + cornerWidth),
					 extWindowsTb, origin);
			}
		}

		// Internal windows - append directly to intWindowsTb
		if (cell.hasFlag(CellFlags::IntWindowXNeg) ||
		    cell.hasFlag(CellFlags::IntWindowXPos) ||
		    cell.hasFlag(CellFlags::IntWindowZNeg) ||
		    cell.hasFlag(CellFlags::IntWindowZPos)) {
			float intWallOffset = 0.1f;
			// Internal cell width = 2.0 * hScale - 2 * intWallOffset (1.8f for cellSize=4.0)
			float internalCellWidth =
				2.0f * hScale - 2.0f * intWallOffset;
			float sideWidth =
				(internalCellWidth - windowWidth) / 2.0f;
			// Internal windows start at solidIntOffset, so bottom piece is shorter
			float bottomSize = windowBottomOffset - solidIntOffset;
			float topSize = grid.cellHeight - windowY -
					windowHeight / 2.0f - 0.3f;

			if (cell.hasFlag(CellFlags::IntWindowXNeg)) {
				genPlane(
					windowHeight, sideWidth,
					Ogre::Vector3::UNIT_X,
					Ogre::Vector3(-halfCell + intWallOffset,
						      windowY,
						      -windowWidth / 2 -
							      sideWidth / 2),
					intWindowsTb, origin);
				genPlane(
					windowHeight, sideWidth,
					Ogre::Vector3::UNIT_X,
					Ogre::Vector3(-halfCell + intWallOffset,
						      windowY,
						      windowWidth / 2 +
							      sideWidth / 2),
					intWindowsTb, origin);
				genPlane(topSize, sideWidth,
					 Ogre::Vector3::UNIT_X,
					 Ogre::Vector3(
						 -halfCell + intWallOffset,
						 windowY + windowHeight / 2 +
							 topSize / 2,
						 -windowWidth / 2 -
							 sideWidth / 2),
					 intWindowsTb, origin);
				genPlane(topSize, sideWidth,
					 Ogre::Vector3::UNIT_X,
					 Ogre::Vector3(
						 -halfCell + intWallOffset,
						 windowY + windowHeight / 2 +
							 topSize / 2,
						 windowWidth / 2 +
							 sideWidth / 2),
					 intWindowsTb, origin);
				genPlane(
					bottomSize, sideWidth,
					Ogre::Vector3::UNIT_X,
					Ogre::Vector3(-halfCell + intWallOffset,
						      solidIntOffset +
							      bottomSize / 2,
						      -windowWidth / 2 -
							      sideWidth / 2),
					intWindowsTb, origin);
				genPlane(
					bottomSize, sideWidth,
					Ogre::Vector3::UNIT_X,
					Ogre::Vector3(-halfCell + intWallOffset,
						      solidIntOffset +
							      bottomSize / 2,
						      windowWidth / 2 +
							      sideWidth / 2),
					intWindowsTb, origin);
				genPlane(topSize, windowWidth,
					 Ogre::Vector3::UNIT_X,
					 Ogre::Vector3(
						 -halfCell + intWallOffset,
						 windowY + windowHeight / 2 +
							 topSize / 2,
						 0),
					 intWindowsTb, origin);
				genPlane(
					bottomSize, windowWidth,
					Ogre::Vector3::UNIT_X,
					Ogre::Vector3(-halfCell + intWallOffset,
						      solidIntOffset +
							      bottomSize / 2,
						      0),
					intWindowsTb, origin);
			}
			if (cell.hasFlag(CellFlags::IntWindowXPos)) {
				genPlane(windowHeight, sideWidth,
					 Ogre::Vector3::NEGATIVE_UNIT_X,
					 Ogre::Vector3(halfCell - intWallOffset,
						       windowY,
						       -windowWidth / 2 -
							       sideWidth / 2),
					 intWindowsTb, origin);
				genPlane(windowHeight, sideWidth,
					 Ogre::Vector3::NEGATIVE_UNIT_X,
					 Ogre::Vector3(halfCell - intWallOffset,
						       windowY,
						       windowWidth / 2 +
							       sideWidth / 2),
					 intWindowsTb, origin);
				genPlane(topSize, sideWidth,
					 Ogre::Vector3::NEGATIVE_UNIT_X,
					 Ogre::Vector3(
						 halfCell - intWallOffset,
						 windowY + windowHeight / 2 +
							 topSize / 2,
						 -windowWidth / 2 -
							 sideWidth / 2),
					 intWindowsTb, origin);
				genPlane(topSize, sideWidth,
					 Ogre::Vector3::NEGATIVE_UNIT_X,
					 Ogre::Vector3(
						 halfCell - intWallOffset,
						 windowY + windowHeight / 2 +
							 topSize / 2,
						 windowWidth / 2 +
							 sideWidth / 2),
					 intWindowsTb, origin);
				genPlane(bottomSize, sideWidth,
					 Ogre::Vector3::NEGATIVE_UNIT_X,
					 Ogre::Vector3(halfCell - intWallOffset,
						       solidIntOffset +
							       bottomSize / 2,
						       -windowWidth / 2 -
							       sideWidth / 2),
					 intWindowsTb, origin);
				genPlane(bottomSize, sideWidth,
					 Ogre::Vector3::NEGATIVE_UNIT_X,
					 Ogre::Vector3(halfCell - intWallOffset,
						       solidIntOffset +
							       bottomSize / 2,
						       windowWidth / 2 +
							       sideWidth / 2),
					 intWindowsTb, origin);
				genPlane(topSize, windowWidth,
					 Ogre::Vector3::NEGATIVE_UNIT_X,
					 Ogre::Vector3(
						 halfCell - intWallOffset,
						 windowY + windowHeight / 2 +
							 topSize / 2,
						 0),
					 intWindowsTb, origin);
				genPlane(bottomSize, windowWidth,
					 Ogre::Vector3::NEGATIVE_UNIT_X,
					 Ogre::Vector3(halfCell - intWallOffset,
						       solidIntOffset +
							       bottomSize / 2,
						       0),
					 intWindowsTb, origin);
			}
			if (cell.hasFlag(CellFlags::IntWindowZNeg)) {
				genPlane(windowHeight, sideWidth,
					 Ogre::Vector3::UNIT_Z,
					 Ogre::Vector3(-windowWidth / 2 -
							       sideWidth / 2,
						       windowY,
						       -halfCell +
							       intWallOffset),
					 intWindowsTb, origin);
				genPlane(windowHeight, sideWidth,
					 Ogre::Vector3::UNIT_Z,
					 Ogre::Vector3(windowWidth / 2 +
							       sideWidth / 2,
						       windowY,
						       -halfCell +
							       intWallOffset),
					 intWindowsTb, origin);
				genPlane(topSize, sideWidth,
					 Ogre::Vector3::UNIT_Z,
					 Ogre::Vector3(
						 -windowWidth / 2 -
							 sideWidth / 2,
						 windowY + windowHeight / 2 +
							 topSize / 2,
						 -halfCell + intWallOffset),
					 intWindowsTb, origin);
				genPlane(
					topSize, sideWidth,
					Ogre::Vector3::UNIT_Z,
					Ogre::Vector3(
						windowWidth / 2 + sideWidth / 2,
						windowY + windowHeight / 2 +
							topSize / 2,
						-halfCell + intWallOffset),
					intWindowsTb, origin);
				genPlane(bottomSize, sideWidth,
					 Ogre::Vector3::UNIT_Z,
					 Ogre::Vector3(-windowWidth / 2 -
							       sideWidth / 2,
						       solidIntOffset +
							       bottomSize / 2,
						       -halfCell +
							       intWallOffset),
					 intWindowsTb, origin);
				genPlane(
					bottomSize, sideWidth,
					Ogre::Vector3::UNIT_Z,
					Ogre::Vector3(
						windowWidth / 2 + sideWidth / 2,
						solidIntOffset + bottomSize / 2,
						-halfCell + intWallOffset),
					intWindowsTb, origin);
				genPlane(topSize, windowWidth,
					 Ogre::Vector3::UNIT_Z,
					 Ogre::Vector3(
						 0,
						 windowY + windowHeight / 2 +
							 topSize / 2,
						 -halfCell + intWallOffset),
					 intWindowsTb, origin);
				genPlane(bottomSize, windowWidth,
					 Ogre::Vector3::UNIT_Z,
					 Ogre::Vector3(0,
						       solidIntOffset +
							       bottomSize / 2,
						       -halfCell +
							       intWallOffset),
					 intWindowsTb, origin);
			}
			if (cell.hasFlag(CellFlags::IntWindowZPos)) {
				genPlane(
					windowHeight, sideWidth,
					Ogre::Vector3::NEGATIVE_UNIT_Z,
					Ogre::Vector3(-windowWidth / 2 -
							      sideWidth / 2,
						      windowY,
						      halfCell - intWallOffset),
					intWindowsTb, origin);
				genPlane(
					windowHeight, sideWidth,
					Ogre::Vector3::NEGATIVE_UNIT_Z,
					Ogre::Vector3(windowWidth / 2 +
							      sideWidth / 2,
						      windowY,
						      halfCell - intWallOffset),
					intWindowsTb, origin);
				genPlane(topSize, sideWidth,
					 Ogre::Vector3::NEGATIVE_UNIT_Z,
					 Ogre::Vector3(
						 -windowWidth / 2 -
							 sideWidth / 2,
						 windowY + windowHeight / 2 +
							 topSize / 2,
						 halfCell - intWallOffset),
					 intWindowsTb, origin);
				genPlane(
					topSize, sideWidth,
					Ogre::Vector3::NEGATIVE_UNIT_Z,
					Ogre::Vector3(
						windowWidth / 2 + sideWidth / 2,
						windowY + windowHeight / 2 +
							topSize / 2,
						halfCell - intWallOffset),
					intWindowsTb, origin);
				genPlane(
					bottomSize, sideWidth,
					Ogre::Vector3::NEGATIVE_UNIT_Z,
					Ogre::Vector3(-windowWidth / 2 -
							      sideWidth / 2,
						      solidIntOffset +
							      bottomSize / 2,
						      halfCell - intWallOffset),
					intWindowsTb, origin);
				genPlane(
					bottomSize, sideWidth,
					Ogre::Vector3::NEGATIVE_UNIT_Z,
					Ogre::Vector3(
						windowWidth / 2 + sideWidth / 2,
						solidIntOffset + bottomSize / 2,
						halfCell - intWallOffset),
					intWindowsTb, origin);
				genPlane(topSize, windowWidth,
					 Ogre::Vector3::NEGATIVE_UNIT_Z,
					 Ogre::Vector3(
						 0,
						 windowY + windowHeight / 2 +
							 topSize / 2,
						 halfCell - intWallOffset),
					 intWindowsTb, origin);
				genPlane(
					bottomSize, windowWidth,
					Ogre::Vector3::NEGATIVE_UNIT_Z,
					Ogre::Vector3(0,
						      solidIntOffset +
							      bottomSize / 2,
						      halfCell - intWallOffset),
					intWindowsTb, origin);
			}
		}
	}
}

/**
 * Deform roof side vertices to create triangular gable ends
 * 
 * For Normal roof (deformXAxis=true): Deforms side pieces along X axis
 * For Normal2 roof (deformXAxis=false): Deforms side pieces along Z axis
 */
static void deformRoofSideVertices(Procedural::TriangleBuffer &tb,
				   size_t startVertexIndex,
				   bool deformXAxis)
{
	auto &vertices = tb.getVertices();
	if (vertices.size() <= startVertexIndex)
		return;

	// Find min/max Y and coord (X or Z depending on axis)
	float minY = std::numeric_limits<float>::max();
	float maxY = std::numeric_limits<float>::lowest();
	float minCoord = std::numeric_limits<float>::max();
	float maxCoord = std::numeric_limits<float>::lowest();

	for (size_t i = startVertexIndex; i < vertices.size(); ++i) {
		const auto &v = vertices[i];
		minY = std::min(minY, v.mPosition.y);
		maxY = std::max(maxY, v.mPosition.y);
		float coord = deformXAxis ? v.mPosition.x : v.mPosition.z;
		minCoord = std::min(minCoord, coord);
		maxCoord = std::max(maxCoord, coord);
	}

	float midCoord = minCoord + (maxCoord - minCoord) / 2.0f;
	float coordRange = (maxCoord - minCoord) / 2.0f;
	if (coordRange < 0.001f)
		return;

	// Deform vertices to create triangular shape
	for (size_t i = startVertexIndex; i < vertices.size(); ++i) {
		auto &v = vertices[i];
		float md = maxY - v.mPosition.y;
		float hm = (deformXAxis ? v.mPosition.x : v.mPosition.z) -
			   midCoord;
		float he = (hm / coordRange) * md;

		if (deformXAxis) {
			v.mPosition.x = he + midCoord;
		} else {
			v.mPosition.z = he + midCoord;
		}
	}
}

void CellGridSystem::buildRoofs(flecs::entity cellGridEntity,
				const CellGridComponent &grid,
				Procedural::TriangleBuffer &roofTopTb,
				Procedural::TriangleBuffer &roofSideTb)
{
	// Get roof components from CellGrid's children
	// Roof components are children of the CellGrid entity
	cellGridEntity.children([&](flecs::entity child) {
		if (child.has<RoofComponent>()) {
			auto &roof = child.get<RoofComponent>();
			
			// Skip if not dirty (no changes)
			if (!roof.dirty) {
				return;
			}
			
			// Mark as clean since we're building it now
			child.set<RoofComponent>(roof);
			child.get_mut<RoofComponent>().dirty = false;

			// Roof cells are twice the size of GridCell cells
			// So we use full cellSize instead of halfCell for width/depth
			float roofCellSize = grid.cellSize;  // Full cell size for roof cells
			float x = roof.posX * roofCellSize - roofCellSize / 2.0f +
				  roof.sizeX * roofCellSize / 2.0f;
			float z = roof.posZ * roofCellSize - roofCellSize / 2.0f +
				  roof.sizeZ * roofCellSize / 2.0f;
			// Y uses cellHeight directly
			float baseY = roof.posY * grid.cellHeight;

			Ogre::Vector3 origin(x + roof.offsetX,
					     baseY + roof.offsetY,
					     z + roof.offsetZ);

			float width = roof.sizeX * roofCellSize;
			float depth = roof.sizeZ * roofCellSize;
			float height = roof.maxHeight - roof.baseHeight;

			// EXTEND constant for roof edges (like old code)
			const float EXTEND = 0.24f;

			switch (roof.type) {
			case RoofComponent::Flat: {
				// Main roof box -> roofTopTb
				float baseY = origin.y + roof.baseHeight / 2.0f;
				Procedural::BoxGenerator()
					.setSizeX(width)
					.setSizeY(roof.baseHeight)
					.setSizeZ(depth)
					.setNumSegY(1)
					.setNumSegX(1)
					.setNumSegZ(1)
					.setEnableNormals(true)
					.setPosition(Ogre::Vector3(
						origin.x, baseY, origin.z))
					.addToTriangleBuffer(roofTopTb);

				// Edge trim pieces -> roofSideTb
				float trimBaseY =
					origin.y + roof.baseHeight / 2.0f;
				// Z+ edge
				Procedural::BoxGenerator()
					.setSizeX(width)
					.setSizeY(roof.baseHeight +
						  EXTEND * 2.0f)
					.setSizeZ(EXTEND)
					.setNumSegY(1)
					.setNumSegX(1)
					.setNumSegZ(1)
					.setEnableNormals(true)
					.setPosition(Ogre::Vector3(
						origin.x, trimBaseY + EXTEND,
						origin.z + depth / 2.0f +
							EXTEND / 2.0f))
					.addToTriangleBuffer(roofSideTb);

				// Z- edge
				Procedural::BoxGenerator()
					.setSizeX(width)
					.setSizeY(roof.baseHeight +
						  EXTEND * 2.0f)
					.setSizeZ(EXTEND)
					.setNumSegY(1)
					.setNumSegX(1)
					.setNumSegZ(1)
					.setEnableNormals(true)
					.setPosition(Ogre::Vector3(
						origin.x, trimBaseY + EXTEND,
						origin.z - depth / 2.0f -
							EXTEND / 2.0f))
					.addToTriangleBuffer(roofSideTb);

				// X+ edge
				Procedural::BoxGenerator()
					.setSizeX(EXTEND)
					.setSizeY(roof.baseHeight +
						  EXTEND * 2.0f)
					.setSizeZ(depth + EXTEND * 2.0f)
					.setNumSegY(1)
					.setNumSegX(1)
					.setNumSegZ(1)
					.setEnableNormals(true)
					.setPosition(Ogre::Vector3(
						origin.x + width / 2.0f +
							EXTEND / 2.0f,
						trimBaseY + EXTEND, origin.z))
					.addToTriangleBuffer(roofSideTb);

				// X- edge
				Procedural::BoxGenerator()
					.setSizeX(EXTEND)
					.setSizeY(roof.baseHeight +
						  EXTEND * 2.0f)
					.setSizeZ(depth + EXTEND * 2.0f)
					.setNumSegY(1)
					.setNumSegX(1)
					.setNumSegZ(1)
					.setEnableNormals(true)
					.setPosition(Ogre::Vector3(
						origin.x - width / 2.0f -
							EXTEND / 2.0f,
						trimBaseY + EXTEND, origin.z))
					.addToTriangleBuffer(roofSideTb);
				break;
			}
			case RoofComponent::Normal: {
				// Gable roof along X axis (ridge runs along Z)
				// Two angled boxes -> roofTopTb
				float q = width / 2.0f;
				float m = 1.0f;
				float d = Ogre::Math::Sqrt(2.0f) * (q + m);
				float baseY = origin.y;

				// First angled box
				Procedural::BoxGenerator()
					.setSizeX(d)
					.setSizeY(roof.baseHeight / 2.0f)
					.setSizeZ(depth)
					.setNumSegY(1)
					.setNumSegX(1)
					.setNumSegZ(1)
					.setEnableNormals(true)
					.setOrientation(Ogre::Quaternion(
						Ogre::Degree(45),
						Ogre::Vector3::NEGATIVE_UNIT_Z))
					.setPosition(Ogre::Vector3(
						origin.x - width / 2.0f + q +
							q / 2.0f + m / 2.0f,
						baseY + roof.baseHeight +
							q / 2.0f - m / 2.0f,
						origin.z))
					.addToTriangleBuffer(roofTopTb);

				// Second angled box
				Procedural::BoxGenerator()
					.setSizeX(d)
					.setSizeY(roof.baseHeight / 2.0f)
					.setSizeZ(depth)
					.setNumSegY(1)
					.setNumSegX(1)
					.setNumSegZ(1)
					.setEnableNormals(true)
					.setOrientation(Ogre::Quaternion(
						Ogre::Degree(-45),
						Ogre::Vector3::NEGATIVE_UNIT_Z))
					.setPosition(Ogre::Vector3(
						origin.x - width / 2.0f +
							q / 2.0f - m / 2.0f,
						baseY + roof.baseHeight +
							q / 2.0f - m / 2.0f,
						origin.z))
					.addToTriangleBuffer(roofTopTb);
				
				// Side trim pieces (gable ends) -> roofSideTb
				size_t sideVertexStart = roofSideTb.getVertices().size();
				float sideY = baseY + q / 2.0f;
				float sideHeight = q + roof.baseHeight * 3.0f;

				// Z+ edge
				Procedural::BoxGenerator()
					.setSizeX(width + EXTEND * 2.0f)
					.setSizeY(sideHeight)
					.setSizeZ(EXTEND)
					.setNumSegX(3)
					.setNumSegY(3)
					.setNumSegZ(1)
					.setEnableNormals(true)
					.setPosition(Ogre::Vector3(
						origin.x, sideY + roof.baseHeight,
						origin.z + depth / 2.0f + EXTEND / 2.0f))
					.addToTriangleBuffer(roofSideTb);

				// Z- edge
				Procedural::BoxGenerator()
					.setSizeX(width + EXTEND * 2.0f)
					.setSizeY(sideHeight)
					.setSizeZ(EXTEND)
					.setNumSegX(3)
					.setNumSegY(3)
					.setNumSegZ(1)
					.setEnableNormals(true)
					.setPosition(Ogre::Vector3(
						origin.x, sideY + roof.baseHeight,
						origin.z - depth / 2.0f - EXTEND / 2.0f))
					.addToTriangleBuffer(roofSideTb);
				
				// Apply vertex deformation to side pieces
				deformRoofSideVertices(roofSideTb, sideVertexStart, true);
				break;
			}
			case RoofComponent::Normal2: {
				// Gable roof along Z axis (ridge runs along X)
				// Two angled boxes -> roofTopTb
				float q = depth / 2.0f;
				float m = 1.0f;
				float d = Ogre::Math::Sqrt(2.0f) * (q + m);
				float baseY = origin.y;

				// First angled box
				Procedural::BoxGenerator()
					.setSizeX(width)
					.setSizeY(roof.baseHeight / 2.0f)
					.setSizeZ(d)
					.setNumSegY(1)
					.setNumSegX(1)
					.setNumSegZ(1)
					.setEnableNormals(true)
					.setOrientation(Ogre::Quaternion(
						Ogre::Degree(45),
						Ogre::Vector3::NEGATIVE_UNIT_X))
					.setPosition(Ogre::Vector3(
						origin.x,
						baseY + roof.baseHeight +
							q / 2.0f - m / 2.0f,
						origin.z - depth / 2.0f +
							q / 2.0f - m / 2.0f))
					.addToTriangleBuffer(roofTopTb);

				// Second angled box
				Procedural::BoxGenerator()
					.setSizeX(width)
					.setSizeY(roof.baseHeight / 2.0f)
					.setSizeZ(d)
					.setNumSegX(1)
					.setNumSegY(1)
					.setNumSegZ(1)
					.setEnableNormals(true)
					.setOrientation(Ogre::Quaternion(
						Ogre::Degree(-45),
						Ogre::Vector3::NEGATIVE_UNIT_X))
					.setPosition(Ogre::Vector3(
						origin.x,
						baseY + roof.baseHeight +
							q / 2.0f - m / 2.0f,
						origin.z - depth / 2.0f + q +
							q / 2.0f + m / 2.0f))
					.addToTriangleBuffer(roofTopTb);
				
				// Side trim pieces (gable ends) -> roofSideTb
				size_t sideVertexStart = roofSideTb.getVertices().size();
				float sideY = baseY + q / 2.0f;
				float sideHeight = q + roof.baseHeight * 3.0f;
				float sideDepth = depth + 2.0f * roof.baseHeight * 3.0f;

				// X+ edge
				Procedural::BoxGenerator()
					.setSizeX(EXTEND)
					.setSizeY(sideHeight)
					.setSizeZ(sideDepth)
					.setNumSegX(2)
					.setNumSegY(2)
					.setNumSegZ(1)
					.setEnableNormals(true)
					.setPosition(Ogre::Vector3(
						origin.x + width / 2.0f + EXTEND / 2.0f,
						sideY + roof.baseHeight, origin.z))
					.addToTriangleBuffer(roofSideTb);

				// X- edge
				Procedural::BoxGenerator()
					.setSizeX(EXTEND)
					.setSizeY(sideHeight)
					.setSizeZ(sideDepth)
					.setNumSegX(2)
					.setNumSegY(2)
					.setNumSegZ(1)
					.setEnableNormals(true)
					.setPosition(Ogre::Vector3(
						origin.x - width / 2.0f - EXTEND / 2.0f,
						sideY + roof.baseHeight, origin.z))
					.addToTriangleBuffer(roofSideTb);
				
				// Apply vertex deformation to side pieces
				deformRoofSideVertices(roofSideTb, sideVertexStart, false);
				break;
			}
			case RoofComponent::Cone: {
				// Cone -> roofTopTb
				Procedural::ConeGenerator cone;
				cone.setRadius(width / 2.0f);
				cone.setHeight(roof.baseHeight + height);
				cone.setPosition(Ogre::Vector3(
					origin.x, origin.y, origin.z));
				cone.addToTriangleBuffer(roofTopTb);
				break;
			}
			case RoofComponent::Cylinder: {
				// Cylinder -> roofTopTb
				Procedural::CylinderGenerator cyl;
				cyl.setRadius(width / 2.0f);
				cyl.setHeight(roof.baseHeight);
				cyl.setPosition(Ogre::Vector3(
					origin.x,
					origin.y + roof.baseHeight / 2.0f,
					origin.z));
				cyl.addToTriangleBuffer(roofTopTb);
				break;
			}
			}
		}
	});
}

Ogre::MeshPtr CellGridSystem::convertToMesh(const std::string &name,
					    Procedural::TriangleBuffer &tb,
					    const std::string &materialName)
{
	try {
		Ogre::MeshPtr mesh = tb.transformToMesh(name);
		Ogre::LogManager::getSingleton().logMessage(
			"CellGrid: convertToMesh '" + name + "' vertices=" +
			std::to_string(tb.getVertices().size()) +
			" material=" + materialName);
		generateLodForMesh(mesh);
		if (!materialName.empty() && mesh->getNumSubMeshes() > 0) {
			mesh->getSubMesh(0)->setMaterialName(materialName);
			Ogre::LogManager::getSingleton().logMessage(
				"CellGrid: Set material on mesh '" + name +
				"' to '" + materialName + "'");
		}
		return mesh;
	} catch (const Ogre::Exception &e) {
		Ogre::LogManager::getSingleton().logMessage(
			"CellGrid: Failed to create mesh '" + name +
			"': " + e.getDescription());
		return Ogre::MeshPtr();
	}
}

void CellGridSystem::buildFurniture(flecs::entity entity,
				      const CellGridComponent &grid)
{
	destroyFurniture(entity);

	if (!entity.has<TransformComponent>())
		return;
	auto &transform = entity.get_mut<TransformComponent>();
	if (!transform.node)
		return;

	FurnitureLibrary::getInstance().loadAll();
	auto &meshData = m_entityMeshes[entity.id()];

	Ogre::StaticGeometry *staticGeom = meshData.frameStaticGeometry;

	Ogre::Vector3 parentPos = transform.node->_getDerivedPosition();
	Ogre::Quaternion parentRot = transform.node->_getDerivedOrientation();
	Ogre::Vector3 parentScale = transform.node->_getDerivedScale();

	int batchedCount = 0;

	for (const auto &fcell : grid.furnitureCells) {
		const FurnitureDefinition *def =
			FurnitureLibrary::getInstance().findByName(
				fcell.furnitureType);
		if (!def) {
			// Fallback: try to find by exact mesh name
			for (const auto &d :
			     FurnitureLibrary::getInstance().getDefinitions()) {
				if (d.meshName == fcell.furnitureType ||
				    d.name == fcell.furnitureType) {
					def = &d;
					break;
				}
			}
		}
		if (!def)
			continue;

		Ogre::MeshPtr mesh =
			Ogre::MeshManager::getSingleton().getByName(
				def->meshName);
		if (!mesh) {
			Ogre::String group =
				Ogre::ResourceGroupManager::getSingleton()
					.findGroupContainingResource(
						def->meshName);
			if (!group.empty()) {
				mesh = Ogre::MeshManager::getSingleton().load(
					def->meshName, group);
			}
		}
		if (!mesh) {
			Ogre::LogManager::getSingleton().logMessage(
				"CellGrid: Could not load furniture mesh '" +
				def->meshName + "'");
			continue;
		}

		generateLodForMesh(mesh);

		bool isolated = false;
		for (const auto &tag : def->tags) {
			if (tag == "isolated") {
				isolated = true;
				break;
			}
		}

		Ogre::Vector3 pos = grid.cellToWorld(fcell.x, fcell.y,
						     fcell.z);
		// Determine Y offset from the room that contains this furniture cell
		float furnitureYOffset = 0.05f;
		bool foundRoom = false;
		entity.children([&](flecs::entity child) {
			if (foundRoom) return;
			if (child.has<RoomComponent>()) {
				const auto &room = child.get<RoomComponent>();
				if (room.contains(fcell.x, fcell.y, fcell.z)) {
					furnitureYOffset = room.furnitureYOffset;
					foundRoom = true;
				}
			}
		});
		pos.y += furnitureYOffset;
		Ogre::Quaternion localRot(
			Ogre::Degree(90.0f * fcell.rotation),
			Ogre::Vector3::UNIT_Y);

		if (!isolated && staticGeom) {
			Ogre::Vector3 worldPos = parentPos +
				(parentRot * (pos * parentScale));
			Ogre::Quaternion worldRot = parentRot * localRot;

			Ogre::Entity *tempEnt =
				m_sceneMgr->createEntity(def->meshName);
			if (!mesh->getSubMesh(0)->getMaterialName().empty()) {
				tempEnt->setMaterialName(
					mesh->getSubMesh(0)->getMaterialName());
			}
			staticGeom->addEntity(tempEnt, worldPos, worldRot,
					      parentScale);
			m_sceneMgr->destroyEntity(tempEnt);
			meshData.furniturePlacements.push_back(
				{def->meshName, worldPos, worldRot});
			batchedCount++;
		} else {
			Ogre::Entity *ent =
				m_sceneMgr->createEntity(def->meshName);
			if (!ent)
				continue;

			Ogre::SceneNode *fnode =
				transform.node->createChildSceneNode();
			fnode->setPosition(pos);
			fnode->setOrientation(localRot);
			fnode->attachObject(ent);
			ent->setRenderingDistance(100.0f);

			if (!mesh->getSubMesh(0)->getMaterialName().empty()) {
				ent->setMaterialName(
					mesh->getSubMesh(0)->getMaterialName());
			}

			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);
		}
	}

	if (batchedCount > 0) {
		Ogre::LogManager::getSingleton().logMessage(
			"CellGrid: Batched " +
			std::to_string(batchedCount) +
			" furniture items into StaticGeometry");
	}
}

void CellGridSystem::destroyFurniture(flecs::entity entity)
{
	auto it = m_entityMeshes.find(entity.id());
	if (it == m_entityMeshes.end())
		return;

	for (auto *ent : it->second.furnitureEntities) {
		if (!ent)
			continue;
		Ogre::SceneNode *node = ent->getParentSceneNode();
		if (node) {
			node->detachObject(ent);
			m_sceneMgr->destroySceneNode(node);
		}
		try {
			m_sceneMgr->destroyEntity(ent);
		} catch (...) {
		}
	}
	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)
{
	if (!mesh || !mesh->isLoaded())
		return;
	try {
		Ogre::LodConfig config(mesh);
		config.advanced.useVertexNormals = true;
		config.advanced.preventPunchingHoles = true;
		config.advanced.preventBreakingLines = true;
		config.createGeneratedLodLevel(10.0f, 0.15f);
		Ogre::MeshLodGenerator::getSingleton().generateLodLevels(config);
	} catch (const std::exception &e) {
		Ogre::LogManager::getSingleton().logMessage(
			"CellGrid: LOD generation failed for " +
			mesh->getName() + ": " + e.what());
	}
}

void CellGridSystem::destroyCellGridMeshes(CellGridComponent &grid)
{
	// This is called from buildCellGrid which uses the entity ID as key in m_entityMeshes
	// The actual cleanup needs to happen there
}

void CellGridSystem::destroyCellGridMeshes(flecs::entity entity)
{
	auto it = m_entityMeshes.find(entity.id());
	if (it != m_entityMeshes.end()) {
		// Destroy all entities first
		for (auto *ent : it->second.entities) {
			if (ent) {
				try {
					m_sceneMgr->destroyEntity(ent);
				} catch (...) {
				}
			}
		}
		it->second.entities.clear();

		// Destroy meshes
		auto &meshMgr = Ogre::MeshManager::getSingleton();

		auto removeMesh = [&](const std::string &name) {
			if (!name.empty()) {
				try {
					meshMgr.remove(name);
				} catch (...) {
				}
			}
		};

		removeMesh(it->second.floorMesh);
		removeMesh(it->second.ceilingMesh);
		removeMesh(it->second.extWallMesh);
		removeMesh(it->second.intWallMesh);
		removeMesh(it->second.intWindowsMesh);
		removeMesh(it->second.roofMesh);
		removeMesh(it->second.roofSideMesh);
		for (const auto &name : it->second.doorMeshes) {
			removeMesh(name);
		}
		for (const auto &name : it->second.windowMeshes) {
			removeMesh(name);
		}

		for (const auto &name : it->second.doorMeshes) {
			removeMesh(name);
		}
		for (const auto &name : it->second.windowMeshes) {
			removeMesh(name);
		}

		// Destroy furniture
		destroyFurniture(entity);
		
		// Destroy frames
		destroyFrames(entity);
		
		// Destroy physics colliders
		destroyPhysicsColliders(entity);

		m_entityMeshes.erase(it);
	}
}

void CellGridSystem::updateTownMaterial(flecs::entity townEntity)
{
	if (!townEntity.has<TownComponent>())
		return;

	auto &town = townEntity.get_mut<TownComponent>();
	if (town.materialName.empty()) {
		town.materialName =
			"TownMaterial_" + std::to_string(townEntity.id());
	}

	// Create or update material
	auto &matMgr = Ogre::MaterialManager::getSingleton();
	Ogre::MaterialPtr mat;

	if (matMgr.resourceExists(
		    town.materialName,
		    Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME)) {
		mat = matMgr.getByName(town.materialName);
		mat->unload();
	} else {
		mat = matMgr.create(
			town.materialName,
			Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME);
	}

	// For now, create a simple colored material
	// Full implementation would create texture atlas from color rects
	auto pass = mat->getTechnique(0)->getPass(0);
	pass->setDiffuse(Ogre::ColourValue(0.8f, 0.8f, 0.8f));
	pass->setAmbient(Ogre::ColourValue(0.4f, 0.4f, 0.4f));
	pass->setSpecular(Ogre::ColourValue(0.1f, 0.1f, 0.1f));

	// Register with RTSS so the material works on shader-only render systems
	Ogre::RTShader::ShaderGenerator *shaderGen =
		Ogre::RTShader::ShaderGenerator::getSingletonPtr();
	if (shaderGen) {
		shaderGen->createShaderBasedTechnique(
			*mat,
			Ogre::MaterialManager::DEFAULT_SCHEME_NAME,
			Ogre::RTShader::ShaderGenerator::DEFAULT_SCHEME_NAME);
		shaderGen->validateMaterial(
			Ogre::RTShader::ShaderGenerator::DEFAULT_SCHEME_NAME,
			town.materialName);
	}

	Ogre::LogManager::getSingleton().logMessage(
		"CellGrid: Updated town material '" + town.materialName + "'");
}

void CellGridSystem::rebuildCellGrid(flecs::entity entity)
{
	if (!entity.is_alive() || !entity.has<CellGridComponent>())
		return;
	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});
		RigidBodyComponent cellGridRb;
		if (entity.has<CellGridComponent>())
			cellGridRb.friction = entity.get<CellGridComponent>().friction;
		physicsParent.set<RigidBodyComponent>(cellGridRb);
		physicsParent.add<GeneratedPhysicsTag>();
		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});
		RigidBodyComponent plazaRb;
		if (entity.has<DistrictComponent>())
			plazaRb.friction = entity.get<DistrictComponent>().friction;
		physicsParent.set<RigidBodyComponent>(plazaRb);
		physicsParent.add<GeneratedPhysicsTag>();
		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});
		RigidBodyComponent lotRb;
		if (entity.has<LotComponent>())
			lotRb.friction = entity.get<LotComponent>().friction;
		physicsParent.set<RigidBodyComponent>(lotRb);
		physicsParent.add<GeneratedPhysicsTag>();
		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)
{
	// Destroy existing plaza if any
	auto it = m_plazaMeshes.find(entity.id());
	if (it != m_plazaMeshes.end()) {
		if (it->second.entity) {
			try {
				m_sceneMgr->destroyEntity(it->second.entity);
			} catch (...) {
			}
		}
		if (!it->second.meshName.empty()) {
			try {
				Ogre::MeshManager::getSingleton().remove(
					it->second.meshName);
			} catch (...) {
			}
		}
		destroyPhysicsColliders(entity);
		m_plazaMeshes.erase(it);
	}

	if (!district.isPlaza) {
		return;
	}

	// Get transform from entity
	if (!entity.has<TransformComponent>()) {
		Ogre::LogManager::getSingleton().logMessage(
			"CellGrid: District has no TransformComponent");
		return;
	}
	auto &transform = entity.get_mut<TransformComponent>();
	if (!transform.node) {
		Ogre::LogManager::getSingleton().logMessage(
			"CellGrid: District has no SceneNode");
		return;
	}

	// Get material from district's procedural material entity
	std::string materialName = "Ogre/StandardFloor"; // fallback

	if (district.proceduralMaterialEntity.is_valid() && district.proceduralMaterialEntity.is_alive() &&
	    district.proceduralMaterialEntity
		    .has<ProceduralMaterialComponent>()) {
		const auto &mat = district.proceduralMaterialEntity
					  .get<ProceduralMaterialComponent>();
		if (!mat.materialName.empty()) {
			materialName = mat.materialName;
		}
	}

	Ogre::LogManager::getSingleton().logMessage(
		"CellGrid: Building plaza for district " +
		std::to_string(entity.id()) +
		" with material: " + materialName);

	// Create plaza mesh using Lathe
	Procedural::TriangleBuffer tb;
	Procedural::Shape plazaShape;

	float radius = district.radius;
	float height = district.height;
	float elevation = district.elevation;
	float mh = 4.0f; // margin height

	// Build the plaza shape profile (based on original code)
	plazaShape.addPoint(0, -height - mh - mh);
	plazaShape.addPoint(radius * 0.5f + mh, -height - mh - mh);
	plazaShape.addPoint(radius + mh, -height - mh);
	plazaShape.addPoint(radius, -height);
	plazaShape.addPoint(radius, 0.0f);
	plazaShape.addPoint(radius - 0.1f, 0.1f);
	plazaShape.addPoint(radius - mh + 0.1f, height);
	plazaShape.addPoint(radius - mh, height + 0.1f);
	plazaShape.addPoint(radius * 0.5f + mh, height);
	plazaShape.addPoint(0, height);

	// Generate lathe geometry
	Procedural::Lathe()
		.setShapeToExtrude(&plazaShape)
		.setEnableNormals(true)
		.setPosition(
			Ogre::Vector3(0.0f, height / 2.0f + elevation, 0.0f))
		.setNumSeg(24)
		.addToTriangleBuffer(tb);

	// Check if we have valid geometry
	size_t vertexCount = tb.getVertices().size();
	size_t indexCount = tb.getIndices().size();
	Ogre::LogManager::getSingleton().logMessage(
		"CellGrid: Plaza triangle buffer has " +
		std::to_string(vertexCount) + " vertices, " +
		std::to_string(indexCount) + " indices");

	if (vertexCount < 3) {
		Ogre::LogManager::getSingleton().logMessage(
			"CellGrid: Not enough vertices for plaza mesh");
		return;
	}

	// Apply UV mapping if texture rect is specified
	if (!district.textureRectName.empty()) {
		// Find the texture entity from the material
		flecs::entity textureEntity = flecs::entity::null();
		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_valid() && mat.diffuseTextureEntity.is_alive()) {
				textureEntity = mat.diffuseTextureEntity;
			}
		}

		if (textureEntity.is_valid() && textureEntity.is_alive() &&
		    textureEntity.has<ProceduralTextureComponent>()) {
			const auto &texture =
				textureEntity.get<ProceduralTextureComponent>();
			const TextureRectInfo *rect =
				texture.getNamedRect(district.textureRectName);
			if (rect) {
				// Apply UV mapping with configurable margin
				const float margin = texture.uvMargin;
				float uRange = (rect->u2 - rect->u1) *
					       (1.0f - 2.0f * margin);
				float vRange = (rect->v2 - rect->v1) *
					       (1.0f - 2.0f * margin);
				float uOffset = rect->u1 +
						(rect->u2 - rect->u1) * margin;
				float vOffset = rect->v1 +
						(rect->v2 - rect->v1) * margin;

				auto &vertices = tb.getVertices();
				for (auto &vertex : vertices) {
					vertex.mUV.x =
						uOffset + vertex.mUV.x * uRange;
					vertex.mUV.y =
						vOffset + vertex.mUV.y * vRange;
				}

				Ogre::LogManager::getSingleton().logMessage(
					"CellGrid: Applied UV mapping for rect '" +
					district.textureRectName + "'");
			}
		}
	}

	// Generate mesh name
	std::string meshName = "Plaza_" + std::to_string(entity.id()) + "_" +
			       std::to_string(m_meshCount++);

	// Create mesh and entity
	try {
		Ogre::MeshPtr mesh = tb.transformToMesh(meshName);

		if (mesh.isNull()) {
			Ogre::LogManager::getSingleton().logMessage(
				"CellGrid: transformToMesh returned null");
			return;
		}

		Ogre::LogManager::getSingleton().logMessage(
			"CellGrid: Created mesh '" + meshName + "' with " +
			std::to_string(mesh->getNumSubMeshes()) + " submeshes");

		if (!materialName.empty() && mesh->getNumSubMeshes() > 0) {
			mesh->getSubMesh(0)->setMaterialName(materialName);
			Ogre::LogManager::getSingleton().logMessage(
				"CellGrid: Set material to '" + materialName +
				"'");
		}

		Ogre::Entity *plazzaEntity = m_sceneMgr->createEntity(meshName);
		if (!plazzaEntity) {
			Ogre::LogManager::getSingleton().logMessage(
				"CellGrid: createEntity returned null");
			return;
		}

		transform.node->attachObject(plazzaEntity);

		// Force visibility
		plazzaEntity->setVisible(true);

		// Store for cleanup
		PlazaData data;
		data.meshName = meshName;
		data.entity = plazzaEntity;
		// Store texture dependency for automatic rebuild when texture changes
		if (district.proceduralMaterialEntity.is_valid() && district.proceduralMaterialEntity.is_alive() &&
		    district.proceduralMaterialEntity.has<ProceduralMaterialComponent>()) {
			const auto &mat = district.proceduralMaterialEntity.get<ProceduralMaterialComponent>();
			if (mat.diffuseTextureEntity.is_valid() && mat.diffuseTextureEntity.is_alive() &&
			    mat.diffuseTextureEntity.has<ProceduralTextureComponent>()) {
				data.textureEntity = mat.diffuseTextureEntity;
				const auto &tex = mat.diffuseTextureEntity.get<ProceduralTextureComponent>();
				data.textureVersion = tex.version;
			}
		}
		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 '" +
			transform.node->getName() + "'");

	} catch (const Ogre::Exception &e) {
		Ogre::LogManager::getSingleton().logMessage(
			"CellGrid: Failed to create plaza mesh: " +
			e.getDescription());
	}
}

void CellGridSystem::buildLotBase(flecs::entity entity, LotComponent &lot)
{
	// Cleanup old mesh
	auto it = m_lotBaseMeshes.find(entity.id());
	if (it != m_lotBaseMeshes.end()) {
		if (it->second.entity) {
			try {
				m_sceneMgr->destroyEntity(it->second.entity);
			} catch (...) {
			}
		}
		if (!it->second.meshName.empty()) {
			try {
				Ogre::MeshManager::getSingleton().remove(
					it->second.meshName);
			} catch (...) {
			}
		}
		destroyPhysicsColliders(entity);
		m_lotBaseMeshes.erase(it);
	}

	// Validate transform
	if (!entity.has<TransformComponent>()) {
		Ogre::LogManager::getSingleton().logMessage(
			"CellGrid: Lot has no TransformComponent");
		return;
	}
	auto &transform = entity.get_mut<TransformComponent>();
	if (!transform.node) {
		Ogre::LogManager::getSingleton().logMessage(
			"CellGrid: Lot has no SceneNode");
		return;
	}

	// Find parent district and town for radius and material
	flecs::entity districtEntity = flecs::entity::null();
	flecs::entity townEntity = flecs::entity::null();
	float districtRadius = 50.0f;
	std::string materialName = "Ogre/StandardFloor";

	// Material hierarchy: Lot -> District -> Town
	// 1. Check Lot's own material first
	if (lot.proceduralMaterialEntity.is_valid() && lot.proceduralMaterialEntity.is_alive() &&
	    lot.proceduralMaterialEntity.has<ProceduralMaterialComponent>()) {
		const auto &mat = lot.proceduralMaterialEntity
					  .get<ProceduralMaterialComponent>();
		if (!mat.materialName.empty()) {
			materialName = mat.materialName;
			Ogre::LogManager::getSingleton().logMessage(
				"CellGrid: Using Lot's own material: " +
				materialName);
		}
	}

	// 2. Walk up hierarchy for District and Town, and fallback materials
	flecs::entity parent = entity.parent();
	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_valid() && district.proceduralMaterialEntity.is_alive() &&
			    district.proceduralMaterialEntity
				    .has<ProceduralMaterialComponent>()) {
				const auto &mat =
					district.proceduralMaterialEntity.get<
						ProceduralMaterialComponent>();
				if (!mat.materialName.empty()) {
					materialName = mat.materialName;
					Ogre::LogManager::getSingleton().logMessage(
						"CellGrid: Using District material: " +
						materialName);
				}
			}
		}
		if (parent.has<TownComponent>()) {
			townEntity = parent;
			// Check Town's ProceduralMaterial first, then generated material
			if (materialName == "Ogre/StandardFloor") {
				auto &town = parent.get<TownComponent>();
				if (town.proceduralMaterialEntity.is_valid() && town.proceduralMaterialEntity.is_alive() &&
				    town.proceduralMaterialEntity.has<
					    ProceduralMaterialComponent>()) {
					const auto &mat =
						town.proceduralMaterialEntity.get<
							ProceduralMaterialComponent>();
					if (!mat.materialName.empty()) {
						materialName = mat.materialName;
						Ogre::LogManager::getSingleton()
							.logMessage(
								"CellGrid: Using Town ProceduralMaterial: " +
								materialName);
					}
				}
				// Fallback to Town's generated material
				if (materialName == "Ogre/StandardFloor" &&
				    !town.materialName.empty()) {
					materialName = town.materialName;
					Ogre::LogManager::getSingleton().logMessage(
						"CellGrid: Using Town generated material: " +
						materialName);
				}
			}
			break;
		}
		parent = parent.parent();
	}

	if (!districtEntity.is_valid() || !districtEntity.is_alive()) {
		Ogre::LogManager::getSingleton().logMessage(
			"CellGrid: Lot has no parent District");
		return;
	}

	Ogre::LogManager::getSingleton().logMessage(
		"CellGrid: Material selection for lot " +
		std::to_string(entity.id()) + " final materialName='" +
		materialName + "'");

	// Position the Lot's SceneNode at the correct location around the district
	Ogre::Quaternion lotRotation(Ogre::Degree(lot.angle),
				     Ogre::Vector3::UNIT_Y);
	Ogre::Vector3 lotOffset =
		lotRotation * (Ogre::Vector3::UNIT_Z * districtRadius);
	lotOffset += Ogre::Vector3(lot.offsetX, 0, lot.offsetZ);

	// Set the Lot's position (CellGrid children will inherit this)
	transform.node->setPosition(lotOffset);
	transform.node->setOrientation(lotRotation);

	// Update the TransformComponent to match
	transform.position = lotOffset;
	transform.rotation = lotRotation;

	Ogre::LogManager::getSingleton().logMessage(
		"CellGrid: Building lot base for entity " +
		std::to_string(entity.id()) + " at position (" +
		std::to_string(lotOffset.x) + ", " +
		std::to_string(lotOffset.y) + ", " +
		std::to_string(lotOffset.z) +
		") with material: " + materialName);

	// Build lot base geometry (box foundation)
	Procedural::TriangleBuffer tb;

	const float cellSize = 4.0f;
	const float baseHeight = 4.0f;
	float width = cellSize * lot.width;
	float depth = cellSize * lot.depth;

	// Create box for lot base
	Procedural::BoxGenerator()
		.setSizeX(width)
		.setSizeY(baseHeight)
		.setSizeZ(depth)
		.setNumSegY(8)
		.setNumSegX(8)
		.setNumSegZ(8)
		.setEnableNormals(true)
		.setPosition(Ogre::Vector3(
			0.0f, -baseHeight / 2.0f + lot.elevation - 0.01f, 0.0f))
		.addToTriangleBuffer(tb);

	// Determine which texture rect to use (Lot -> District -> Town)
	std::string textureRectToUse;
	flecs::entity materialEntityToUse;

	// If Lot has its own material, use its texture rect
	if (lot.proceduralMaterialEntity.is_valid() && lot.proceduralMaterialEntity.is_alive()) {
		textureRectToUse = lot.textureRectName;
		materialEntityToUse = lot.proceduralMaterialEntity;
		if (!textureRectToUse.empty()) {
			Ogre::LogManager::getSingleton().logMessage(
				"CellGrid: Using Lot's own texture rect: " +
				textureRectToUse);
		}
	} else {
		// Lot uses District/Town material - look up hierarchy for texture rect
		flecs::entity parent = entity.parent();
		while (parent.is_valid() && parent.is_alive()) {
			if (parent.has<DistrictComponent>()) {
				auto &district =
					parent.get<DistrictComponent>();
				if (!district.textureRectName.empty()) {
					textureRectToUse =
						district.textureRectName;
					materialEntityToUse =
						district.proceduralMaterialEntity;
					Ogre::LogManager::getSingleton().logMessage(
						"CellGrid: Using District texture rect: " +
						textureRectToUse);
					break;
				}
			}
			if (parent.has<TownComponent>()) {
				auto &town = parent.get<TownComponent>();
				if (!town.textureRectName.empty()) {
					textureRectToUse = town.textureRectName;
					materialEntityToUse =
						town.proceduralMaterialEntity;
					Ogre::LogManager::getSingleton().logMessage(
						"CellGrid: Using Town texture rect: " +
						textureRectToUse);
				}
				break;
			}
			parent = parent.parent();
		}
	}

	// Find texture entity from the material that was selected
	flecs::entity textureEntity = flecs::entity::null();
	
	// Apply UV mapping
	if (!textureRectToUse.empty()) {
		if (materialEntityToUse.is_valid() && materialEntityToUse.is_alive() &&
		    materialEntityToUse.has<ProceduralMaterialComponent>()) {
			const auto &mat =
				materialEntityToUse
					.get<ProceduralMaterialComponent>();
			if (mat.diffuseTextureEntity.is_valid() && mat.diffuseTextureEntity.is_alive()) {
				textureEntity = mat.diffuseTextureEntity;
			}
		}
		// Fallback to District/Town texture
		if (!textureEntity.is_valid()) {
			flecs::entity parent = entity.parent();
			while (parent.is_valid() && parent.is_alive()) {
				if (parent.has<DistrictComponent>()) {
					auto &district =
						parent.get<DistrictComponent>();
					if (district.proceduralMaterialEntity
						    .is_valid() &&
					    district.proceduralMaterialEntity.is_alive() &&
					    district.proceduralMaterialEntity.has<
						    ProceduralMaterialComponent>()) {
						const auto &mat =
							district.proceduralMaterialEntity
								.get<ProceduralMaterialComponent>();
						if (mat.diffuseTextureEntity
							    .is_valid() &&
						    mat.diffuseTextureEntity.is_alive()) {
							textureEntity =
								mat.diffuseTextureEntity;
							break;
						}
					}
				}
				if (parent.has<TownComponent>()) {
					auto &town =
						parent.get<TownComponent>();
					if (town.proceduralMaterialEntity
						    .is_valid() &&
					    town.proceduralMaterialEntity.is_alive() &&
					    town.proceduralMaterialEntity.has<
						    ProceduralMaterialComponent>()) {
						const auto &mat =
							town.proceduralMaterialEntity
								.get<ProceduralMaterialComponent>();
						if (mat.diffuseTextureEntity
							    .is_valid() &&
						    mat.diffuseTextureEntity.is_alive()) {
							textureEntity =
								mat.diffuseTextureEntity;
							break;
						}
					}
					break;
				}
				parent = parent.parent();
			}
		}

		if (textureEntity.is_valid() && textureEntity.is_alive() &&
		    textureEntity.has<ProceduralTextureComponent>()) {
			const auto &texture =
				textureEntity.get<ProceduralTextureComponent>();
			const TextureRectInfo *rect =
				texture.getNamedRect(textureRectToUse);
			if (rect) {
				// Apply UV mapping with configurable margin
				const float margin = texture.uvMargin;
				float uRange = (rect->u2 - rect->u1) *
					       (1.0f - 2.0f * margin);
				float vRange = (rect->v2 - rect->v1) *
					       (1.0f - 2.0f * margin);
				float uOffset = rect->u1 +
						(rect->u2 - rect->u1) * margin;
				float vOffset = rect->v1 +
						(rect->v2 - rect->v1) * margin;

				for (auto &v : tb.getVertices()) {
					v.mUV.x = uOffset + v.mUV.x * uRange;
					v.mUV.y = vOffset + v.mUV.y * vRange;
				}
				Ogre::LogManager::getSingleton().logMessage(
					"CellGrid: Applied UV mapping for rect '" +
					textureRectToUse + "'");
			} else {
				Ogre::LogManager::getSingleton().logMessage(
					"CellGrid: WARNING - Rect not found: " +
					textureRectToUse);
			}
		} else {
			Ogre::LogManager::getSingleton().logMessage(
				"CellGrid: WARNING - No texture entity for rect: " +
				textureRectToUse);
		}
	} else {
		// Default UV mapping for texture atlas (same as original town.cpp)
		Ogre::LogManager::getSingleton().logMessage(
			"CellGrid: Using default UV mapping (no texture rect set)");
		for (auto &v : tb.getVertices()) {
			v.mUV *= 0.08f;
			v.mUV.x += 0.41f;
			v.mUV.x = Ogre::Math::Clamp(v.mUV.x, 0.4f, 0.5f);
			v.mUV.y = Ogre::Math::Clamp(v.mUV.y, 0.0f, 0.1f);
		}
	}

	// Create mesh and entity
	std::string meshName = "LotBase_" + std::to_string(entity.id()) + "_" +
			       std::to_string(m_meshCount++);
	try {
		if (tb.getVertices().size() < 3) {
			Ogre::LogManager::getSingleton().logMessage(
				"CellGrid: ERROR - Not enough vertices for lot base mesh");
			return;
		}

		Ogre::MeshPtr mesh = tb.transformToMesh(meshName);
		if (mesh.isNull()) {
			Ogre::LogManager::getSingleton().logMessage(
				"CellGrid: ERROR - transformToMesh returned null for lot base");
			return;
		}

		if (!materialName.empty() && mesh->getNumSubMeshes() > 0) {
			mesh->getSubMesh(0)->setMaterialName(materialName);
			Ogre::LogManager::getSingleton().logMessage(
				"CellGrid: Set lot base material to: " +
				materialName);
		} else {
			Ogre::LogManager::getSingleton().logMessage(
				"CellGrid: WARNING - Could not set material. materialName='" +
				materialName + "' subMeshes=" +
				std::to_string(mesh->getNumSubMeshes()));
		}

		Ogre::Entity *lotBaseEntity =
			m_sceneMgr->createEntity(meshName);
		if (!lotBaseEntity) {
			Ogre::LogManager::getSingleton().logMessage(
				"CellGrid: ERROR - createEntity returned null for lot base");
			return;
		}

		// Attach to the Lot's SceneNode directly (position already set above)
		transform.node->attachObject(lotBaseEntity);

		// Debug: Check actual material
		if (lotBaseEntity->getNumSubEntities() > 0) {
			std::string actualMat = lotBaseEntity->getSubEntity(0)
							->getMaterialName();
			Ogre::LogManager::getSingleton().logMessage(
				"CellGrid: Lot base actual material: " +
				actualMat);
		}

		LotBaseData data;
		data.meshName = meshName;
		data.entity = lotBaseEntity;
		// Store texture dependency for automatic rebuild when texture changes
		// textureEntity was determined earlier in the function
		if (textureEntity.is_valid() && textureEntity.is_alive() &&
		    textureEntity.has<ProceduralTextureComponent>()) {
			data.textureEntity = textureEntity;
			const auto &tex = textureEntity.get<ProceduralTextureComponent>();
			data.textureVersion = tex.version;
		}
		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 " +
			std::to_string(tb.getVertices().size()) + " vertices");

	} catch (const Ogre::Exception &e) {
		Ogre::LogManager::getSingleton().logMessage(
			"CellGrid: Failed to create lot base mesh: " +
			e.getDescription());
	}
}

void CellGridSystem::applyUVMappingToBuffer(Procedural::TriangleBuffer &tb,
					    const std::string &rectName,
					    flecs::entity materialEntity)
{
	if (tb.getVertices().empty())
		return;

	// If no rect specified, use default UV mapping
	if (rectName.empty()) {
		// Default mapping - scale down UVs to a small atlas region
		for (auto &v : tb.getVertices()) {
			v.mUV *= 0.08f;
			v.mUV.x += 0.41f;
			v.mUV.x = Ogre::Math::Clamp(v.mUV.x, 0.4f, 0.5f);
			v.mUV.y = Ogre::Math::Clamp(v.mUV.y, 0.0f, 0.1f);
		}
		return;
	}

	Ogre::LogManager::getSingleton().logMessage(
		"CellGrid: Looking for texture rect: " + rectName);

	// Find texture entity from material
	flecs::entity textureEntity = flecs::entity::null();
	if (materialEntity.is_valid() && materialEntity.is_alive() &&
	    materialEntity.has<ProceduralMaterialComponent>()) {
		const auto &mat =
			materialEntity.get<ProceduralMaterialComponent>();
		if (mat.diffuseTextureEntity.is_valid() && mat.diffuseTextureEntity.is_alive()) {
			textureEntity = mat.diffuseTextureEntity;
		}
	}

	if (!textureEntity.is_valid() || !textureEntity.is_alive()) {
		Ogre::LogManager::getSingleton().logMessage(
			"CellGrid: WARNING - No texture entity for rect: " +
			rectName);
		// Fall back to default UV mapping
		for (auto &v : tb.getVertices()) {
			v.mUV *= 0.08f;
			v.mUV.x += 0.41f;
			v.mUV.x = Ogre::Math::Clamp(v.mUV.x, 0.4f, 0.5f);
			v.mUV.y = Ogre::Math::Clamp(v.mUV.y, 0.0f, 0.1f);
		}
		return;
	}

	if (!textureEntity.has<ProceduralTextureComponent>()) {
		Ogre::LogManager::getSingleton().logMessage(
			"CellGrid: WARNING - Texture entity has no ProceduralTextureComponent for rect: " +
			rectName);
		// Fall back to default UV mapping
		for (auto &v : tb.getVertices()) {
			v.mUV *= 0.08f;
			v.mUV.x += 0.41f;
			v.mUV.x = Ogre::Math::Clamp(v.mUV.x, 0.4f, 0.5f);
			v.mUV.y = Ogre::Math::Clamp(v.mUV.y, 0.0f, 0.1f);
		}
		return;
	}

	const auto &texture = textureEntity.get<ProceduralTextureComponent>();
	const TextureRectInfo *rect = texture.getNamedRect(rectName);
	if (!rect) {
		Ogre::LogManager::getSingleton().logMessage(
			"CellGrid: WARNING - Rect not found: " + rectName);
		// Fall back to default UV mapping
		for (auto &v : tb.getVertices()) {
			v.mUV *= 0.08f;
			v.mUV.x += 0.41f;
			v.mUV.x = Ogre::Math::Clamp(v.mUV.x, 0.4f, 0.5f);
			v.mUV.y = Ogre::Math::Clamp(v.mUV.y, 0.0f, 0.1f);
		}
		return;
	}

	Ogre::LogManager::getSingleton().logMessage(
		"CellGrid: Applying rect " + rectName +
		" u=" + std::to_string(rect->u1) + "-" +
		std::to_string(rect->u2) + " v=" + std::to_string(rect->v1) +
		"-" + std::to_string(rect->v2));

	// Apply texture rectangle UV mapping with configurable margin
	const float margin = texture.uvMargin;
	float uRange = (rect->u2 - rect->u1) * (1.0f - 2.0f * margin);
	float vRange = (rect->v2 - rect->v1) * (1.0f - 2.0f * margin);
	float uOffset = rect->u1 + (rect->u2 - rect->u1) * margin;
	float vOffset = rect->v1 + (rect->v2 - rect->v1) * margin;

	for (auto &v : tb.getVertices()) {
		v.mUV.x = uOffset + v.mUV.x * uRange;
		v.mUV.y = vOffset + v.mUV.y * vRange;
	}
}

// ============================================================================
// Window and Door Frame Methods
// ============================================================================

void CellGridSystem::buildFrames(flecs::entity entity,
				 const CellGridComponent &grid,
				 const std::string &materialName)
{
	// Destroy old frames first
	destroyFrames(entity);

	// Get material entity for UV mapping
	flecs::entity materialEntity = flecs::entity::null();
	flecs::entity parent = entity.parent();
	while (parent.is_valid() && parent.is_alive()) {
		if (parent.has<LotComponent>()) {
			auto &lot = parent.get<LotComponent>();
			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_valid() && district.proceduralMaterialEntity.is_alive()) {
				materialEntity =
					district.proceduralMaterialEntity;
				break;
			}
		}
		if (parent.has<TownComponent>()) {
			auto &town = parent.get<TownComponent>();
			if (town.proceduralMaterialEntity.is_valid() && town.proceduralMaterialEntity.is_alive()) {
				materialEntity = town.proceduralMaterialEntity;
				break;
			}
		}
		parent = parent.parent();
	}

	// Get the transform for frame positioning
	Ogre::SceneNode *transformNode = nullptr;
	if (entity.has<TransformComponent>()) {
		auto &transform = entity.get<TransformComponent>();
		transformNode = transform.node;
	}
	if (!transformNode) {
		Ogre::LogManager::getSingleton().logMessage(
			"CellGrid: No transform node for frames!");
		return;
	}
	
	// Get parent transform for world position calculation
	Ogre::Vector3 parentPos = transformNode->_getDerivedPosition();
	Ogre::Quaternion parentRot = transformNode->_getDerivedOrientation();
	Ogre::Vector3 parentScale = transformNode->_getDerivedScale();

	// Create frame meshes with unique names per entity
	std::string meshPrefix =
		"CellGrid_" + std::to_string(entity.id()) + "_";
	createWindowFrameMeshes(grid, materialName, meshPrefix, materialEntity);
	createDoorFrameMeshes(grid, materialName, meshPrefix, materialEntity);
	
	// Create or get StaticGeometry region for frames
	std::string regionName = "FrameRegion_" + std::to_string(entity.id());
	Ogre::StaticGeometry* staticGeom = nullptr;
	
	auto it = m_entityMeshes.find(entity.id());
	if (it != m_entityMeshes.end() && it->second.frameStaticGeometry) {
		// Reuse existing static geometry - destroy it first
		m_sceneMgr->destroyStaticGeometry(it->second.frameStaticGeometry);
		it->second.frameStaticGeometry = nullptr;
	}
	
	staticGeom = m_sceneMgr->createStaticGeometry(regionName);
	staticGeom->setCastShadows(true);
	staticGeom->setRegionDimensions(Ogre::Vector3(1000.0f)); // Large region for all frames
	
	// Place frames into StaticGeometry (batched into single draw call)
	int frameCount = 0;
	placeWindowFramesInStaticGeometry(entity, grid, parentPos, parentRot, parentScale, staticGeom, frameCount);
	placeDoorFramesInStaticGeometry(entity, grid, parentPos, parentRot, parentScale, staticGeom, frameCount);
	
	Ogre::LogManager::getSingleton().logMessage(
		"CellGrid: Queued " + std::to_string(frameCount) +
		" frames for StaticGeometry batching");

	// Store reference
	if (it != m_entityMeshes.end()) {
		it->second.frameStaticGeometry = staticGeom;
	} else {
		// Clean up if we can't store it
		m_sceneMgr->destroyStaticGeometry(staticGeom);
	}
}

void CellGridSystem::createWindowFrameMeshes(const CellGridComponent &grid,
					     const std::string &materialName,
					     const std::string &meshPrefix,
					     flecs::entity materialEntity)
{
	// Create unique mesh names per CellGrid entity
	std::string extFrameMeshName = meshPrefix + "window_external";
	std::string intFrameMeshName = meshPrefix + "window_internal";

	// Destroy old meshes if they exist
	auto &meshMgr = Ogre::MeshManager::getSingleton();
	if (meshMgr.resourceExists(extFrameMeshName)) {
		meshMgr.remove(extFrameMeshName);
	}
	if (meshMgr.resourceExists(intFrameMeshName)) {
		meshMgr.remove(intFrameMeshName);
	}

	// Window dimensions (scaled by cellSize/cellHeight)
	float windowWidth = 3.2f * (grid.cellSize / 4.0f);
	float windowHeight = 2.0f * (grid.cellHeight / 4.0f);
	float frameDepth = 0.34f /* * (grid.cellSize / 4.0f) */;
	float frameThickness = 0.1f /* * (grid.cellSize / 4.0f) */;

	// Create external window frame mesh
	Procedural::TriangleBuffer extFrameTb;

	// Left frame
	Procedural::BoxGenerator()
		.setSizeX(frameThickness)
		.setSizeY(windowHeight)
		.setSizeZ(frameDepth)
		.setNumSegX(1)
		.setNumSegY(2)
		.setNumSegZ(1)
		.setPosition(
			Ogre::Vector3(-windowWidth / 2 + frameThickness / 2,
				      windowHeight / 2, 0))
		.setEnableNormals(true)
		.addToTriangleBuffer(extFrameTb);

	// Right frame
	Procedural::BoxGenerator()
		.setSizeX(frameThickness)
		.setSizeY(windowHeight)
		.setSizeZ(frameDepth)
		.setNumSegX(1)
		.setNumSegY(2)
		.setNumSegZ(1)
		.setPosition(Ogre::Vector3(windowWidth / 2 - frameThickness / 2,
					   windowHeight / 2, 0))
		.setEnableNormals(true)
		.addToTriangleBuffer(extFrameTb);

	// Top frame
	Procedural::BoxGenerator()
		.setSizeX(windowWidth)
		.setSizeY(frameThickness)
		.setSizeZ(frameDepth)
		.setNumSegX(2)
		.setNumSegY(1)
		.setNumSegZ(1)
		.setPosition(
			Ogre::Vector3(0, windowHeight - frameThickness / 2, 0))
		.setEnableNormals(true)
		.addToTriangleBuffer(extFrameTb);

	// Windowsill (extends outward)
	float sillDepth = frameDepth * 1.5f;
	Procedural::BoxGenerator()
		.setSizeX(windowWidth + frameThickness * 2)
		.setSizeY(frameThickness * 1.5f)
		.setSizeZ(sillDepth)
		.setNumSegX(2)
		.setNumSegY(1)
		.setNumSegZ(1)
		.setPosition(Ogre::Vector3(0, frameThickness * 0.75f,
					   -(sillDepth - frameDepth) / 2))
		.setEnableNormals(true)
		.addToTriangleBuffer(extFrameTb);

	// Apply UV mapping and create mesh
	applyUVMappingToBuffer(extFrameTb, grid.extWindowFrameRectName,
			       materialEntity);
	Ogre::MeshPtr extMesh = extFrameTb.transformToMesh(extFrameMeshName);
	if (!materialName.empty() && extMesh->getNumSubMeshes() > 0) {
		extMesh->getSubMesh(0)->setMaterialName(materialName);
	}
	generateLodForMesh(extMesh);

	// Create internal window frame mesh (simpler, no sill)
	Procedural::TriangleBuffer intFrameTb;
	float intFrameDepth = frameDepth * 0.5f;

	// Left frame
	Procedural::BoxGenerator()
		.setSizeX(frameThickness)
		.setSizeY(windowHeight)
		.setSizeZ(intFrameDepth)
		.setNumSegX(1)
		.setNumSegY(2)
		.setNumSegZ(1)
		.setPosition(
			Ogre::Vector3(-windowWidth / 2 + frameThickness / 2,
				      windowHeight / 2, 0))
		.setEnableNormals(true)
		.addToTriangleBuffer(intFrameTb);

	// Right frame
	Procedural::BoxGenerator()
		.setSizeX(frameThickness)
		.setSizeY(windowHeight)
		.setSizeZ(intFrameDepth)
		.setNumSegX(1)
		.setNumSegY(2)
		.setNumSegZ(1)
		.setPosition(Ogre::Vector3(windowWidth / 2 - frameThickness / 2,
					   windowHeight / 2, 0))
		.setEnableNormals(true)
		.addToTriangleBuffer(intFrameTb);

	// Top frame
	Procedural::BoxGenerator()
		.setSizeX(windowWidth)
		.setSizeY(frameThickness)
		.setSizeZ(intFrameDepth)
		.setNumSegX(2)
		.setNumSegY(1)
		.setNumSegZ(1)
		.setPosition(
			Ogre::Vector3(0, windowHeight - frameThickness / 2, 0))
		.setEnableNormals(true)
		.addToTriangleBuffer(intFrameTb);

	applyUVMappingToBuffer(intFrameTb, grid.intWindowFrameRectName,
			       materialEntity);
	Ogre::MeshPtr intMesh = intFrameTb.transformToMesh(intFrameMeshName);
	if (!materialName.empty() && intMesh->getNumSubMeshes() > 0) {
		intMesh->getSubMesh(0)->setMaterialName(materialName);
	}
	generateLodForMesh(intMesh);
}

void CellGridSystem::createDoorFrameMeshes(const CellGridComponent &grid,
					   const std::string &materialName,
					   const std::string &meshPrefix,
					   flecs::entity materialEntity)
{
	// Create unique mesh names per CellGrid entity
	std::string extFrameMeshName = meshPrefix + "door_external";
	std::string intFrameMeshName = meshPrefix + "door_internal";

	// Destroy old meshes if they exist
	auto &meshMgr = Ogre::MeshManager::getSingleton();
	if (meshMgr.resourceExists(extFrameMeshName)) {
		meshMgr.remove(extFrameMeshName);
	}
	if (meshMgr.resourceExists(intFrameMeshName)) {
		meshMgr.remove(intFrameMeshName);
	}

	// Door dimensions
	float doorWidth = 2.8f * (grid.cellSize / 4.0f);
	float doorHeight = 3.0f * (grid.cellHeight / 4.0f);
	float frameDepth = 0.34f /* * (grid.cellSize / 4.0f) */;
	float frameThickness = 0.1f /* * (grid.cellSize / 4.0f) */;

	// Create external door frame mesh
	Procedural::TriangleBuffer extFrameTb;
	float stepDepth = frameDepth * 1.2f;

	// Left frame with step
	Procedural::BoxGenerator()
		.setSizeX(frameThickness)
		.setSizeY(doorHeight)
		.setSizeZ(stepDepth)
		.setNumSegX(1)
		.setNumSegY(3)
		.setNumSegZ(1)
		.setPosition(Ogre::Vector3(-doorWidth / 2 + frameThickness / 2,
					   doorHeight / 2,
					   (stepDepth - frameDepth) / 2))
		.setEnableNormals(true)
		.addToTriangleBuffer(extFrameTb);

	// Right frame with step
	Procedural::BoxGenerator()
		.setSizeX(frameThickness)
		.setSizeY(doorHeight)
		.setSizeZ(stepDepth)
		.setNumSegX(1)
		.setNumSegY(3)
		.setNumSegZ(1)
		.setPosition(Ogre::Vector3(doorWidth / 2 - frameThickness / 2,
					   doorHeight / 2,
					   (stepDepth - frameDepth) / 2))
		.setEnableNormals(true)
		.addToTriangleBuffer(extFrameTb);

	// Top frame
	Procedural::BoxGenerator()
		.setSizeX(doorWidth)
		.setSizeY(frameThickness)
		.setSizeZ(stepDepth)
		.setNumSegX(2)
		.setNumSegY(1)
		.setNumSegZ(1)
		.setPosition(Ogre::Vector3(0, doorHeight + frameThickness / 2,
					   (stepDepth - frameDepth) / 2))
		.setEnableNormals(true)
		.addToTriangleBuffer(extFrameTb);

	// Step
	float stepWidth = doorWidth + frameThickness * 4;
	Procedural::BoxGenerator()
		.setSizeX(stepWidth)
		.setSizeY(frameThickness * 2.0f)
		.setSizeZ(stepDepth)
		.setNumSegX(2)
		.setNumSegY(1)
		.setNumSegZ(1)
		.setPosition(
			Ogre::Vector3(0, frameThickness,
				      (stepDepth - frameDepth) / 2 + 0.05f))
		.setEnableNormals(true)
		.addToTriangleBuffer(extFrameTb);
	Procedural::BoxGenerator()
		.setSizeX(stepWidth)
		.setSizeY(frameThickness)
		.setSizeZ(stepDepth)
		.setNumSegX(2)
		.setNumSegY(1)
		.setNumSegZ(1)
		.setPosition(Ogre::Vector3(0, frameThickness * 0.5f,
					   (stepDepth - frameDepth) / 2 +
						   0.05f + stepDepth))
		.setEnableNormals(true)
		.addToTriangleBuffer(extFrameTb);

	applyUVMappingToBuffer(extFrameTb, grid.extDoorFrameRectName,
			       materialEntity);
	Ogre::MeshPtr extMesh = extFrameTb.transformToMesh(extFrameMeshName);
	if (!materialName.empty() && extMesh->getNumSubMeshes() > 0) {
		extMesh->getSubMesh(0)->setMaterialName(materialName);
	}
	generateLodForMesh(extMesh);

	// Create internal door frame (simpler)
	Procedural::TriangleBuffer intFrameTb;
	float intFrameDepth = frameDepth * 0.5f;

	Procedural::BoxGenerator()
		.setSizeX(frameThickness)
		.setSizeY(doorHeight)
		.setSizeZ(intFrameDepth)
		.setNumSegX(1)
		.setNumSegY(2)
		.setNumSegZ(1)
		.setPosition(Ogre::Vector3(-doorWidth / 2 + frameThickness / 2,
					   doorHeight / 2, 0))
		.setEnableNormals(true)
		.addToTriangleBuffer(intFrameTb);

	Procedural::BoxGenerator()
		.setSizeX(frameThickness)
		.setSizeY(doorHeight)
		.setSizeZ(intFrameDepth)
		.setNumSegX(1)
		.setNumSegY(2)
		.setNumSegZ(1)
		.setPosition(Ogre::Vector3(doorWidth / 2 - frameThickness / 2,
					   doorHeight / 2, 0))
		.setEnableNormals(true)
		.addToTriangleBuffer(intFrameTb);

	Procedural::BoxGenerator()
		.setSizeX(doorWidth)
		.setSizeY(frameThickness)
		.setSizeZ(intFrameDepth)
		.setNumSegX(2)
		.setNumSegY(1)
		.setNumSegZ(1)
		.setPosition(
			Ogre::Vector3(0, doorHeight - frameThickness / 2, 0))
		.setEnableNormals(true)
		.addToTriangleBuffer(intFrameTb);

	applyUVMappingToBuffer(intFrameTb, grid.intDoorFrameRectName,
			       materialEntity);
	Ogre::MeshPtr intMesh = intFrameTb.transformToMesh(intFrameMeshName);
	if (!materialName.empty() && intMesh->getNumSubMeshes() > 0) {
		intMesh->getSubMesh(0)->setMaterialName(materialName);
	}
	generateLodForMesh(intMesh);
}

void CellGridSystem::placeWindowFrames(
	flecs::entity entity, const CellGridComponent &grid,
	Ogre::SceneNode *parentNode, std::vector<Ogre::Entity *> &frameEntities)
{
	std::string meshPrefix =
		"CellGrid_" + std::to_string(entity.id()) + "_";
	Ogre::MeshPtr extMesh = Ogre::MeshManager::getSingleton().getByName(
		meshPrefix + "window_external");
	Ogre::MeshPtr intMesh = Ogre::MeshManager::getSingleton().getByName(
		meshPrefix + "window_internal");

	if (!extMesh && !intMesh)
		return;

	float halfCell = grid.cellSize / 2.0f;
	float frameOffset = 0.0f /* * (grid.cellSize / 4.0f) */;
	// Window frame should match window geometry positioning
	// Window center Y = windowBottomOffset + windowHeight/2
	float windowHeight = 2.0f * (grid.cellHeight / 4.0f);
	float windowBottomOffset = 0.8f * (grid.cellHeight / 4.0f);
	float windowY = windowBottomOffset /* + windowHeight / 2.0f */;

	for (const auto &cell : grid.cells) {
		Ogre::Vector3 origin = grid.cellToWorld(cell.x, cell.y, cell.z);

		auto addFrame = [&](Ogre::MeshPtr mesh,
				    const Ogre::Vector3 &pos,
				    const Ogre::Quaternion &rot,
				    const char *name) {
			if (!mesh)
				return;
			// Position is relative to parent node (local coordinates)
			Ogre::LogManager::getSingleton().logMessage(
				"CellGrid: Placing " + Ogre::String(name) +
				" frame at local pos=" + std::to_string(pos.x) +
				"," + std::to_string(pos.y) + "," +
				std::to_string(pos.z));

			// Create entity
			Ogre::Entity *ent =
				m_sceneMgr->createEntity(mesh->getName());

			// Create child node attached to parent
			Ogre::SceneNode *frameNode =
				parentNode->createChildSceneNode();
			frameNode->setPosition(pos);
			frameNode->setOrientation(rot);
			frameNode->attachObject(ent);

			frameEntities.push_back(ent);
		};

		if (extMesh) {
			frameOffset = 0.1f;
			if (cell.hasFlag(CellFlags::WindowXNeg)) {
				Ogre::Vector3 pos =
					origin +
					Ogre::Vector3(-halfCell - frameOffset,
						      windowY, 0);
				Ogre::Quaternion rot(Ogre::Degree(-90),
						     Ogre::Vector3::UNIT_Y);
				addFrame(extMesh, pos, rot, "extWindowXNeg");
			}
			if (cell.hasFlag(CellFlags::WindowXPos)) {
				Ogre::Vector3 pos =
					origin +
					Ogre::Vector3(halfCell + frameOffset,
						      windowY, 0);
				Ogre::Quaternion rot(Ogre::Degree(90),
						     Ogre::Vector3::UNIT_Y);
				addFrame(extMesh, pos, rot, "extWindowXPos");
			}
			if (cell.hasFlag(CellFlags::WindowZNeg)) {
				Ogre::Vector3 pos =
					origin +
					Ogre::Vector3(0, windowY,
						      -halfCell - frameOffset);
				Ogre::Quaternion rot(Ogre::Degree(180),
						     Ogre::Vector3::UNIT_Y);
				addFrame(extMesh, pos, rot, "extWindowZNeg");
			}
			if (cell.hasFlag(CellFlags::WindowZPos)) {
				Ogre::Vector3 pos =
					origin +
					Ogre::Vector3(0, windowY,
						      halfCell + frameOffset);
				Ogre::Quaternion rot(Ogre::Degree(0),
						     Ogre::Vector3::UNIT_Y);
				addFrame(extMesh, pos, rot, "extWindowZPos");
			}
		}

		if (intMesh) {
			frameOffset = 0.1f;
			if (cell.hasFlag(CellFlags::IntWindowXNeg)) {
				Ogre::Vector3 pos =
					origin +
					Ogre::Vector3(-halfCell + frameOffset,
						      windowY, 0);
				Ogre::Quaternion rot(Ogre::Degree(-90),
						     Ogre::Vector3::UNIT_Y);
				addFrame(intMesh, pos, rot, "intWindowXNeg");
			}
			if (cell.hasFlag(CellFlags::IntWindowXPos)) {
				Ogre::Vector3 pos =
					origin +
					Ogre::Vector3(halfCell - frameOffset,
						      windowY, 0);
				Ogre::Quaternion rot(Ogre::Degree(90),
						     Ogre::Vector3::UNIT_Y);
				addFrame(intMesh, pos, rot, "intWindowXPos");
			}
			if (cell.hasFlag(CellFlags::IntWindowZNeg)) {
				Ogre::Vector3 pos =
					origin +
					Ogre::Vector3(0, windowY,
						      -halfCell + frameOffset);
				Ogre::Quaternion rot(Ogre::Degree(180),
						     Ogre::Vector3::UNIT_Y);
				addFrame(intMesh, pos, rot, "intWindowZNeg");
			}
			if (cell.hasFlag(CellFlags::IntWindowZPos)) {
				Ogre::Vector3 pos =
					origin +
					Ogre::Vector3(0, windowY,
						      halfCell - frameOffset);
				Ogre::Quaternion rot(Ogre::Degree(0),
						     Ogre::Vector3::UNIT_Y);
				addFrame(intMesh, pos, rot, "intWindowZPos");
			}
		}
	}
}

void CellGridSystem::placeDoorFrames(flecs::entity entity,
				     const CellGridComponent &grid,
				     Ogre::SceneNode *parentNode,
				     std::vector<Ogre::Entity *> &frameEntities)
{
	std::string meshPrefix =
		"CellGrid_" + std::to_string(entity.id()) + "_";
	Ogre::MeshPtr extMesh = Ogre::MeshManager::getSingleton().getByName(
		meshPrefix + "door_external");
	Ogre::MeshPtr intMesh = Ogre::MeshManager::getSingleton().getByName(
		meshPrefix + "door_internal");

	if (!extMesh && !intMesh)
		return;

	float halfCell = grid.cellSize / 2.0f;
	float frameOffset = 0.1f /* * (grid.cellSize / 4.0f) */;
	// Door frame should match door geometry positioning
	// External doors start at solidExtOffset=0, internal at solidIntOffset=0.1f
	float solidExtOffset = 0.0f;
	float solidIntOffset = 0.1f;
	float doorHeightBase = 3.0f * (grid.cellHeight / 4.0f);
	float extDoorHeight = doorHeightBase - solidExtOffset;
	float intDoorHeight = doorHeightBase - solidIntOffset;
	// Frame center Y = baseOffset + doorHeight/2
	float extDoorFrameY = solidExtOffset; /*+ extDoorHeight / 2.0f */
	;
	float intDoorFrameY = solidIntOffset /*+ intDoorHeight / 2.0f */;

	auto addFrame = [&](Ogre::MeshPtr mesh, const Ogre::Vector3 &pos,
			    const Ogre::Quaternion &rot, const char *name) {
		if (!mesh)
			return;
		// Position is relative to parent node (local coordinates)
		Ogre::LogManager::getSingleton().logMessage(
			"CellGrid: Placing " + Ogre::String(name) +
			" frame at local pos=" + std::to_string(pos.x) + "," +
			std::to_string(pos.y) + "," + std::to_string(pos.z));

		// Create entity
		Ogre::Entity *ent = m_sceneMgr->createEntity(mesh->getName());

		// Create child node attached to parent
		Ogre::SceneNode *frameNode = parentNode->createChildSceneNode();
		frameNode->setPosition(pos);
		frameNode->setOrientation(rot);
		frameNode->attachObject(ent);

		frameEntities.push_back(ent);
	};

	for (const auto &cell : grid.cells) {
		Ogre::Vector3 origin = grid.cellToWorld(cell.x, cell.y, cell.z);

		if (extMesh) {
			if (cell.hasFlag(CellFlags::DoorXNeg)) {
				Ogre::Vector3 pos =
					origin +
					Ogre::Vector3(-halfCell - frameOffset,
						      extDoorFrameY, 0);
				Ogre::Quaternion rot(Ogre::Degree(-90),
						     Ogre::Vector3::UNIT_Y);
				addFrame(extMesh, pos, rot, "extDoorXNeg");
			}
			if (cell.hasFlag(CellFlags::DoorXPos)) {
				Ogre::Vector3 pos =
					origin +
					Ogre::Vector3(halfCell + frameOffset,
						      extDoorFrameY, 0);
				Ogre::Quaternion rot(Ogre::Degree(90),
						     Ogre::Vector3::UNIT_Y);
				addFrame(extMesh, pos, rot, "extDoorXPos");
			}
			if (cell.hasFlag(CellFlags::DoorZNeg)) {
				Ogre::Vector3 pos =
					origin +
					Ogre::Vector3(0, extDoorFrameY,
						      -halfCell - frameOffset);
				Ogre::Quaternion rot(Ogre::Degree(180),
						     Ogre::Vector3::UNIT_Y);
				addFrame(extMesh, pos, rot, "extDoorZNeg");
			}
			if (cell.hasFlag(CellFlags::DoorZPos)) {
				Ogre::Vector3 pos =
					origin +
					Ogre::Vector3(0, extDoorFrameY,
						      halfCell + frameOffset);
				Ogre::Quaternion rot(Ogre::Degree(0),
						     Ogre::Vector3::UNIT_Y);
				addFrame(extMesh, pos, rot, "extDoorZPos");
			}
		}

		if (intMesh) {
			if (cell.hasFlag(CellFlags::IntDoorXNeg)) {
				Ogre::Vector3 pos =
					origin +
					Ogre::Vector3(-halfCell + frameOffset,
						      intDoorFrameY, 0);
				Ogre::Quaternion rot(Ogre::Degree(-90),
						     Ogre::Vector3::UNIT_Y);
				addFrame(intMesh, pos, rot, "intDoorXNeg");
			}
			if (cell.hasFlag(CellFlags::IntDoorXPos)) {
				Ogre::Vector3 pos =
					origin +
					Ogre::Vector3(halfCell - frameOffset,
						      intDoorFrameY, 0);
				Ogre::Quaternion rot(Ogre::Degree(90),
						     Ogre::Vector3::UNIT_Y);
				addFrame(intMesh, pos, rot, "intDoorXPos");
			}
			if (cell.hasFlag(CellFlags::IntDoorZNeg)) {
				Ogre::Vector3 pos =
					origin +
					Ogre::Vector3(0, intDoorFrameY,
						      -halfCell + frameOffset);
				Ogre::Quaternion rot(Ogre::Degree(180),
						     Ogre::Vector3::UNIT_Y);
				addFrame(intMesh, pos, rot, "intDoorZNeg");
			}
			if (cell.hasFlag(CellFlags::IntDoorZPos)) {
				Ogre::Vector3 pos =
					origin +
					Ogre::Vector3(0, intDoorFrameY,
						      halfCell - frameOffset);
				Ogre::Quaternion rot(Ogre::Degree(0),
						     Ogre::Vector3::UNIT_Y);
				addFrame(intMesh, pos, rot, "intDoorZPos");
			}
		}
	}
}

void CellGridSystem::destroyFrames(flecs::entity entity)
{
	auto it = m_entityMeshes.find(entity.id());
	if (it != m_entityMeshes.end()) {
		// Destroy StaticGeometry if present (replaces individual entities)
		if (it->second.frameStaticGeometry) {
			try {
				m_sceneMgr->destroyStaticGeometry(it->second.frameStaticGeometry);
			} catch (...) {
			}
			it->second.frameStaticGeometry = nullptr;
		}
		
		// Destroy old frame entities (legacy cleanup)
		for (auto *ent : it->second.frameEntities) {
			if (ent) {
				// Get the node that owns this entity
				Ogre::SceneNode *node =
					ent->getParentSceneNode();
				if (node) {
					// Detach and destroy the node (this also detaches the entity)
					node->getParentSceneNode()->removeChild(
						node);
					m_sceneMgr->destroySceneNode(node);
				}
				// Destroy the entity
				try {
					m_sceneMgr->destroyEntity(ent);
				} catch (...) {
				}
			}
		}
		it->second.frameEntities.clear();
		it->second.framePlacements.clear();

		// Destroy frame meshes
		auto &meshMgr = Ogre::MeshManager::getSingleton();
		std::string meshPrefix =
			"CellGrid_" + std::to_string(entity.id()) + "_";

		try {
			meshMgr.remove(meshPrefix + "window_external");
		} catch (...) {
		}
		try {
			meshMgr.remove(meshPrefix + "window_internal");
		} catch (...) {
		}
		try {
			meshMgr.remove(meshPrefix + "door_external");
		} catch (...) {
		}
		try {
			meshMgr.remove(meshPrefix + "door_internal");
		} catch (...) {
		}
	}
}

// ============================================================================
// StaticGeometry Frame Placement (Batched for better performance)
// ============================================================================

void CellGridSystem::placeWindowFramesInStaticGeometry(
	flecs::entity entity, const CellGridComponent &grid,
	const Ogre::Vector3 &parentPos, const Ogre::Quaternion &parentRot,
	const Ogre::Vector3 &parentScale, Ogre::StaticGeometry *staticGeom,
	int &frameCount)
{
	std::string meshPrefix =
		"CellGrid_" + std::to_string(entity.id()) + "_";
	Ogre::MeshPtr extMesh = Ogre::MeshManager::getSingleton().getByName(
		meshPrefix + "window_external");
	Ogre::MeshPtr intMesh = Ogre::MeshManager::getSingleton().getByName(
		meshPrefix + "window_internal");

	if (!extMesh && !intMesh)
		return;

	float halfCell = grid.cellSize / 2.0f;
	float frameOffset = 0.0f;
	float windowHeight = 2.0f * (grid.cellHeight / 4.0f);
	float windowBottomOffset = 0.8f * (grid.cellHeight / 4.0f);
	float windowY = windowBottomOffset;

	for (const auto &cell : grid.cells) {
		Ogre::Vector3 origin = grid.cellToWorld(cell.x, cell.y, cell.z);

		auto addFrame = [&](Ogre::MeshPtr mesh, const Ogre::Vector3 &localPos,
				    const Ogre::Quaternion &localRot) {
			if (!mesh)
				return;
			
			// Calculate world position relative to parent
			Ogre::Vector3 worldPos = parentPos + (parentRot * (localPos * parentScale));
			Ogre::Quaternion worldRot = parentRot * localRot;
			
			// Create temporary entity to add to StaticGeometry
			Ogre::Entity* tempEnt = m_sceneMgr->createEntity(mesh->getName());
			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) {
			frameOffset = 0.1f;
			if (cell.hasFlag(CellFlags::WindowXNeg)) {
				Ogre::Vector3 pos = origin + Ogre::Vector3(-halfCell - frameOffset, windowY, 0);
				Ogre::Quaternion rot(Ogre::Degree(-90), Ogre::Vector3::UNIT_Y);
				addFrame(extMesh, pos, rot);
			}
			if (cell.hasFlag(CellFlags::WindowXPos)) {
				Ogre::Vector3 pos = origin + Ogre::Vector3(halfCell + frameOffset, windowY, 0);
				Ogre::Quaternion rot(Ogre::Degree(90), Ogre::Vector3::UNIT_Y);
				addFrame(extMesh, pos, rot);
			}
			if (cell.hasFlag(CellFlags::WindowZNeg)) {
				Ogre::Vector3 pos = origin + Ogre::Vector3(0, windowY, -halfCell - frameOffset);
				Ogre::Quaternion rot(Ogre::Degree(180), Ogre::Vector3::UNIT_Y);
				addFrame(extMesh, pos, rot);
			}
			if (cell.hasFlag(CellFlags::WindowZPos)) {
				Ogre::Vector3 pos = origin + Ogre::Vector3(0, windowY, halfCell + frameOffset);
				Ogre::Quaternion rot(Ogre::Degree(0), Ogre::Vector3::UNIT_Y);
				addFrame(extMesh, pos, rot);
			}
		}

		if (intMesh) {
			frameOffset = 0.1f;
			if (cell.hasFlag(CellFlags::IntWindowXNeg)) {
				Ogre::Vector3 pos = origin + Ogre::Vector3(-halfCell + frameOffset, windowY, 0);
				Ogre::Quaternion rot(Ogre::Degree(-90), Ogre::Vector3::UNIT_Y);
				addFrame(intMesh, pos, rot);
			}
			if (cell.hasFlag(CellFlags::IntWindowXPos)) {
				Ogre::Vector3 pos = origin + Ogre::Vector3(halfCell - frameOffset, windowY, 0);
				Ogre::Quaternion rot(Ogre::Degree(90), Ogre::Vector3::UNIT_Y);
				addFrame(intMesh, pos, rot);
			}
			if (cell.hasFlag(CellFlags::IntWindowZNeg)) {
				Ogre::Vector3 pos = origin + Ogre::Vector3(0, windowY, -halfCell + frameOffset);
				Ogre::Quaternion rot(Ogre::Degree(180), Ogre::Vector3::UNIT_Y);
				addFrame(intMesh, pos, rot);
			}
			if (cell.hasFlag(CellFlags::IntWindowZPos)) {
				Ogre::Vector3 pos = origin + Ogre::Vector3(0, windowY, halfCell - frameOffset);
				Ogre::Quaternion rot(Ogre::Degree(0), Ogre::Vector3::UNIT_Y);
				addFrame(intMesh, pos, rot);
			}
		}
	}
}

void CellGridSystem::placeDoorFramesInStaticGeometry(
	flecs::entity entity, const CellGridComponent &grid,
	const Ogre::Vector3 &parentPos, const Ogre::Quaternion &parentRot,
	const Ogre::Vector3 &parentScale, Ogre::StaticGeometry *staticGeom,
	int &frameCount)
{
	std::string meshPrefix =
		"CellGrid_" + std::to_string(entity.id()) + "_";
	Ogre::MeshPtr extMesh = Ogre::MeshManager::getSingleton().getByName(
		meshPrefix + "door_external");
	Ogre::MeshPtr intMesh = Ogre::MeshManager::getSingleton().getByName(
		meshPrefix + "door_internal");

	if (!extMesh && !intMesh)
		return;

	float halfCell = grid.cellSize / 2.0f;
	float frameOffset = 0.1f;
	float solidExtOffset = 0.0f;
	float solidIntOffset = 0.1f;
	float doorHeightBase = 3.0f * (grid.cellHeight / 4.0f);
	float extDoorFrameY = solidExtOffset;
	float intDoorFrameY = solidIntOffset;

	auto addFrame = [&](Ogre::MeshPtr mesh, const Ogre::Vector3 &localPos,
			    const Ogre::Quaternion &localRot) {
		if (!mesh)
			return;
		
		// Calculate world position relative to parent
		Ogre::Vector3 worldPos = parentPos + (parentRot * (localPos * parentScale));
		Ogre::Quaternion worldRot = parentRot * localRot;
		
		// Create temporary entity to add to StaticGeometry
		Ogre::Entity* tempEnt = m_sceneMgr->createEntity(mesh->getName());
		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) {
		Ogre::Vector3 origin = grid.cellToWorld(cell.x, cell.y, cell.z);

		if (extMesh) {
			if (cell.hasFlag(CellFlags::DoorXNeg)) {
				Ogre::Vector3 pos = origin + Ogre::Vector3(-halfCell - frameOffset, extDoorFrameY, 0);
				Ogre::Quaternion rot(Ogre::Degree(-90), Ogre::Vector3::UNIT_Y);
				addFrame(extMesh, pos, rot);
			}
			if (cell.hasFlag(CellFlags::DoorXPos)) {
				Ogre::Vector3 pos = origin + Ogre::Vector3(halfCell + frameOffset, extDoorFrameY, 0);
				Ogre::Quaternion rot(Ogre::Degree(90), Ogre::Vector3::UNIT_Y);
				addFrame(extMesh, pos, rot);
			}
			if (cell.hasFlag(CellFlags::DoorZNeg)) {
				Ogre::Vector3 pos = origin + Ogre::Vector3(0, extDoorFrameY, -halfCell - frameOffset);
				Ogre::Quaternion rot(Ogre::Degree(180), Ogre::Vector3::UNIT_Y);
				addFrame(extMesh, pos, rot);
			}
			if (cell.hasFlag(CellFlags::DoorZPos)) {
				Ogre::Vector3 pos = origin + Ogre::Vector3(0, extDoorFrameY, halfCell + frameOffset);
				Ogre::Quaternion rot(Ogre::Degree(0), Ogre::Vector3::UNIT_Y);
				addFrame(extMesh, pos, rot);
			}
		}

		if (intMesh) {
			if (cell.hasFlag(CellFlags::IntDoorXNeg)) {
				Ogre::Vector3 pos = origin + Ogre::Vector3(-halfCell + frameOffset, intDoorFrameY, 0);
				Ogre::Quaternion rot(Ogre::Degree(-90), Ogre::Vector3::UNIT_Y);
				addFrame(intMesh, pos, rot);
			}
			if (cell.hasFlag(CellFlags::IntDoorXPos)) {
				Ogre::Vector3 pos = origin + Ogre::Vector3(halfCell - frameOffset, intDoorFrameY, 0);
				Ogre::Quaternion rot(Ogre::Degree(90), Ogre::Vector3::UNIT_Y);
				addFrame(intMesh, pos, rot);
			}
			if (cell.hasFlag(CellFlags::IntDoorZNeg)) {
				Ogre::Vector3 pos = origin + Ogre::Vector3(0, intDoorFrameY, -halfCell + frameOffset);
				Ogre::Quaternion rot(Ogre::Degree(180), Ogre::Vector3::UNIT_Y);
				addFrame(intMesh, pos, rot);
			}
			if (cell.hasFlag(CellFlags::IntDoorZPos)) {
				Ogre::Vector3 pos = origin + Ogre::Vector3(0, intDoorFrameY, halfCell - frameOffset);
				Ogre::Quaternion rot(Ogre::Degree(0), Ogre::Vector3::UNIT_Y);
				addFrame(intMesh, pos, rot);
			}
		}
	}
}
