Skip to content

Instantly share code, notes, and snippets.

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

  • Save devrim/0cc8e2097646a791ebde92434c31c05c to your computer and use it in GitHub Desktop.

Select an option

Save devrim/0cc8e2097646a791ebde92434c31c05c to your computer and use it in GitHub Desktop.
House Definition System - YAML-driven house composition with pluggable generators for LuckyEngine

House Definition System

YAML-driven house composition with pluggable generators and asset.luckyrobots.com integration

Why This Instead of the Original Proposal

The original Procedural Building Generation proposal is a solid C++ mesh-generation system for walls, doors, windows, and floors -- and we want to keep all of that code. The problem is its architecture: BuildingGenerator sits at the top and owns the whole pipeline, and room-specific things like the KitchenLayoutBuilder get bolted on as post-materialization hooks dispatched by room type. That means every new room feature (kitchen cabinets, garage doors, sliding doors, closet shelving) needs its own special-case integration path into the building generator.

In practice, all of these are the same class of problem -- composing assets and procedural geometry inside a room. A kitchen cabinet run along a wall is structurally no different from a garage door filling an opening or a sliding door on a rail. They all need room bounds, wall references, and access to both procedural mesh generation and downloadable assets. Making each one a separate post-hoc dispatch creates a system that gets harder to extend with every new room feature.

What we propose instead: flip the hierarchy. Instead of the building generator being the top-level system, make it one generator among many, all driven by a single YAML house definition file. The YAML describes the entire house -- structure, rooms, furniture, generators -- and a HouseLoader orchestrates everything in one pass. The original gist's wall/floor/ceiling code becomes the structural_generator. Kitchen cabinets become the cabinet_generator. Garage doors, sliding doors, future closets and staircases -- they're all just generators registered in the same registry, invoked by the same YAML syntax. No special cases, no post-materialization hacks, and artists can define entire houses without touching C++.

Concretely, this document proposes:

  • A YAML house definition format where the house is a tree of rooms, each typed (kitchen, living_room, garage, etc.)
  • Rooms contain asset placements (positioned references to assets from assets.luckyrobots.com with baked and mujoco_enabled flags) and generator invocations (pluggable helper systems like cabinet_generator, garage_door_generator, sliding_door_generator)
  • The procedural building code from the original gist becomes the structural generator -- all its classes (MeshBuilder, WallGenerator, etc.) are preserved, just wrapped in the generator interface
  • A generator registry so new generators can be added with one class + one registration call, no changes to the loader or room types

House YAML Format

# house.yaml โ€” complete house definition
house:
  name: "Suburban Home v1"
  version: 1

  # Structural shell โ€” walls, floors, ceilings generated procedurally
  structure:
    floors:
      - id: ground_floor
        height: 2.7
        wall_thickness: 0.15
        ceiling_thickness: 0.12
        footprint:  # outer boundary polygon (meters, CW winding)
          - [0, 0]
          - [12, 0]
          - [12, 8]
          - [0, 8]

  # Room definitions โ€” children of a floor
  rooms:
    # โ”€โ”€ Kitchen โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
    - id: kitchen
      type: kitchen                 # triggers kitchen-specific generators
      floor: ground_floor
      bounds:
        origin: [0, 0]
        size: [4, 3.5]

      # Generator invocation โ€” kitchen cabinets generator fills the walls
      generators:
        - type: cabinet_generator
          config:
            layout: L-shape           # L-shape | U-shape | galley | single-wall
            counter_height: 0.9
            upper_cabinet_height: 0.7
            walls: [north, west]      # which walls get cabinets
            countertop_material: granite_dark
            cabinet_style: shaker
            appliance_slots:
              - wall: north
                offset: 1.2
                appliance: sink
              - wall: north
                offset: 2.4
                appliance: dishwasher
              - wall: west
                offset: 0.6
                appliance: oven

      # Explicit asset placements
      assets:
        - slug: modern-refrigerator        # fetched from assets.luckyrobots.com
          position: [3.5, 0, 0.4]
          rotation: [0, 90, 0]
          baked: true                       # pre-baked lighting/textures
          mujoco_enabled: false             # no physics sim needed

        - slug: kitchen-island-marble
          position: [2.0, 0, 1.8]
          rotation: [0, 0, 0]
          baked: false
          mujoco_enabled: true              # interactive in sim

      # Openings โ€” doors/windows cut into the structural walls
      openings:
        - wall: south
          offset: 1.5
          type: doorway                     # open archway, no door asset
          width: 0.9
          height: 2.1

        - wall: east
          offset: 0.5
          type: window
          width: 1.2
          height: 1.0
          sill_height: 0.9

    # โ”€โ”€ Living Room โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
    - id: living_room
      type: living_room
      floor: ground_floor
      bounds:
        origin: [4, 0]
        size: [5, 5]

      assets:
        - slug: modern-sofa-grey
          position: [2.5, 0, 3.5]
          rotation: [0, 180, 0]
          baked: true
          mujoco_enabled: false

        - slug: coffee-table-wood
          position: [2.5, 0, 2.0]
          baked: true
          mujoco_enabled: true

        - slug: tv-65inch-mounted
          position: [2.5, 1.4, 0.05]       # wall-mounted
          rotation: [0, 0, 0]
          baked: true
          mujoco_enabled: false

        - slug: bookshelf-walnut
          position: [0.1, 0, 2.5]
          rotation: [0, 90, 0]
          baked: false
          mujoco_enabled: false

      openings:
        - wall: south
          offset: 1.5
          type: sliding_door
          width: 2.4
          height: 2.1
          generator: sliding_door_generator  # delegates to helper generator
          config:
            panels: 2
            frame_material: aluminum_black

        - wall: west                         # shared wall with kitchen
          offset: 1.5
          type: doorway
          width: 0.9
          height: 2.1

    # โ”€โ”€ Garage โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
    - id: garage
      type: garage
      floor: ground_floor
      bounds:
        origin: [9, 0]
        size: [3, 5]

      generators:
        - type: garage_door_generator
          config:
            wall: south
            width: 2.6
            height: 2.3
            style: sectional              # sectional | roll-up | swing
            material: steel_white
            mujoco_enabled: true          # door is openable in sim

      assets:
        - slug: metal-shelf-unit
          position: [0.2, 0, 4.5]
          baked: false
          mujoco_enabled: false

        - slug: workbench-heavy-duty
          position: [1.5, 0, 4.5]
          baked: false
          mujoco_enabled: true

      openings:
        - wall: west
          offset: 2.0
          type: door
          width: 0.85
          height: 2.1
          config:
            style: panel
            handle_side: left

    # โ”€โ”€ Hallway โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
    - id: hallway
      type: hallway
      floor: ground_floor
      bounds:
        origin: [4, 5]
        size: [5, 3]

      assets:
        - slug: coat-rack-modern
          position: [0.3, 0, 1.5]
          baked: true
          mujoco_enabled: false

      openings:
        - wall: north
          offset: 2.0
          type: door
          width: 1.0
          height: 2.1
          config:
            style: front_door
            material: wood_oak

Architecture

System Overview

flowchart TD
    YAML["house.yaml"]
    HL["HouseLoader"]
    GR["GeneratorRegistry"]
    AR["AssetResolver"]
    SCENE["Scene (Entity Hierarchy)"]

    YAML -->|"parsed by"| HL
    HL -->|"looks up generators"| GR
    HL -->|"resolves asset slugs"| AR
    HL -->|"builds"| SCENE

    subgraph Generators
        SG["StructuralGenerator"]
        CG["CabinetGenerator"]
        SDG["SlidingDoorGenerator"]
        GDG["GarageDoorGenerator"]
    end

    GR --> SG
    GR --> CG
    GR --> SDG
    GR --> GDG

    SG -->|"creates walls, floors, ceilings"| SCENE
    CG -->|"creates cabinets, countertops"| SCENE
    SDG -->|"creates sliding doors"| SCENE
    GDG -->|"creates garage doors"| SCENE
    AR -->|"downloads GLBs, creates entities"| SCENE

    API["assets.luckyrobots.com"]
    CACHE["Local GLB Cache"]
    AR <-->|"HTTP fetch"| API
    AR <-->|"read/write"| CACHE
Loading

Class Diagram

classDiagram
    class IGenerator {
        <<interface>>
        +GetType() string
        +Generate(ctx GeneratorContext, config YAML::Node) void
    }

    class GeneratorContext {
        +Room : RoomDefinition&
        +Floor : FloorDefinition&
        +ParentEntity : Entity
        +Scene : Scene*
    }

    class GeneratorRegistry {
        -m_Generators : map~string, unique_ptr~IGenerator~~
        +Register(type string, gen unique_ptr~IGenerator~) void
        +Get(type string) IGenerator*
    }

    class HouseLoader {
        -m_Registry : GeneratorRegistry
        -m_AssetResolver : AssetResolver
        +LoadFromYAML(path string, scene Scene*) Entity
        -ParseStructure(node YAML::Node) HouseDefinition
        -BuildRoom(def RoomDefinition, parent Entity) void
    }

    class AssetResolver {
        +Resolve(slug string) AssetHandle
        -DownloadGlb(slug string) path
        -CheckCache(slug string) optional~path~
    }

    class StructuralGenerator {
        +GetType() string
        +Generate(ctx, config) void
        -m_WallGen : WallGenerator
        -m_CeilingGen : CeilingGenerator
        -m_DoorGen : DoorGenerator
        -m_WindowGen : WindowGenerator
    }

    class CabinetGenerator {
        +GetType() string
        +Generate(ctx, config) void
        -GenerateLowerCabinets(wall, config) void
        -GenerateUpperCabinets(wall, config) void
        -GenerateCountertop(wall, config) void
        -PlaceApplianceSlots(slots) void
    }

    class SlidingDoorGenerator {
        +GetType() string
        +Generate(ctx, config) void
    }

    class GarageDoorGenerator {
        +GetType() string
        +Generate(ctx, config) void
    }

    IGenerator <|.. StructuralGenerator
    IGenerator <|.. CabinetGenerator
    IGenerator <|.. SlidingDoorGenerator
    IGenerator <|.. GarageDoorGenerator

    GeneratorRegistry o-- IGenerator : stores
    HouseLoader *-- GeneratorRegistry
    HouseLoader *-- AssetResolver
    HouseLoader ..> GeneratorContext : creates

    class WallGenerator {
        +Generate(walls, openings) GeneratedMeshData[]
    }
    class CeilingGenerator {
        +Generate(bounds, thickness) GeneratedMeshData
    }
    class DoorGenerator {
        +Generate(opening OpeningSpec) GeneratedMeshData[]
    }
    class WindowGenerator {
        +Generate(opening OpeningSpec) GeneratedMeshData[]
    }
    class MeshBuilder {
        +AddQuad(verts) void
        +AddBox(origin, size) void
        +AddPlaneXZ(origin, size) void
        +Build() GeneratedMeshData
    }

    StructuralGenerator *-- WallGenerator
    StructuralGenerator *-- CeilingGenerator
    StructuralGenerator *-- DoorGenerator
    StructuralGenerator *-- WindowGenerator
    WallGenerator ..> MeshBuilder : uses
    CeilingGenerator ..> MeshBuilder : uses
    DoorGenerator ..> MeshBuilder : uses
    WindowGenerator ..> MeshBuilder : uses
    CabinetGenerator ..> MeshBuilder : uses
Loading

YAML Data Model

classDiagram
    class HouseDefinition {
        +name : string
        +version : int
        +structure : StructureDefinition
        +rooms : RoomDefinition[]
    }

    class StructureDefinition {
        +floors : FloorDefinition[]
    }

    class FloorDefinition {
        +id : string
        +height : float
        +wall_thickness : float
        +ceiling_thickness : float
        +footprint : vec2[]
    }

    class RoomDefinition {
        +id : string
        +type : string
        +floor : string
        +bounds : RoomBounds
        +generators : GeneratorInvocation[]
        +assets : AssetPlacement[]
        +openings : OpeningSpec[]
    }

    class RoomBounds {
        +origin : vec2
        +size : vec2
    }

    class GeneratorInvocation {
        +type : string
        +config : YAML::Node
    }

    class AssetPlacement {
        +slug : string
        +position : vec3
        +rotation : vec3
        +baked : bool
        +mujoco_enabled : bool
    }

    class OpeningSpec {
        +wall : string
        +offset : float
        +type : string
        +width : float
        +height : float
        +sill_height : float
        +generator : string
        +config : YAML::Node
    }

    HouseDefinition *-- StructureDefinition
    HouseDefinition *-- RoomDefinition
    StructureDefinition *-- FloorDefinition
    RoomDefinition *-- RoomBounds
    RoomDefinition *-- GeneratorInvocation
    RoomDefinition *-- AssetPlacement
    RoomDefinition *-- OpeningSpec
Loading

Entity Hierarchy Output

graph TD
    H["House (Entity)"]
    GF["GroundFloor"]
    W["Walls (procedural mesh)"]
    F["Floor (procedural mesh)"]
    C["Ceiling (procedural mesh)"]

    K["Kitchen"]
    CAB["Cabinets (generated)"]
    UC0["UpperCabinet_N_0"]
    UC1["UpperCabinet_N_1"]
    LC0["LowerCabinet_N_0"]
    CT["Countertop_N"]
    FRIDGE["Refrigerator (asset, baked)"]
    ISLAND["KitchenIsland (asset, mujoco)"]
    WIN["Window_E_0 (procedural)"]

    LR["LivingRoom"]
    SOFA["Sofa (asset, baked)"]
    TABLE["CoffeeTable (asset, mujoco)"]
    TV["TV (asset, baked)"]
    SLIDE["SlidingDoor_S (generated, mujoco)"]

    G["Garage"]
    GDOOR["GarageDoor_S (generated, mujoco)"]
    SHELF["ShelfUnit (asset)"]
    BENCH["Workbench (asset, mujoco)"]

    H --> GF
    GF --> W
    GF --> F
    GF --> C
    GF --> K
    GF --> LR
    GF --> G

    K --> CAB
    CAB --> UC0
    CAB --> UC1
    CAB --> LC0
    CAB --> CT
    K --> FRIDGE
    K --> ISLAND
    K --> WIN

    LR --> SOFA
    LR --> TABLE
    LR --> TV
    LR --> SLIDE

    G --> GDOOR
    G --> SHELF
    G --> BENCH
Loading

Generation Pipeline (Sequence)

sequenceDiagram
    participant User
    participant HL as HouseLoader
    participant GR as GeneratorRegistry
    participant SG as StructuralGenerator
    participant RG as Room Generators
    participant AR as AssetResolver
    participant API as assets.luckyrobots.com
    participant S as Scene

    User->>HL: LoadFromYAML("house.yaml")
    HL->>HL: Parse YAML โ†’ HouseDefinition

    loop Each Floor
        HL->>GR: Get("structural")
        GR-->>HL: StructuralGenerator
        HL->>SG: Generate(floor footprint, all room openings)
        SG->>S: Create Wall/Floor/Ceiling entities

        loop Each Room
            HL->>S: Create room parent entity

            loop Each Generator Invocation
                HL->>GR: Get(generator.type)
                GR-->>HL: e.g. CabinetGenerator
                HL->>RG: Generate(room context, config)
                RG->>S: Create generated entities
            end

            loop Each Asset Placement
                HL->>AR: Resolve(slug)
                alt Cache hit
                    AR-->>HL: cached AssetHandle
                else Cache miss
                    AR->>API: GET /api/download?slug=...
                    API-->>AR: .glb file
                    AR->>AR: Cache locally
                    AR-->>HL: new AssetHandle
                end
                HL->>S: Create entity (position, rotation, baked, mujoco flags)
            end
        end
    end

    HL-->>User: Root house Entity
Loading

Room Types as Templates

Room types serve as semantic tags + default behavior, not rigid classes. A room type can:

  1. Suggest which generators are available (kitchen -> cabinet_generator)
  2. Provide defaults (garage -> overhead lighting, concrete floor material)
  3. Enable type-specific validation (kitchen must have at least one sink slot)
graph LR
    subgraph Room_Types["Room Types (templates)"]
        kitchen
        living_room
        bedroom
        bathroom
        garage
        hallway
        dining_room
        laundry_room
        office
    end

    subgraph Generators["Helper Generators (pluggable)"]
        cab[cabinet_generator]
        slide[sliding_door_generator]
        gdoor[garage_door_generator]
        closet[closet_generator ๐Ÿ”ฎ]
        shelf[shelf_generator ๐Ÿ”ฎ]
        stair[staircase_generator ๐Ÿ”ฎ]
        rail[railing_generator ๐Ÿ”ฎ]
    end

    kitchen -.->|"default"| cab
    living_room -.-> slide
    garage -.->|"default"| gdoor
    bedroom -.-> closet
    bathroom -.-> shelf

    style closet stroke-dasharray: 5 5
    style shelf stroke-dasharray: 5 5
    style stair stroke-dasharray: 5 5
    style rail stroke-dasharray: 5 5
Loading

Dashed generators are future additions. The dashed arrows show default associations -- any room can invoke any generator explicitly in YAML.

Room types are NOT code classes. They're string tags in the YAML. The engine uses them to:

  • Pick defaults when a field is omitted
  • Validate that generator configs make sense
  • Provide editor UX hints (e.g. show cabinet config panel when room type = kitchen)

Generators

Interface

Every generator implements a simple contract:

namespace Hazel::ProceduralBuilding {

    struct GeneratorContext
    {
        const RoomDefinition& Room;     // parsed from YAML
        const FloorDefinition& Floor;   // parent floor
        Entity ParentEntity;            // scene entity to attach children to
        Scene* Scene;
    };

    // All generators implement this
    class IGenerator
    {
    public:
        virtual ~IGenerator() = default;
        virtual std::string GetType() const = 0;
        virtual void Generate(const GeneratorContext& ctx, const YAML::Node& config) = 0;
    };
}

Built-in Generators

Generator What it produces Inputs
structural_generator Walls, floor slab, ceiling slab, wall cutouts for openings Floor footprint, room bounds, openings list
cabinet_generator Upper/lower cabinets, countertops, filler panels Layout shape, wall list, appliance slots, style
garage_door_generator Sectional/roll-up door with tracks Wall, dimensions, style, mujoco flag
sliding_door_generator Multi-panel sliding door with frame and rail Panel count, dimensions, frame material

Generator Registration

// In HouseLoader initialization
GeneratorRegistry::Register("structural", std::make_unique<StructuralGenerator>());
GeneratorRegistry::Register("cabinet_generator", std::make_unique<CabinetGenerator>());
GeneratorRegistry::Register("garage_door_generator", std::make_unique<GarageDoorGenerator>());
GeneratorRegistry::Register("sliding_door_generator", std::make_unique<SlidingDoorGenerator>());

Kitchen Cabinets Generator (Detail)

This is the existing "kitchen configurator" concept, scoped properly as a generator:

generators:
  - type: cabinet_generator
    config:
      layout: L-shape
      walls: [north, west]
      counter_height: 0.9
      upper_cabinet_height: 0.7
      upper_gap: 0.45           # gap between counter and upper cabinets
      cabinet_depth: 0.6
      upper_cabinet_depth: 0.35
      countertop_overhang: 0.02
      countertop_material: granite_dark
      cabinet_style: shaker     # shaker | flat | raised_panel

      appliance_slots:
        - wall: north
          offset: 1.2
          appliance: sink
          width: 0.8
        - wall: north
          offset: 2.4
          appliance: dishwasher
          width: 0.6
        - wall: west
          offset: 0.6
          appliance: oven
          width: 0.6
        - wall: west
          offset: 1.8
          appliance: range_hood
          width: 0.6
          mount: upper           # replaces upper cabinet at this position

The generator:

  1. Walks each specified wall segment
  2. Places lower cabinets at regular intervals, skipping appliance slots
  3. Places countertop as continuous slab with sink cutout
  4. Places upper cabinets, skipping range_hood / window positions
  5. Each piece is either a procedural mesh (MeshBuilder boxes) or an asset from assets.luckyrobots.com

Asset Resolution

Every slug in the YAML refers to an asset on assets.luckyrobots.com. The system needs to know two things about each asset:

Field Meaning
baked: true/false Whether the asset has pre-baked lighting/textures. Baked assets skip dynamic lighting passes.
mujoco_enabled: true/false Whether the asset participates in MuJoCo physics. If true, it gets a MuJoCo body + geom at export time.

Resolution Flow

1. YAML references: slug: "modern-refrigerator"
2. HouseLoader checks local GLB cache (~/.cache/LuckyEngine/AssetLibrary/glb/)
3. Cache miss โ†’ HTTP GET assets.luckyrobots.com/api/search?slug=modern-refrigerator
4. Download .glb to cache
5. Import into engine asset system (AssetManager::ImportStaticMesh)
6. Create entity with StaticMeshComponent
7. Tag with baked/mujoco_enabled metadata

This reuses the existing AssetLibraryBrowser download pipeline (StartGlbDownload, GetGlbCacheDir) but drives it programmatically instead of through the UI.

How the Gist's Building Generator Fits In

The gist proposes these classes:

MeshBuilder, WallGenerator, DoorGenerator, WindowGenerator,
CeilingGenerator, FloorGenerator, BuildingGenerator

In this system, all of those become the structural_generator:

Gist Class Role in This System
BuildingGenerator HouseLoader (top-level orchestrator)
FloorGenerator Part of structural_generator -- generates one floor's shell
WallGenerator Part of structural_generator -- generates wall panels with cutouts
CeilingGenerator Part of structural_generator -- generates floor/ceiling slabs
DoorGenerator Invoked by structural_generator for standard doors; sliding_door_generator and garage_door_generator handle specialized doors
WindowGenerator Part of structural_generator
MeshBuilder Shared utility used by ALL generators

The gist's RoomType enum and post-materialization kitchen dispatch are replaced by the generator system. Instead of materializing the building first and then hacking in kitchens after the fact, rooms declare their generators upfront and everything is built in one pass.

File Organization

Hazel/src/Hazel/ProceduralGeneration/
โ”œโ”€โ”€ HouseDefinition.h           # YAML schema structs (HouseDef, RoomDef, AssetPlacement, etc.)
โ”œโ”€โ”€ HouseLoader.h/.cpp          # Parses YAML, orchestrates generation
โ”œโ”€โ”€ GeneratorRegistry.h/.cpp    # Maps type strings to IGenerator instances
โ”œโ”€โ”€ IGenerator.h                # Generator interface
โ”œโ”€โ”€ AssetResolver.h/.cpp        # Slug โ†’ cached GLB โ†’ imported asset handle
โ”‚
โ”œโ”€โ”€ Structural/                 # The gist's building generation code
โ”‚   โ”œโ”€โ”€ MeshBuilder.h/.cpp
โ”‚   โ”œโ”€โ”€ WallGenerator.h/.cpp
โ”‚   โ”œโ”€โ”€ DoorGenerator.h/.cpp
โ”‚   โ”œโ”€โ”€ WindowGenerator.h/.cpp
โ”‚   โ”œโ”€โ”€ CeilingGenerator.h/.cpp
โ”‚   โ””โ”€โ”€ StructuralGenerator.h/.cpp   # IGenerator impl, orchestrates the above
โ”‚
โ”œโ”€โ”€ Kitchen/
โ”‚   โ””โ”€โ”€ CabinetGenerator.h/.cpp      # IGenerator impl
โ”‚
โ”œโ”€โ”€ Doors/
โ”‚   โ”œโ”€โ”€ SlidingDoorGenerator.h/.cpp   # IGenerator impl
โ”‚   โ””โ”€โ”€ GarageDoorGenerator.h/.cpp    # IGenerator impl
โ”‚
โ””โ”€โ”€ (future)/
    โ”œโ”€โ”€ ClosetGenerator.h/.cpp
    โ”œโ”€โ”€ StaircaseGenerator.h/.cpp
    โ””โ”€โ”€ ...

Implementation Plan

Phase 1 โ€” Foundation

  • Define HouseDefinition.h structs (YAML schema โ†’ C++ structs)
  • Implement HouseLoader YAML parsing (using existing yaml-cpp dependency)
  • Implement IGenerator interface and GeneratorRegistry
  • Implement AssetResolver (extract download logic from AssetLibraryBrowser into reusable service)

Phase 2 โ€” Structural Generator

  • Port MeshBuilder, WallGenerator, CeilingGenerator from the gist
  • Implement StructuralGenerator as an IGenerator
  • Wall cutout algorithm (gap approach from gist)
  • DoorGenerator + WindowGenerator for standard openings

Phase 3 โ€” Cabinet Generator

  • Implement CabinetGenerator as an IGenerator
  • Layout algorithms: single-wall, L-shape, U-shape, galley
  • Appliance slot system
  • Countertop generation with sink cutouts
  • Material assignment

Phase 4 โ€” Specialized Door Generators

  • SlidingDoorGenerator โ€” multi-panel sliding doors with rail system
  • GarageDoorGenerator โ€” sectional door with tracks, MuJoCo-enabled

Phase 5 โ€” Editor Integration

  • "Load House YAML" in editor menu
  • Per-room property panel showing generator config
  • Live preview: edit YAML โ†’ regenerate โ†’ see changes
  • Export to .hscene for runtime use

Key Design Decisions

  1. YAML over C++ config structs โ€” Houses are data, not code. Artists and level designers edit YAML files, not recompile C++.

  2. Generators are plugins, not inheritance โ€” Adding a new generator (e.g. staircase_generator) means writing one class and registering it. No modification to HouseLoader or room types.

  3. Kitchen cabinets are a generator, not a separate system โ€” The "kitchen configurator" is just cabinet_generator invoked inside a room of type kitchen. Same pattern for garage doors, sliding doors, closets, etc.

  4. baked and mujoco_enabled are per-asset flags โ€” A baked sofa and a physics-enabled coffee table can coexist in the same room. These flags travel with the asset placement, not the room.

  5. Structural generation is one generator among many โ€” The gist's BuildingGenerator doesn't own the world. It generates the shell (walls/floors/ceilings), and other generators fill the rooms.

  6. Asset slugs resolve at load time โ€” The YAML doesn't embed geometry. It references assets.luckyrobots.com slugs that get downloaded and cached. This keeps house files tiny and assets centrally managed.

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