Skip to content

Collision Mapping

How the plugin turns 2D collision into grid tiles for placement rules and indicators. CollisionMapper is the central orchestrator that extracts collision geometry from objects and produces tile position → rule / object maps. Introduced in v5.0.0 as a refactoring and enhancement of previous collision handling logic.

Grid Building 5.0.0 features thoroughly tested and validated collision detection with:

  • Multi-shape collision support for complex building footprints
  • Precise edge case handling for overlapping geometries
  • Optimized physics queries with intelligent caching
  • Validated collision mathematics backed by comprehensive automated tests

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:

The Collision Mapping subsystem standardizes this translation with consistent geometric rules and thresholds. In practice, the subsystem is most often invoked by indicator code (for example IndicatorManager / RuleCheckIndicator) during preview and placement: the indicator layer asks the mapper to map the preview instance’s collision against the active TileMapLayer and then creates per-tile indicators for every tile that passes the mapping and significance filters.

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.
CollisionTestSetup2DLightweight 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 (shape→polygon conversion continues to use GBGeometryMath).
CollisionGeometryUtilsStateless helpers that drive the unified polygon→tile path (delegates to CollisionGeometryCalculator). Contains conservative test defaults (edge epsilon, min area fraction) used across tests and the mapper.
CollisionGeometryCalculatorPure geometry calculator: precise polygon/tile overlap, clipping and area calculations used by CollisionGeometryUtils and tests.
CollisionProcessorRuntime processor that unifies polygon and shape paths, returns relative offsets for indicators and manages cache invalidation via GeometryCacheManager.
GeometryCacheManagerInternal cache for polygon bounds and intermediate tile polygons to avoid duplicate expensive geometry calculations during previews.
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.
  • The mapping pipeline now uses a unified geometry path: CollisionGeometryUtils (test-friendly helpers) delegates to the precise CollisionGeometryCalculator for polygon→tile overlap calculations. Defaults used in tests are: edge epsilon = 0.01 and min area fraction = 0.05 (5%).
  • Shapes: shape→polygon conversion is still performed via GBGeometryMath.convert_shape_to_polygon, then fed through the unified polygon path. Shape processing uses a baseline shape_epsilon ≈ 0.035 in the shape path.
    • Round primitives (circle / capsule) use a slightly lower per-tile threshold (~0.025) and additional corner pruning / mirroring logic to avoid spurious corner tiles.
  • Polygons: the mapper-level polygon sliver suppression constant remains MIN_POLY_TILE_OVERLAP_RATIO = 0.12 (12%) and is defined on the runtime mapper where rule-level aggregation occurs.

Currently supported primitive shapes (converted to polygons):

  • RectangleShape2D → 4-point polygon
  • CircleShape2D → N-gon approximation (default 16 segments)
  • CapsuleShape2D / Capsule approximation → approximated polygon (often replaced by explicit CollisionPolygon2D 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 (single polygon→tile path simplifies verification and testing)
  • Enables accurate partial-tile filtering via area-based thresholds (prevents hairline contacts from triggering tiles)
  • Supports arbitrary future shapes by adding a single shape→polygon converter; all downstream code re-uses the unified overlap routine

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. In the current implementation this iteration and overlap testing is performed by CollisionGeometryCalculator.calculate_tile_overlap() (via CollisionGeometryUtils.compute_polygon_tile_offsets()), ensuring polygon and shape code paths produce identical tile sets.
  4. Apply significance filters (shape_epsilon ≈ 0.035 for shapes; 0.025 for round shapes; 12% for polygons) and de‑duplicate tiles.

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 (commonly ~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:

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.

After collision mapping determines which tiles are occupied by a test object, the system needs to position visual indicators to show validation results. The positioning follows a specific transformation from absolute collision coordinates to relative indicator placement.

  1. Collision Detection: The collision mapping system identifies absolute tile positions where the test object’s collision shapes (CollisionShape2D, CollisionPolygon2D) would intersect with the tilemap.

  2. Position Normalization: Each collision tile position is normalized into a consistent relative offset pattern using modulo arithmetic:

    var offset_x = (position.x % 7) - 3 # Range: -3 to +3 tiles
    var offset_y = (position.y % 7) - 3 # Range: -3 to +3 tiles
  3. Relative Positioning: Indicators are positioned relative to the test object’s current location, not at the absolute collision coordinates. This ensures indicators maintain consistent spatial relationships when the test object moves.

  4. World Coordinate Translation: The normalized offsets are applied to the test object’s tile position and converted back to world coordinates for final indicator placement.

  • Movement Following: When the test object moves, indicators automatically follow while preserving their relative layout pattern.
  • Consistent Patterns: The modulo-based normalization produces deterministic indicator layouts for the same collision patterns.
  • Tile-Based Offsets: Indicators are placed using whole-tile offsets relative to the test object’s center (no sub-tile offsets).
  • Shape-Aware Placement: Different collision silhouettes (rectangles, circles, polygons) produce different mapped tile sets and therefore different indicator arrangements.

The positioning system begins by mapping collision geometry into candidate tiles and then deciding which tiles should receive indicators:

  • CollisionShape2D Sources: Rectangle, circle, and capsule shapes are converted to polygons (via GBGeometryMath.convert_shape_to_polygon) and tested for tile overlap.
  • CollisionPolygon2D Sources: Polygons are tested directly using polygon→tile overlap and clipping.
  • Layer Mask Filtering: Only collision objects matching the rule’s apply_to_objects_mask contribute to the mapped tile set used for indicator generation.
  • Significance Thresholds: Minimum overlap requirements and pruning heuristics are applied before indicators are created to avoid noise from negligible contacts. The positioning system begins with comprehensive collision testing against the test object’s shapes:

This approach transforms raw collision detection results into meaningful spatial feedback, showing players exactly where placement constraints apply relative to their intended build location.

Optimizing collision mapping is about choosing sensible defaults that preserve placement accuracy while avoiding per-frame expensive computation during previews. The subsystem uses a few small techniques to balance precision and runtime cost:

Optimizations:

  • Bounds pruning: most geometry is quickly rejected by testing tile-bounds overlap before performing precise polygon clipping.
  • Per-frame caching: CollisionMapper and CollisionProcessor rely on GeometryCacheManager to reuse intermediate polygons and tile masks during continuous previews.
  • Unified polygon path: converting shapes to polygons and running a single polygon→tile routine reduces duplicated code and allows a single, optimized hot path (CollisionGeometryCalculator).
  • Sliver suppression & significance filters: area-based thresholds eliminate tiny overlaps that would otherwise create noisy indicators or false rule failures.

The repo exposes small helpers and filters to make indicator placement deterministic and easier to reason about:

  • GBCollisionTileFilter — utilities for normalizing tile spans, enforcing odd-span centering, and mirroring/pruning corner tiles for round primitives. See the API page: GBCollisionTileFilter.
  • Tile offset normalization — the indicator positioning flow uses modulo-based normalization (documented above) to convert absolute collision tile coordinates into stable relative offsets.

Performance Notes:

  • Caching scope: caches are intentionally per-preview (per-indicator) and invalidated when transforms or the target tilemap change; long-term caches of intermediate tile polygons are stored in GeometryCacheManager to avoid expensive rebuilds across many previews.
  • Hot-path cost: the most expensive work is polygon clipping and exact area calculation in CollisionGeometryCalculator. Keep complex polygon vertex counts reasonable in authored assets when possible.
  • Tuning knobs: use the mapper and geometry utils constants (edge epsilon, min area fraction, MIN_POLY_TILE_OVERLAP_RATIO) to relax or tighten sensitivity — configuration is documented on the respective API pages (see links).

Support / Purchase Hub: Linktree – All Grid Builder Links

  • Bounds cover approximately one tile
  • Overlap area = 225 px² (> 5% threshold) so the tile is mapped
  • 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”
  • Very small area compared to tile size (e.g. 0.01 px²) — below the min-area threshold
  • Filtered out and produces 0 mapped tiles

Complex building with multiple CollisionShapes

Section titled “Complex building with multiple CollisionShapes”
  • Each shape is converted independently to polygons and mapped
  • Tiles are aggregated and de-duplicated; if multiple shapes contribute the same tile, the tile stores references to all contributors (useful for rule attribution)

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

Targeted regressions:

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:

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-09-16Added comprehensive “Indicator Positioning” section documenting collision-to-indicator transformationDocument how collision detection results are transformed into relative indicator positioningImproves developer understanding of positioning system architecture
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-09-16