Skip to content

Instantly share code, notes, and snippets.

@Shlombo
Last active March 13, 2026 03:57
Show Gist options
  • Select an option

  • Save Shlombo/f9237a6c01f49ef5b4bc2b3394c2ea55 to your computer and use it in GitHub Desktop.

Select an option

Save Shlombo/f9237a6c01f49ef5b4bc2b3394c2ea55 to your computer and use it in GitHub Desktop.
Building Generator Architecture

Procedural Building Generation System

Context

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).

UML Class Diagram

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
Loading

Data Flow Diagram

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
Loading

Entity Hierarchy Output

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
Loading

Wall Cutout Algorithm (Gap Approach)

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
Loading

Walls are split into rectangular solid panels around openings. No CSG boolean subtraction — clean geometry, correct normals, straightforward UVs.

File Structure

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

send-pr Compliance

The implementation adheres to all /send-pr rules:

PR Strategy (Rules 1, 2)

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

Minimal Core File Modifications (Rules 7, 13)

  • Scene.h/.cpp — NOT modified. BuildingGenerator uses the existing public CreateEntity/CreateChildEntity API.
  • EditorLayer.h — 1 line added: panel ID constant in EditorPanelIDs namespace.
  • EditorLayer.cpp — 2 lines added: #include and AddPanel<BuildingGeneratorPanel> registration call. No logic added.
  • TimeManager.h/.cpp — NOT modified. Building generation is on-demand, not per-frame.

No Duplicate Functions (Rule 3)

  • MeshBuilder::AddQuad and AddBox are building-generation-specific (oriented quads with UV control for architectural meshes). They do NOT duplicate MeshFactory::CreateBox/CreatePlane which return AssetHandles — MeshBuilder accumulates raw vertex data for composition before asset creation.
  • Asset registration follows the exact same MeshSource → StaticMesh → AddMemoryOnlyAsset pattern as MeshFactory.

System Boundaries (Rules 4, 5)

  • All building code lives in Hazel::ProceduralBuilding namespace, self-contained under ProceduralGeneration/Building/.
  • Only uses public APIs: AssetManager::AddMemoryOnlyAsset, Scene::CreateEntity/CreateChildEntity, Entity::AddComponent<StaticMeshComponent>.
  • No friend declarations, no exposing private members.

Code Standards (Rule 14)

  • C++20: constexpr defaults, designated initializers for config structs, std::span<const T> for read-only params, [[nodiscard]] on Generate methods.
  • Performance: reserve() on all vertex/index vectors (sizes known from geometry), pass configs by const&, 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.

Named Constants (Rule 18)

Config defaults use k_ convention:

constexpr float k_DefaultWallHeight = 2.7f;
constexpr float k_DefaultWallThickness = 0.15f;
constexpr float k_DefaultDoorWidth = 0.9f;

Comments (Rule 15)

Only // comments explaining non-obvious "why" (e.g., why reveal quads face inward). No section banners, no restating the obvious.

Implementation Order

PR 1 — Types + MeshBuilder:

  1. BuildingTypes.h — all config structs, GeneratedMeshData, GeneratorResult
  2. MeshBuilder.h/.cppAddQuad, 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)

Editor Panel

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.h
  • LuckyEditor/src/Panels/BuildingGeneratorPanel.cpp
  • LuckyEditor/src/EditorLayer.h — added BuildingGenerator panel ID
  • LuckyEditor/src/EditorLayer.cpp — added include + AddPanel registration

KitchenGenerator Integration

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.

Existing Kitchen System Architecture

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

Integration Point

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]
Loading

RoomConfig (New)

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 generators

Post-Materialization Hook

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

Implementation Order

PR 6 — Kitchen Integration (after kitchen system is merged from Tommy/KitchenMerging):

  1. Add RoomType enum and RoomConfig struct to BuildingTypes.h
  2. Add std::vector<RoomConfig> Rooms to FloorConfig
  3. Add post-materialization room dispatch loop in BuildingGenerator::Generate
  4. Add kitchen room controls to BuildingGeneratorPanel (preset selector, custom layout toggle)
  5. Test: generate a building with a kitchen room, verify fixtures appear parented under the correct floor

Future Room Types

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.

Critical Files to Reference

  • Hazel/src/Hazel/Renderer/MeshFactory.cpp — vertex/index → MeshSource → StaticMesh → AssetHandle pattern
  • Hazel/src/Hazel/Renderer/Mesh.h — Vertex, Index, Submesh, MeshSource, StaticMesh
  • Hazel/src/Hazel/Scene/Scene.h — CreateEntity / CreateChildEntity
  • Hazel/src/Hazel/Scene/Components.h — StaticMeshComponent, TransformComponent
  • Hazel/src/Hazel/Asset/AssetManager.h — AddMemoryOnlyAsset

Verification

  1. Build the project — must compile with no errors
  2. Open the editor, go to View > Building Generator to open the panel
  3. Click Generate Building — entities should appear in the scene hierarchy
  4. Verify walls, floor slab, and ceiling render in the viewport
  5. Add openings (doors/windows) to walls, regenerate, verify cutouts with visible depth
  6. Add a second floor, regenerate, verify stacking
@devrim

devrim commented Mar 13, 2026

Copy link
Copy Markdown

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment