1. Cell Structure
A polar cell lives at (ring, sector) and is composed of 6 mesh faces and 4 wall slots:
| Slot | Type | Description |
|---|---|---|
floor_mesh | Face | Bottom face (wedge quad) |
roof_mesh | Face | Top face (wedge quad) |
cw_mesh | Face | Clockwise radial wall |
ccw_mesh | Face | Counter-clockwise radial wall |
out_mesh | Face | Outer arc wall |
in_mesh | Face | Inner arc wall |
cw_wall | Wall | Boundary wall on CW side |
ccw_wall | Wall | Boundary wall on CCW side |
out_wall | Wall | Boundary wall on outer arc |
in_wall | Wall | Boundary 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:
| Slot | Purpose |
|---|---|
out2_mesh / in2_mesh | Mirror 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 2× 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:
| Ring | Sectors | Notes |
|---|---|---|
| 0 | 1 | center fan |
| 1 | 2 | |
| 2 | 4 | |
| 3 | 8 | |
| 4 | 16 | outermost |
3. Maze Generation
generate_polar_maze() in polar_maze.py handles all algorithm variants.
| Algorithm | Behavior |
|---|---|
| DFS / Recursive Backtracker | Native 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_bias—FloatProperty, range[0.0, 1.0], default0.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 atz = 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:
| Wall | Direction | Wall 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 edgedphi_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_alignment — EnumProperty, default Procedural Only.
| Value | Enum | Description |
|---|---|---|
procedural | Procedural Only | No custom tile geometry is used. All cells use native wedge/quad meshes generated by _add_polar_wedge() and _add_polar_fences() |
trapezoidal | Trapezoidal Scaling | Custom 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 |
bending | Polar 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.
| Feature | Detail |
|---|---|
| Min–Max Footprint | 1×1 (no 1×2 or 2×2 in polar) |
| Orientation Mapping | CCW → sector - 1, OUT → ring + 1, CW → sector + 1, IN → ring - 1 |
| Inner Ring | Orientation IN at ring 0 → maps to center (allowed) |
| Two-tile footprints | Two 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:
- Compute radial distance
r = hypot(hit_x, hit_y) - Compute angle
theta = atan2(hit_y, hit_x)normalized to[0, 2π) - Map
randthetathroughring_sectorsto 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
| Property | Type | Default | Range | Description |
|---|---|---|---|---|
fire_maze.polar_rings | Int | 5 | 2–100 | Number of concentric rings |
fire_maze.polar_custom_alignment | Enum | procedural | — | Custom tile alignment mode |
fire_maze.radial_bias | Float | 0.5 | 0.0–1.0 | 0 = 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