1. 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.
2. 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_mazecustom 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.
3. Placement Helper
All props are placed via place_prop(src_obj, pos, rot_z, cell_id):
- Copy the source object with
src_obj.copy(). - Link the copy to the props collection.
- Set location to the target world position.
- Set rotation — preserves the source's X and Y euler rotation; overrides Z rotation to the calculated facing angle.
- Set scale to match the source.
- Tag with
fire_mazeandfire_maze_cellcustom properties.
4. 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.
5. 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).
6. 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.
7. 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.seedvalue 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.
8. 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.