Skip to content

Prop & Decor Spawner

FireRat edited this page Jun 27, 2026 · 2 revisions

Prop & Decor Spawner

Automatically place decorative objects on wall faces, in dead-end cells, and at entrance/exit openings. Marked as EXPERIMENTAL — props are spawned at generation time and cleaned up on maze clear or regeneration.


Overview

The prop spawner operates as the final step of maze generation (inside _spawn_decorations). It:

  • Scans every cell in the maze for valid placement locations.
  • Uses per-cell seeded random number generation for deterministic placement.
  • Places copies of the assigned source objects, not linked duplicates.
  • Groups all spawned props under a scoped sub-collection.
  • Supports incremental rebuild during interactive editing — only props on dirty cells are re-evaluated.

Prop Collection

All spawned props are organized into a sub-collection:

FireMaze_Props_{parent_collection_name}

For example, a maze in the FireMaze collection gets props in FireMaze_Props_FireMaze.

Lifecycle

  • The props collection is created if it doesn't exist and linked as a child of the main maze collection.
  • Before repopulating, all existing props in the collection with the fire_maze custom property are removed.
  • Each prop object is tagged with:
    • fire_maze (True) — Marks it for scene cleanup (Clear Maze).
    • fire_maze_cell (cell_id) — Links the prop to its parent cell for incremental updates.

Incremental Editing

When dirty_cells are provided (interactive editing), only props whose fire_maze_cell is in the dirty set are removed and re-spawned. Props on clean cells are left untouched.


Placement Helper

All props are placed via place_prop(src_obj, pos, rot_z, cell_id):

  1. Copy the source object with src_obj.copy().
  2. Link the copy to the props collection.
  3. Set location to the target world position.
  4. Set rotation — preserves the source's X and Y euler rotation; overrides Z rotation to the calculated facing angle.
  5. Set scale to match the source.
  6. Tag with fire_maze and fire_maze_cell custom properties.

Torches

Source: prop_torch_mesh (Object pointer). Density: prop_torch_density (0.0–1.0, default 0.2)

Torches are placed on wall faces that border open space, at 60% of wall height above the floor (z * level_height + 0.6 * wh). A small offset (0.02 × tile_size) moves the torch slightly away from the wall surface.

Rectangular — Thin Wall Mode

Each of the four wall flags (N, S, E, W) is checked independently. A torch is placed on every wall segment that is present, subject to density:

Wall flag Position Rotation (Z)
North (c[0]) (x*ts + ts/2, (y+1)*ts - offset, ...) π (facing south)
South (c[1]) (x*ts + ts/2, y*ts + offset, ...) 0 (facing north)
East (c[2]) ((x+1)*ts - offset, y*ts + ts/2, ...) π/2 (facing west)
West (c[3]) (x*ts + offset, y*ts + ts/2, ...) -π/2 (facing east)

Rectangular — Cube Mode

Wall cubes are checked. A torch is placed on each side of a wall cube that faces an open (non-wall) neighbor cell:

Direction Condition Position Rotation
North cells[y+1][x][0] == False 0.02 × ts outside north face 0 (north)
South cells[y-1][x][0] == False 0.02 × ts outside south face π (south)
East cells[y][x+1][0] == False 0.02 × ts outside east face π/2 (east)
West cells[y][x-1][0] == False 0.02 × ts outside west face -π/2 (west)

Polar — Cube Mode

Checks boundary walls between wedge-shaped cells:

Boundary Condition Placement
CW radial Wall cell has open CW neighbor Torch on CW boundary facing CW
CW radial (open) Open cell has wall CW neighbor Torch on CW boundary facing CCW/inward
IN angular Wall cell has open inward neighbor Torch on IN boundary facing IN
IN angular (open) Open cell has wall inward neighbor Torch on IN boundary facing OUT

Polar — Thin Wall Mode

Checks the CW and IN wall flags on each cell:

Wall flag Placement
CW (cells[r][theta][0]) Torch on clockwise radial boundary
IN (cells[r][theta][1]) Torch on inward angular boundary

Outermost Ring (Polar)

On the outermost ring (r == rings - 1), torches are also placed on the outer boundary of open cells, skipping entrance and exit cells.


Chests

Source: prop_chest_mesh (Object pointer). Density: prop_chest_density (0.0–1.0, default 0.5)

Chests are placed in dead-end cells — cells with exactly one open neighbor (one accessible direction). Stair cells, entrances, and exits are excluded.

Dead-End Detection

Rectangular — Thin Wall Mode

A cell is a dead-end when exactly 3 of its 4 wall flags are set (i.e., sum(c[:4]) == 3). The single open direction becomes the chest's orientation.

Rectangular — Cube Mode

An open cell (not a wall) is a dead-end when exactly 1 of its 4 orthogonal neighbors is also open.

Polar

Accessible neighbors are counted by checking:

  • CW/CCW radial boundaries — open if the adjacent cell in the same ring is not a wall (or is a stair cell).
  • IN/OUT angular boundaries — open if the adjacent cell in the neighboring ring is not a wall (or is a stair cell). Overlapping sector cells in the adjacent ring are resolved according to the ring sector ratio.

A cell with exactly 1 accessible neighbor (and r > 0 to exclude the center) is a dead-end.

Chest Orientation

The chest faces toward its single open direction, placed 0.15 × tile_size from the cell center toward the opening:

Open direction Position offset Rotation (Z)
North +Y from center 0
South -Y from center π
East +X from center π/2
West -X from center -π/2

For polar, directional mapping:

  • IN → positioned toward inner ring, faces IN (rotation + π)
  • OUT → positioned toward outer ring, faces OUT
  • CW → positioned toward CW neighbor, rotation theta_mid - π/2
  • CCW → positioned toward CCW neighbor, rotation theta_mid + π/2

Exclusions

Chests are not placed on:

  • Stair cells (any floor).
  • Entrance cell (floor 0).
  • Exit cells (top floor).

Doors

Source: prop_door_mesh (Object pointer). No density control — doors always spawn at valid locations.

Doors are placed at entrance and exit openings on the maze boundary.

Entrance Door (Floor 0)

One door at the entrance cell, positioned at the center of the boundary opening:

Entrance side Position Rotation (Z)
North (ex*ts + ts/2, (ey+1)*ts, z*level_height) 0
South (ex*ts + ts/2, ey*ts, z*level_height) 0
East ((ex+1)*ts, ey*ts + ts/2, z*level_height) π/2
West (ex*ts, ey*ts + ts/2, z*level_height) -π/2

Exit Doors (Top Floor)

One door at each exit cell, same positioning logic as the entrance door.

For polar mazes, doors are placed at the outer boundary of the entrance/exit cell, oriented toward the cell's mid-angle.


Seeded Randomness

Each cell receives a deterministic random number generator to control torch and chest placement:

global_seed = props.seed if props.seed is not None else 0
cell_seed = global_seed + z * 1000000 + y * 1000 + x
cell_rng = random.Random(cell_seed)
  • The same props.seed value always produces identical prop placement.
  • A seed of 0 uses a different random layout each generation.
  • Per-cell seeding ensures stability during incremental edits — cells outside the dirty set retain their existing props.

UI Reference

Panel: VIEW3D_PT_fire_maze_props — Prop & Decor Spawner EXPERIMENTAL

Control Property Type Default
Torch Object prop_torch_mesh Object pointer None
Torch Density prop_torch_density Float 0.0–1.0 0.2
Chest Object prop_chest_mesh Object pointer None
Chest Density prop_chest_density Float 0.0–1.0 0.5
Door Object prop_door_mesh Object pointer None

Density sliders only appear when the corresponding object pointer is assigned.