LuckyEngine needs a procedural building generator that creates floor plans at runtime using mesh generation. The system should start simple (walls, floors, ceilings, doors) but be modular enough to add room-specific customization later. The architecture follows the existing MeshFactory pattern (vertex/index arrays → MeshSource → StaticMesh → entity).
classDiagram
direction TB
class BuildingConfig {
+vec3 Origin
+vector~FloorConfig~ Floors
}
class FloorConfig {
+float Height
+float WallThickness
+float CeilingThickness
+vector~WallConfig~ Walls
}
class WallConfig {
+float Length
+float Height
+float Thickness
+vector~OpeningSpec~ Openings
}
class OpeningSpec {
+float OffsetAlongWall
+float Width
+float Height
+float BottomOffset
+bool IsDoor
}
class DoorConfig {
+float Width
+float Height
+float FrameThickness
+float FrameDepth
+bool HasPanel
}
class WindowConfig {
+float Width
+float Height
+float SillHeight
+float FrameThickness
+float FrameDepth
+bool HasGlass
}
BuildingConfig "1" *-- "1..*" FloorConfig : contains
FloorConfig "1" *-- "1..*" WallConfig : contains
WallConfig "1" *-- "0..*" OpeningSpec : contains
class GeneratorResult {
+vector~GeneratedMeshData~ Meshes
+vec3 LocalOffset
+string EntityName
+vector~GeneratorResult~ Children
}
class GeneratedMeshData {
+vector~Vertex~ Vertices
+vector~Index~ Indices
+string Name
}
GeneratorResult "1" *-- "1..*" GeneratedMeshData : contains
GeneratorResult "1" *-- "0..*" GeneratorResult : children
class MeshBuilder {
-vector~Vertex~ m_Vertices
-vector~Index~ m_Indices
+AddQuad(a, b, c, d, normal, uvMin, uvMax) uint32_t
+AddBox(center, halfExtents) void
+AddPlaneXZ(center, halfSize, faceUp) void
+Build(name) GeneratedMeshData
+GetVertices() vector~Vertex~
+GetIndices() vector~Index~
}
MeshBuilder ..> GeneratedMeshData : produces
class BuildingGenerator {
+Generate(Scene*, BuildingConfig)$ Entity
+GenerateMeshData(BuildingConfig)$ GeneratorResult
-MaterializeResult(Scene*, Entity, GeneratorResult)$ Entity
}
class FloorGenerator {
+Generate(FloorConfig, int floorIndex)$ GeneratorResult
}
class WallGenerator {
+Generate(WallConfig, DoorConfig, WindowConfig)$ GeneratorResult
-BuildWallSegments(openings, length, height, thickness)$
-AddRevealQuads(MeshBuilder, OpeningSpec, thickness)$
}
class CeilingGenerator {
+Generate(width, depth, thickness, name)$ GeneratorResult
}
class DoorGenerator {
+Generate(DoorConfig)$ GeneratorResult
}
class WindowGenerator {
+Generate(WindowConfig)$ GeneratorResult
}
class RoomConfig {
+RoomType Type
+vec3 LocalOffset
+float RotationDeg
+int PresetIndex
+KitchenLayout CustomLayout
}
FloorConfig "1" *-- "0..*" RoomConfig : contains
BuildingGenerator ..> FloorGenerator : calls per floor
BuildingGenerator ..> KitchenLayoutBuilder : calls per kitchen room
FloorGenerator ..> WallGenerator : calls per wall
FloorGenerator ..> CeilingGenerator : calls for slab
WallGenerator ..> DoorGenerator : calls per door opening
WallGenerator ..> WindowGenerator : calls per window opening
BuildingGenerator ..> GeneratorResult : returns
FloorGenerator ..> GeneratorResult : returns
WallGenerator ..> GeneratorResult : returns
CeilingGenerator ..> GeneratorResult : returns
DoorGenerator ..> GeneratorResult : returns
WindowGenerator ..> GeneratorResult : returns
WallGenerator ..> MeshBuilder : uses
DoorGenerator ..> MeshBuilder : uses
WindowGenerator ..> MeshBuilder : uses
CeilingGenerator ..> MeshBuilder : uses
BuildingGenerator ..> BuildingConfig : reads
FloorGenerator ..> FloorConfig : reads
WallGenerator ..> WallConfig : reads
WallGenerator ..> DoorConfig : reads
WallGenerator ..> WindowConfig : reads
DoorGenerator ..> DoorConfig : reads
WindowGenerator ..> WindowConfig : reads
flowchart TD
subgraph Input
BC[BuildingConfig]
end
subgraph "Generation Phase · pure data, no scene"
BG[BuildingGenerator::GenerateMeshData]
FG[FloorGenerator::Generate]
WG[WallGenerator::Generate]
CG[CeilingGenerator::Generate]
DG[DoorGenerator::Generate]
WNG[WindowGenerator::Generate]
MB[MeshBuilder]
BC --> BG
BG -->|per floor| FG
FG -->|per wall| WG
FG -->|floor + ceiling| CG
WG -->|per door opening| DG
WG -->|per window opening| WNG
WG --> MB
DG --> MB
WNG --> MB
CG --> MB
end
subgraph "Result Tree"
GR[GeneratorResult tree]
MB -->|Build| GMD[GeneratedMeshData]
GMD --> GR
end
subgraph "Materialization Phase · creates entities"
MAT[BuildingGenerator::MaterializeResult]
GR --> MAT
MAT -->|CreateChildEntity| ENT[Entity Hierarchy]
MAT -->|MeshSource + StaticMesh| SM[StaticMeshComponents]
end
subgraph "Scene Output"
ENT
SM
end
graph TD
B["Building · root entity"]
F0["Floor_0 · y = 0.0"]
F1["Floor_1 · y = 2.85"]
W0["Wall_0 · StaticMeshComponent"]
W1["Wall_1 · StaticMeshComponent"]
W2["Wall_2 · StaticMeshComponent"]
W3["Wall_3 · StaticMeshComponent"]
D0["Door_0 · StaticMeshComponent"]
WIN0["Window_0 · StaticMeshComponent"]
FS0["FloorSlab_0 · StaticMeshComponent"]
C0["Ceiling_0 · StaticMeshComponent"]
K0["Kitchen · KitchenLayoutBuilder root"]
KF0["Cabinet_0 · fixture"]
KF1["Stove_0 · fixture"]
KF2["Sink_0 · fixture"]
B --> F0
B --> F1
F0 --> W0
F0 --> W1
F0 --> W2
F0 --> W3
W0 --> D0
W1 --> WIN0
F0 --> FS0
F0 --> C0
F0 --> K0
K0 --> KF0
K0 --> KF1
K0 --> KF2
flowchart LR
subgraph "Wall with 1 door + 1 window"
direction TB
A["Left Panel · full height"] --- B["Above Door · partial"]
B --- C["Between · full height"]
C --- D["Above Window · partial"]
D --- E["Below Window · partial"]
E --- F["Right Panel · full height"]
end
subgraph "Per opening"
R["4 Reveal Quads · inner edges giving wall depth"]
DR["DoorGenerator or WindowGenerator · frame + panel"]
end
Walls are split into rectangular solid panels around openings. No CSG boolean subtraction — clean geometry, correct normals, straightforward UVs.
Hazel/src/Hazel/ProceduralGeneration/Building/
├── BuildingTypes.h All config structs + GeneratorResult + GeneratedMeshData
├── MeshBuilder.h/.cpp Vertex/index accumulation, AddQuad, AddBox, AddPlaneXZ
├── WallGenerator.h/.cpp Wall panels with gap-based door/window cutouts
├── DoorGenerator.h/.cpp Door frame + panel mesh
├── WindowGenerator.h/.cpp Window frame + glass pane mesh
├── CeilingGenerator.h/.cpp Floor/ceiling rectangular slabs
├── FloorGenerator.h/.cpp Orchestrates one story: walls + slabs
└── BuildingGenerator.h/.cpp Top-level: stacks floors, MaterializeResult → entities
The implementation adheres to all /send-pr rules:
Each phase is a separate PR targeting mujoco, each addressing a single task:
- PR 1:
BuildingTypes.h+MeshBuilder.h/.cpp— foundation types and utility - PR 2:
WallGenerator+CeilingGenerator— solid wall and slab generation - PR 3:
FloorGenerator+BuildingGenerator— orchestration + entity materialization - PR 4: Wall cutout algorithm +
DoorGenerator+WindowGenerator— openings
- Scene.h/.cpp — NOT modified.
BuildingGeneratoruses the existing publicCreateEntity/CreateChildEntityAPI. - EditorLayer.h — 1 line added: panel ID constant in
EditorPanelIDsnamespace. - EditorLayer.cpp — 2 lines added:
#includeandAddPanel<BuildingGeneratorPanel>registration call. No logic added. - TimeManager.h/.cpp — NOT modified. Building generation is on-demand, not per-frame.
MeshBuilder::AddQuadandAddBoxare building-generation-specific (oriented quads with UV control for architectural meshes). They do NOT duplicateMeshFactory::CreateBox/CreatePlanewhich returnAssetHandles —MeshBuilderaccumulates raw vertex data for composition before asset creation.- Asset registration follows the exact same
MeshSource → StaticMesh → AddMemoryOnlyAssetpattern asMeshFactory.
- All building code lives in
Hazel::ProceduralBuildingnamespace, self-contained underProceduralGeneration/Building/. - Only uses public APIs:
AssetManager::AddMemoryOnlyAsset,Scene::CreateEntity/CreateChildEntity,Entity::AddComponent<StaticMeshComponent>. - No
frienddeclarations, no exposing private members.
- C++20:
constexprdefaults, designated initializers for config structs,std::span<const T>for read-only params,[[nodiscard]]onGeneratemethods. - Performance:
reserve()on all vertex/index vectors (sizes known from geometry), pass configs byconst&, no heap allocations in hot paths (generation is on-demand, not per-frame). - Style: Tabs,
PascalCase,m_prefix,#pragma once, engine includes first. - Cross-platform: No platform-specific code needed.
Config defaults use k_ convention:
constexpr float k_DefaultWallHeight = 2.7f;
constexpr float k_DefaultWallThickness = 0.15f;
constexpr float k_DefaultDoorWidth = 0.9f;Only // comments explaining non-obvious "why" (e.g., why reveal quads face inward). No section banners, no restating the obvious.
PR 1 — Types + MeshBuilder:
BuildingTypes.h— all config structs,GeneratedMeshData,GeneratorResultMeshBuilder.h/.cpp—AddQuad,AddBox,AddPlaneXZ,Build
PR 2 — Wall + Ceiling generators:
3. WallGenerator.h/.cpp — solid walls (no openings yet)
4. CeilingGenerator.h/.cpp — rectangular slab
PR 3 — Floor + Building generators:
5. FloorGenerator.h/.cpp — assembles walls + ceiling into one story
6. BuildingGenerator.h/.cpp — stacks floors, MaterializeResult creates entities
PR 4 — Openings:
7. Wall cutout algorithm in WallGenerator
8. DoorGenerator.h/.cpp — frame + panel
9. WindowGenerator.h/.cpp — frame + glass pane
PR 5 — Editor Panel:
10. BuildingGeneratorPanel.h/.cpp — ImGui panel with config editing + "Generate" button
11. Panel registration in EditorLayer.h/.cpp (ID constant + AddPanel call)
BuildingGeneratorPanel (View > Building Generator) provides:
- Generate Building button at the top
- Origin vec3 property
- Door/Window Defaults — frame thickness, frame depth, has panel/glass
- Floor list — add/remove floors, each with:
- Height, wall thickness, ceiling thickness
- Wall list — add/remove walls, each with:
- Length property
- Opening list — add doors/windows with offset, width, height, bottom offset
Files:
LuckyEditor/src/Panels/BuildingGeneratorPanel.hLuckyEditor/src/Panels/BuildingGeneratorPanel.cppLuckyEditor/src/EditorLayer.h— addedBuildingGeneratorpanel IDLuckyEditor/src/EditorLayer.cpp— added include +AddPanelregistration
The existing kitchen generation system (branch Tommy/KitchenMerging) provides a complete pipeline for populating a kitchen area with fixtures (cabinets, stove, sink, fridge, island, etc.), materials/styles, and decorative objects. The building generator should delegate to it when a floor contains a kitchen room.
Hazel/src/Hazel/Scene/
├── KitchenFixtureTypes.h FixtureType enum (CabinetLower, Stove, Sink, Fridge, Island, etc.)
├── KitchenLayout.h KitchenLayout, FixtureGroup, FixtureStack, FixtureSlot, ArenaSpec
├── KitchenLayoutBuilder.h/.cpp BuildLayout(scene, layout) → root Entity; ClearKitchen; ValidateOverlaps
├── KitchenLayoutPresets.h/.cpp Preset layouts (L-shape, galley, U-shape, etc.)
├── KitchenLayoutRandomizer.h/.cpp Randomized layout generation
├── KitchenLayoutSerializer.h/.cpp JSON save/load for KitchenLayout
├── KitchenArenaBuilder.h/.cpp BuildArena(scene, kitchenRoot, ArenaSpec) — overhead lighting
├── KitchenObjectPlacer.h/.cpp PlaceObjects(scene, kitchenRoot, spec) — random counter objects
├── KitchenObjectMeshCatalog.h/.cpp Mesh asset catalog for kitchen objects
├── KitchenStyle.h/.cpp Material/style definitions for kitchen fixtures
└── KitchenStyleSerializer.h/.cpp JSON save/load for KitchenStyle
The kitchen system operates at the Scene/Entity level — KitchenLayoutBuilder::BuildLayout(scene, layout) creates a root "Kitchen" entity with all fixture children. This means integration happens after MaterializeResult, not during the mesh generation phase.
flowchart LR
BG[BuildingGenerator::Generate] -->|MaterializeResult| ENT[Entity Hierarchy]
ENT -->|per kitchen room| KLB[KitchenLayoutBuilder::BuildLayout]
KLB --> KE[Kitchen Entity parented under Floor]
KLB -->|optional| KAB[KitchenArenaBuilder::BuildArena]
KLB -->|optional| KOP[KitchenObjectPlacer::PlaceObjects]
To support room-specific generators like the kitchen, FloorConfig gains an optional room specification:
enum class RoomType : uint8_t { None = 0, Kitchen, COUNT };
struct RoomConfig
{
RoomType Type = RoomType::None;
glm::vec3 LocalOffset = { 0.0f, 0.0f, 0.0f }; // position within the floor
float RotationDeg = 0.0f; // Y-axis rotation
// Kitchen-specific: index into KitchenLayoutPresets, or -1 for custom
int PresetIndex = 0;
KitchenLayout CustomLayout; // used when PresetIndex == -1
};FloorConfig adds:
std::vector<RoomConfig> Rooms; // optional room-specific generatorsAfter MaterializeResult creates the entity hierarchy, BuildingGenerator::Generate iterates FloorConfig::Rooms and dispatches to the appropriate room generator:
for (const auto& room : floorConfig.Rooms)
{
if (room.Type == RoomType::Kitchen)
{
KitchenLayout layout = (room.PresetIndex >= 0)
? KitchenLayoutPresets::Get(room.PresetIndex)
: room.CustomLayout;
Entity kitchenRoot = KitchenLayoutBuilder::BuildLayout(sceneRef, layout);
// Parent under the floor entity and apply room offset
scene->ParentEntity(kitchenRoot, floorEntity);
kitchenRoot.Transform().Translation = room.LocalOffset;
kitchenRoot.Transform().SetRotationEuler({ 0, glm::radians(room.RotationDeg), 0 });
}
}PR 6 — Kitchen Integration (after kitchen system is merged from Tommy/KitchenMerging):
- Add
RoomTypeenum andRoomConfigstruct toBuildingTypes.h - Add
std::vector<RoomConfig> RoomstoFloorConfig - Add post-materialization room dispatch loop in
BuildingGenerator::Generate - Add kitchen room controls to
BuildingGeneratorPanel(preset selector, custom layout toggle) - Test: generate a building with a kitchen room, verify fixtures appear parented under the correct floor
The RoomType enum and RoomConfig pattern is extensible — add new room types (Bathroom, Bedroom, LivingRoom) by adding enum values and dispatching to their respective generators in the same post-materialization loop.
Hazel/src/Hazel/Renderer/MeshFactory.cpp— vertex/index → MeshSource → StaticMesh → AssetHandle patternHazel/src/Hazel/Renderer/Mesh.h— Vertex, Index, Submesh, MeshSource, StaticMeshHazel/src/Hazel/Scene/Scene.h— CreateEntity / CreateChildEntityHazel/src/Hazel/Scene/Components.h— StaticMeshComponent, TransformComponentHazel/src/Hazel/Asset/AssetManager.h— AddMemoryOnlyAsset
- Build the project — must compile with no errors
- Open the editor, go to View > Building Generator to open the panel
- Click Generate Building — entities should appear in the scene hierarchy
- Verify walls, floor slab, and ceiling render in the viewport
- Add openings (doors/windows) to walls, regenerate, verify cutouts with visible depth
- Add a second floor, regenerate, verify stacking
my comments & proposal here: https://gist.github.com/devrim/0cc8e2097646a791ebde92434c31c05c