FireMaze — Polar (Circular) Grids

← Back to FireMaze

1. Cell Structure

A polar cell lives at (ring, sector) and is composed of 6 mesh faces and 4 wall slots:

SlotTypeDescription
floor_meshFaceBottom face (wedge quad)
roof_meshFaceTop face (wedge quad)
cw_meshFaceClockwise radial wall
ccw_meshFaceCounter-clockwise radial wall
out_meshFaceOuter arc wall
in_meshFaceInner arc wall
cw_wallWallBoundary wall on CW side
ccw_wallWallBoundary wall on CCW side
out_wallWallBoundary wall on outer arc
in_wallWallBoundary wall on inner arc

The inner-most ring (ring 0) has no inner wall; instead the center fan fills the gap.
The outer-most ring (ring polar_rings - 1) always has an out_wall rendered as WALL_ALWAYS.

Additional slots for 2-faced cell construction:

SlotPurpose
out2_mesh / in2_meshMirror face for double-sided walls

2. Ring Sectors

Sector counts across rings are computed by get_polar_sector_counts() in utils.py.

Rules:

  • Outermost ring has at least 4 sectors
  • Adjacent rings differ by at most in sector count (factor ≤ 2)
  • Counts are powers-of-two multiples of the outermost count, descending inward
  • Minimum sectors in any ring: 2
  • The innermost ring may be further sub-sampled to keep sector count ≥ 1

Example — 5 rings, 16 outermost sectors:

RingSectorsNotes
01center fan
12
24
38
416outermost

3. Maze Generation

generate_polar_maze() in polar_maze.py handles all algorithm variants.

AlgorithmBehavior
DFS / Recursive BacktrackerNative polar implementation. Walks ring×sector cells, picks unvisited neighbor using ring-adjacent neighbour mapping
All other algorithms (Kruskal, Prim, Aldous-Broder, Hunt-and-Kill, Wilson, Eller, Growing Tree, Binary Tree, Sidewinder)Fall back to a Kruskal-style spanning tree: randomly shuffle all cell-wall pairings, union cells until the tree is complete. Wall directions are weighted by radial_bias

radial_bias

  • Property: fire_maze.radial_biasFloatProperty, range [0.0, 1.0], default 0.5
  • At 0.0 — strong tangential preference (walls broken on CW/CCW paths → wrap-around corridors)
  • At 1.0 — strong radial preference (walls broken on IN/OUT paths → spoke-like corridors)
  • At 0.5 — balanced (equal probability for any wall direction)

4. Entrances & Exits

  • Entrance and exit are placed on the outermost ring (ring polar_rings - 1)
  • Candidate cells are gathered from outer_candidates (all outermost sectors)
  • If only 1 candidate and fewer than 3 sectors, falls back to cell (0, 0)
  • Entrance/exit cell is removed from the other pool to avoid overlap
  • Entrance wall = out_wall, Exit wall = out_wall (both break the outer boundary)

5. Center Fan

The center of the polar grid is filled by _add_polar_center_fan() in polar_builder.py.

  • Segments: 24 triangles
  • Construction:
    • 9 vertices in a ring around the origin (disc)
    • If thickness > 0, duplicated at z = thickness → 18 vertices total
  • Each vertex stores a UV coordinate (cos(θ)/2 + 0.5, sin(θ)/2 + 0.5)
  • Fan edges connect to (0, 0, z) for the bottom/top face
  • Wall faces are generated around the perimeter using triangle pairs
  • Collider mesh: vertex-only triangle fan (no thickness)

6. Walls

Polar walls support two categories:

WallDirectionWall Tag
Radial (IN / OUT)Along the spoke (inner / outer arc)in_wall, out_wall
Tangential (CW / CCW)Along the angular direction (clockwise / counter-clockwise side)cw_wall, ccw_wall

Thin Wall Offset

Polar walls accept thin_wall_offset (in wall/builder calls) to shift the wall plane inward/outward relative to the cell boundary, preventing z-fighting with adjacent walls.

Radial Extrusion

When generating boundary walls:

  • dr_inner / dr_outer — extra radial length added to the wall quad beyond the cell edge
  • dphi_start / dphi_end — angular overshoot for IN/OUT walls using bend alignment

Wall Meshes (Custom Alignment)

  • CW / CCW walls — placed via _add_wall_polar_bend() with a rotation and offset matrix. No angular bending; instead uses a scaled/translated instance at the sector boundary
  • IN / OUT walls — built with _add_mesh_polar_bend_with_matrix(), applying the full angular warp transformation to a flat wall quad

7. Custom Tile Alignment Modes

Controlled by fire_maze.polar_custom_alignmentEnumProperty, default Procedural Only.

ValueEnumDescription
proceduralProcedural OnlyNo custom tile geometry is used. All cells use native wedge/quad meshes generated by _add_polar_wedge() and _add_polar_fences()
trapezoidalTrapezoidal ScalingCustom tile mesh is stretched via bilinear interpolation to fit the wedge shape. Each vertex (x, y) is mapped to a polar coordinate (r, θ) by blending the four corner values. Materials are preserved
bendingPolar Bending (Warp)Custom tile mesh is first centered at origin, then warped by (x_rel, y_rel) → (r_local * cos(θ_local), r_local * sin(θ_local)). The mesh is subdivided (up to 4 cuts) for smoother curvature. reverse_faces=True flips normals to face outward

Warp Formula (bending mode)

α_r = 2π / Nr
r_mid = (r + 0.5) × ts
θ_mid = (θ + 0.5) × α_r
r_local = r_mid + y_rel
u = (x_rel + ts/2) / ts
θ_local = (1 - u) × (θ_mid - α_r/2) + u × (θ_mid + α_r/2)

x' = r_local × cos(θ_local)
y' = r_local × sin(θ_local)
z' = z + z_off

8. Stairs

Stairs in polar mode are restricted to 1×1 footprint only.

FeatureDetail
Min–Max Footprint1×1 (no 1×2 or 2×2 in polar)
Orientation MappingCCW → sector - 1, OUT → ring + 1, CW → sector + 1, IN → ring - 1
Inner RingOrientation IN at ring 0 → maps to center (allowed)
Two-tile footprintsTwo yoked single-cell stairs placed at adjacent rings

The stair builder detects polar grid type and forces 1×1 placement even if 1×2 or 2×2 is set in the UI.

9. Interactive Editor

_get_polar_coords() in operators.py converts a mouse hit position (hit_x, hit_y) into a (ring, sector) index:

  1. Compute radial distance r = hypot(hit_x, hit_y)
  2. Compute angle theta = atan2(hit_y, hit_x) normalized to [0, 2π)
  3. Map r and theta through ring_sectors to find the matching cell

Wall toggling maps the 4 polar wall slots:

  • Radial (CW / CCW)cw_wall / ccw_wall
  • Tangential (IN / OUT)in_wall / out_wall
  • The editor identifies which wall is being pointed at by hit position and sector boundary angles

10. Pathfinding

_get_polar_neighbors() in pathfinder.py returns adjacent (r, θ) tuples:

  • Same ring: θ ± 1 (wraps around the ring)
  • Adjacent ring: maps sector via ring_sectors[r ± 1] using sector-majority mapping — if the adjacent ring has fewer sectors, multiple old-sectors map to the same new-sector; if it has more, one old-sector maps to ~2 new-sectors
  • Outer ring direction: (r+1, sector) mapped to next ring's sector count
  • Inner ring direction: (r-1, sector) mapped to previous ring's sector count

11. Properties Reference

PropertyTypeDefaultRangeDescription
fire_maze.polar_ringsInt52–100Number of concentric rings
fire_maze.polar_custom_alignmentEnumproceduralCustom tile alignment mode
fire_maze.radial_biasFloat0.50.0–1.00 = tangential preference, 1 = radial preference

12. Limitations

  • Custom tiles must be axis-aligned and centered at the origin for bending alignment to work correctly
  • Trapezoidal Scaling and Polar Bending modes are computationally more expensive than Procedural Only; large meshes may slow rebuild
  • Stairs are limited to 1×1 footprint; 1×2 and 2×2 footprints are not supported in polar grids
  • Polar grid does not support all shape boundary modes (rect, diamond, triangle, hexagon are Cartesian-only)
  • The center fan is always a perfect disc — no custom tile is applied to the center