Skip to content

Polar Circular Grids

FireRat edited this page Jun 27, 2026 · 3 revisions

Polar (Circular) Grids

  • Introduced in: FireMaze v3.0.0
  • Grid dimension: Radial rings × angular sectors per ring
  • Cell shape: Wedge / trapezoidal sector
  • Custom tile alignment: Procedural Only (native mesh), Trapezoidal Scaling, or Polar Bending (Warp)

Polar Grid


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

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:

Ring Sectors Notes
0 1 center fan
1 2
2 4
3 8
4 16 outermost

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_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)

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)

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)

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 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

Custom Tile Alignment Modes

Controlled by fire_maze.polar_custom_alignmentEnumProperty, 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

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.


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

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

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

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

Back to Home

Clone this wiki locally