Collision Mapping
How the plugin turns 2D collision into grid tiles for placement rules and indicators (as of 2025‑08‑21).
Purpose
Section titled “Purpose”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.
Key Components
Section titled “Key Components”Component | Responsibility |
---|---|
CollisionMapper | Orchestrates extraction of collision geometry from objects and produces tile position → rule / object maps. |
CollisionMapper caching | The mapper implements per-frame geometry caching (polygon, bounds, tile results) to reduce repeated work during previews. |
IndicatorCollisionTestSetup | Lightweight context wrapper storing an object’s transform, origin offset, and cached geometry meta used in mapping. |
RuleCheckIndicator | Visual probe shape whose bounds define the sampling region; per-frame it revalidates placement using mapped collision tiles. |
GBGeometryMath | Static geometry helpers: shape → polygon conversion, polygon bounds, polygon/tile overlap tests with epsilon filtering. |
TileCheckRule | Rule data object listing which collision layers (mask) it applies to and additional semantic constraints. |
GridTargetingState | Aggregates targeting context: maps, positioner, currently inspected TileMap layer, etc. |
At a glance
Section titled “At a glance”- Sources: both
CollisionShape2D
(onCollisionObject2D
) and standaloneCollisionPolygon2D
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).
- Shapes: optimized overlap with a baseline ~3.5% tile‑area epsilon (shape_epsilon = 0.035).
- 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 polygonCircleShape2D
→ N-gon approximation (default 16 segments)- (Internal / transitional) Capsule → approximated polygon (now mostly replaced by explicit polygons in assets)
Shape → Polygon Conversion
Section titled “Shape → Polygon Conversion”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
How it works
Section titled “How it works”The grid uses uniform tiles (commonly 16×16).
- Collect sources matching the rule’s layer mask.
- Convert each shape to a polygon in world space (polygons are already polygons).
- Iterate candidate tiles from the polygon’s bounds and test overlap.
- Apply significance filters (shape_epsilon ≈ 0.035 for shapes; 0.025 for round shapes; 12% for polygons) and de‑duplicate tiles.
Significance filters
Section titled “Significance filters”- 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.
Multiple Shapes & Aggregation
Section titled “Multiple Shapes & Aggregation”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.
Collision Layers & Rule Filtering
Section titled “Collision Layers & Rule Filtering”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
Indicator Integration
Section titled “Indicator Integration”RuleCheckIndicator performs per-frame validation:
- Indicator requests collision mapping within its shape bounds
- Placement rules are evaluated against the mapped tile set
- 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.
Precision vs Performance
Section titled “Precision vs Performance”Optimizations:
Normalization helpers
Section titled “Normalization helpers”- Rectangle symmetry (odd span) centered on the shape’s world center ensures predictable coverage. The implementation uses
GBCollisionTileFilter.adjust_rect_tile_range
to compute symmetricstart
/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)
Cross-links
Section titled “Cross-links”- Manipulation System: Complete manipulation system documentation
- Placement Rules Pipeline: Placement rules system overview
- Configuration & Settings: Epsilon settings and configuration options
Support / Purchase Hub: Linktree – All Grid Builder Links
Examples
Section titled “Examples”Small 15×15 rectangle on 16×16 grid
Section titled “Small 15×15 rectangle on 16×16 grid”- 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.
Authoring tips
Section titled “Authoring tips”- 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
Extensibility
Section titled “Extensibility”To add a new shape type:
- Implement shape → polygon conversion in GBGeometryMath
- Add test ensuring correct vertex count and bounds
- (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 useMIN_POLY_TILE_OVERLAP_RATIO
(0.12). - For reproducible changes, update these constants in
collision_mapper.gd
and add tests covering the affected edge cases.
Known limitations & future work
Section titled “Known limitations & future work”Area | Current State | Potential Enhancement |
---|---|---|
Concave polygons | Supported (processed as provided) but no auto-decomposition | Decompose to convex parts for potential faster intersection |
Rotated tileset grids | Assumes axis-aligned square tiles | Add support for isometric / staggered grids |
Large dynamic objects | Per-frame full remap | Add incremental dirty-region updates |
Epsilon globality | Single threshold shared across rules | Per-rule or per-layer override |
Parenting & movement
Section titled “Parenting & movement”- Shapes: offsets pivot around the owner’s tile (stable if only the positioner moves).
- Polygons: offsets pivot around the positioner’s tile.
- Offsets =
tile_pos - center_tile
.
Summary
Section titled “Summary”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.
Change log (focused excerpts)
Section titled “Change log (focused excerpts)”Date | Change | Rationale | Impact |
---|---|---|---|
2025-08-18 | Reverted experimental bounds-center polygon pivot to positioner-centered model | Preserve established test & gameplay frame of reference | Keeps existing offset expectations; removes need for test rewrites |
2025-08-18 | Introduced generic rectangle/circle tile normalization utilities (GBCollisionTileFilter) | Remove magic size branches (32x48, radius 24) | More maintainable, symmetrical coverage without hardcoded cases |
2025-08-18 | Restored trapezoid expected coverage (13 tiles) after mapper corrections | Validate intended geometry | Prevents silent coverage shrink regression |
2025-08-21 | Added polygon min‑area filter (12%) and rectangle center‑anchored odd‑span normalization | Eliminate slivers; ensure full used‑space (e.g., Smithy bottom‑middle) | Stable, predictable coverage; parity with tests |
2025-08-21 | Changed CollisionObject2D shape pivoting: shape offsets pivot around the owner’s tile (owner-centered) while polygon sources remain positioner-centered; allowed Area2D rectangle sources to contribute | Decouple shape offsets from the targeting positioner and avoid surprising offset changes when moving only the positioner | More intuitive behavior; requires no test rebase for most cases |
2025-08-21 | Per-shape tuning for round primitives: lower area threshold and corner pruning, plus symmetric mirroring enforcement | Remove spurious corner tiles for circles/capsules and ensure left/right symmetry | Improved UX for round shapes and consistent indicator geometry |
Last updated: 2025-08-21