Skip to content

Collision Mapping

How the plugin turns 2D collision into grid tiles for placement rules and indicators (as of 2025‑08‑21).

When a player previews or places a building/object, the system needs to know which grid tiles are “touched” by that object’s collision so that:

  • Placement rules (e.g. cannot overlap, must be on foundation) can be evaluated per tile
  • Visual indicators (RuleCheckIndicator) can show which tiles are valid/invalid
  • Downstream systems (e.g. cost, biome, adjacency, blocking) can reason about exact occupied tiles

The Collision Mapping subsystem standardizes this translation with consistent geometric rules and thresholds.

ComponentResponsibility
CollisionMapperOrchestrates extraction of collision geometry from objects and produces tile position → rule / object maps.
CollisionMapper cachingThe mapper implements per-frame geometry caching (polygon, bounds, tile results) to reduce repeated work during previews.
IndicatorCollisionTestSetupLightweight context wrapper storing an object’s transform, origin offset, and cached geometry meta used in mapping.
RuleCheckIndicatorVisual probe shape whose bounds define the sampling region; per-frame it revalidates placement using mapped collision tiles.
GBGeometryMathStatic geometry helpers: shape → polygon conversion, polygon bounds, polygon/tile overlap tests with epsilon filtering.
TileCheckRuleRule data object listing which collision layers (mask) it applies to and additional semantic constraints.
GridTargetingStateAggregates targeting context: maps, positioner, currently inspected TileMap layer, etc.
  • Sources: both CollisionShape2D (on CollisionObject2D) and standalone CollisionPolygon2D contribute. No source is preferred; coverage is the union, tiles are de‑duplicated.
  • Significance: tiles count only when overlap is meaningful.
    • Shapes: optimized overlap with a baseline ~3.5% tile‑area epsilon (shape_epsilon = 0.035).
      • Round primitives (circle / capsule) use a slightly lower per-tile threshold (~2.5%) and extra corner pruning to avoid spurious corner tiles.
    • Polygons: final per‑tile area filter at 12% to suppress slivers (MIN_POLY_TILE_OVERLAP_RATIO = 0.12).
  • Rectangles: coverage is centered on the shape’s world center and forced to an odd span per axis (e.g., 112×80 → 7×5), ensuring the bottom‑middle tile is included.
  • Concave polygons: preserved as authored (no interior fill). Trapezoid expansion is applied only to shallow convex trapezoids when it clearly adds intended coverage.
  • Layers & rules: only collision on layers matching a rule’s mask are considered for that rule.

Currently supported primitive shapes (converted to polygons):

  • RectangleShape2D → 4-point polygon
  • CircleShape2D → N-gon approximation (default 16 segments)
  • (Internal / transitional) Capsule → approximated polygon (now mostly replaced by explicit polygons in assets)

All collision evaluation reduces to polygon vs tile area overlap. Each source shape is converted into a local-space polygon and then transformed into world space using the owning node’s global transform plus any indicator offset. For complex objects with multiple shapes, polygons are processed independently and merged into a tile coverage map.

Advantages:

  • Unified overlap test implementation
  • Enables accurate partial tile filtering via area threshold
  • Supports arbitrary future shapes by adding only one conversion function

The grid uses uniform tiles (commonly 16×16).

  1. Collect sources matching the rule’s layer mask.
  2. Convert each shape to a polygon in world space (polygons are already polygons).
  3. Iterate candidate tiles from the polygon’s bounds and test overlap.
  4. Apply significance filters (shape_epsilon ≈ 0.035 for shapes; 0.025 for round shapes; 12% for polygons) and de‑duplicate tiles.
  • Shapes: baseline ~3.5% minimum overlap per tile (optimized test). Round shapes (circle/capsule) use a lower tuned threshold (~2.5%) and additional corner suppression logic to avoid including corner-only tiles.
  • Polygons: 12% minimum overlap per tile (area‑based).

These thresholds prevent hairline contacts and micro‑slivers from creating indicators or failing rules. Polygons use clipped area so we can discard tiles with negligible true coverage. Round-shape corner tiles are subject to stricter corner thresholds (20% of tile area) during corner pruning to eliminate spurious single-pixel touches on curved geometry.

If multiple shapes overlap the same tile, that tile is inserted only once; the system aggregates contributing objects or rules in a list attached to the tile coordinate key.

Each TileCheckRule declares an apply_to_objects_mask. During mapping, collision objects are filtered by their collision layer bits. A tile is considered relevant for a rule only if at least one contributing object has a matching layer bit set. This ensures rules don’t falsely trigger on unrelated decorative collisions.

Examples:

  • Foundation rule only inspects tiles occupied by layer 1 (foundation-tagged objects)
  • Clearance rule inspects blocking layers (e.g. foliage, structures) but ignores visual-only layers

RuleCheckIndicator performs per-frame validation:

  1. Indicator requests collision mapping within its shape bounds
  2. Placement rules are evaluated against the mapped tile set
  3. Visual state (valid/invalid color) updates immediately when geometry changes (e.g. moving over edge cases)

The indicator shape itself does not contribute to collision mapping—it’s a sampling window that caps unnecessary tile iteration.

Optimizations:

  • Rectangle symmetry (odd span) centered on the shape’s world center ensures predictable coverage. The implementation uses GBCollisionTileFilter.adjust_rect_tile_range to compute symmetric start / end_exclusive ranges so rectangles yield odd-span coverage (e.g., 112×80 → 7×5).
  • Circle/capsule corner pruning avoids distant corner “touches” and applies stricter area thresholds to corner tiles (typically 20% of tile area) so tiny corner overlaps do not create indicator tiles.
  • Per-row horizontal mirroring is enforced for round shapes to avoid left/right drift: if a tile exists on one side of the center row the mirror tile is added so circles remain symmetric.
  • Deduplication guarantees a tile appears only once even if covered by multiple sources.

Performance Notes:

  • Typical building polygons span < 150 tiles; mapping occurs per-frame only while targeting (preview state)
  • Use minimal polygon vertex counts consistent with silhouette fidelity (e.g. 20-point egg instead of high-res capsule sampling)

Support / Purchase Hub: Linktree – All Grid Builder Links

  • Bounds cover exactly one tile
  • Overlap area = 225 px² (> 5% threshold)
  • Mapped tiles: 1

Thin edge polygon (0.1×0.1) touching a tile corner

Section titled “Thin edge polygon (0.1×0.1) touching a tile corner”
  • Area = 0.01 px² (<< 5% of 256)
  • Filtered out (0 tiles)

Complex building with multiple CollisionShapes

Section titled “Complex building with multiple CollisionShapes”
  • Each shape converted, tiles aggregated
  • If two shapes share a tile, tile stored once with both object refs

Automated coverage includes layer filtering, rectangle + polygon overlap (multi‑tile), significance filters, trapezoid heuristics, and concave handling.

Targeted regressions:

  • Polygon sliver suppression (<12% overlap → no tile).
  • Smithy used‑space subset: 112×80 rectangle covers a full 7×5 area; bottom‑middle present. Extras from other sources are allowed.
  • Prefer CollisionPolygon2D for irregular silhouettes (e.g. large egg) over shape approximations (capsule) for tighter tile usage
  • Keep polygons reasonably simple (<= 32 vertices) unless higher fidelity is required
  • Ensure collision layer bits reflect semantic purpose (foundation, blocking, decorative) to enable accurate rule filtering
  • Avoid tiny disconnected polygon slivers; they may be ignored by epsilon filtering

To add a new shape type:

  1. Implement shape → polygon conversion in GBGeometryMath
  2. Add test ensuring correct vertex count and bounds
  3. (If needed) Expose editor tooling to simplify generating the polygon

To adjust sensitivity:

  • Shapes use the local shape_epsilon (currently ~0.035) for general overlap filtering; round shapes have a tuned lower threshold (≈0.025). Polygons use MIN_POLY_TILE_OVERLAP_RATIO (0.12).
  • For reproducible changes, update these constants in collision_mapper.gd and add tests covering the affected edge cases.
AreaCurrent StatePotential Enhancement
Concave polygonsSupported (processed as provided) but no auto-decompositionDecompose to convex parts for potential faster intersection
Rotated tileset gridsAssumes axis-aligned square tilesAdd support for isometric / staggered grids
Large dynamic objectsPer-frame full remapAdd incremental dirty-region updates
Epsilon globalitySingle threshold shared across rulesPer-rule or per-layer override
  1. Shapes: offsets pivot around the owner’s tile (stable if only the positioner moves).
  2. Polygons: offsets pivot around the positioner’s tile.
  3. Offsets = tile_pos - center_tile.

Collision mapping converts all relevant 2D collision shapes into polygons, determines significantly overlapped tiles using a fractional area threshold, filters them by rule-relevant collision layers, and feeds this tile set into placement validation and visual feedback. The design balances accuracy (tight polygon outlines) with performance (bounds pruning + epsilon) to deliver responsive, reliable placement previews.

DateChangeRationaleImpact
2025-08-18Reverted experimental bounds-center polygon pivot to positioner-centered modelPreserve established test & gameplay frame of referenceKeeps existing offset expectations; removes need for test rewrites
2025-08-18Introduced generic rectangle/circle tile normalization utilities (GBCollisionTileFilter)Remove magic size branches (32x48, radius 24)More maintainable, symmetrical coverage without hardcoded cases
2025-08-18Restored trapezoid expected coverage (13 tiles) after mapper correctionsValidate intended geometryPrevents silent coverage shrink regression
2025-08-21Added polygon min‑area filter (12%) and rectangle center‑anchored odd‑span normalizationEliminate slivers; ensure full used‑space (e.g., Smithy bottom‑middle)Stable, predictable coverage; parity with tests
2025-08-21Changed CollisionObject2D shape pivoting: shape offsets pivot around the owner’s tile (owner-centered) while polygon sources remain positioner-centered; allowed Area2D rectangle sources to contributeDecouple shape offsets from the targeting positioner and avoid surprising offset changes when moving only the positionerMore intuitive behavior; requires no test rebase for most cases
2025-08-21Per-shape tuning for round primitives: lower area threshold and corner pruning, plus symmetric mirroring enforcementRemove spurious corner tiles for circles/capsules and ensure left/right symmetryImproved UX for round shapes and consistent indicator geometry

Last updated: 2025-08-21