Table of Contents

Class PolygonTileMapper

Namespace
GridPlacement.GDScript
Assembly
MoonBark.GridPlacement.GDScript.dll

Handles polygon-to-tile-offset conversion with testable, separated concerns.\n\nThis class extracts the complex polygon processing logic from CollisionMapper to enable better testing and separation of responsibilities.\n\nCoordinate semantics - Tile addressing is center-based. For 16×16 tiles, the center is at +8,+8 from the tile origin. - Tests and runtime share "bottom-inclusive, top-exclusive" behavior on axis-aligned boundaries to avoid fencepost rows when a polygon edge lies exactly on a tile boundary.\n\nPipeline stages: 1. Transform polygon to world space and compute initial tile coverage 2. Apply trapezoid expansion heuristics for convex polygons when beneficial 3. Prune concave polygon fringes to remove unwanted overhangs 4. Filter tiles by minimum area overlap to remove slivers (with concave center-point check)

class PolygonTileMapper
PolygonTileMapper

Fields

MIN_POLY_TILE_OVERLAP_RATIO

const MIN_POLY_TILE_OVERLAP_RATIO = 0.12

Properties

default_ratio

Configuration for area-based filtering thresholds

var default_ratio: float = MIN_POLY_TILE_OVERLAP_RATIO

convex_ratio

var convex_ratio: float = 0.01

expanded_trapezoid_ratio

var expanded_trapezoid_ratio: float = 0.01

expansion_candidate_ratio

var expansion_candidate_ratio: float = 0.01

offsets

var offsets = CollisionGeometryUtils.compute_polygon_tile_offsets(world_points, tile_size, center_tile, tile_shape_val, map)

did_expand_trapezoid

var did_expand_trapezoid: bool = false

was_convex

var was_convex: bool = false

initial_offset_count

var initial_offset_count: int = 0

final_offset_count

var final_offset_count: int = 0

center_tile

var center_tile = map.local_to_map(map.to_local(polygon_node.global_position))

center_tile

var center_tile = map.local_to_map(map.to_local(polygon_node.global_position))

world_points

var world_points = _transform_polygon_world(polygon_node)

tile_size

var tile_size = Vector2(map.tile_set.tile_size)

has_tile_shape

var has_tile_shape = false

tile_shape_val

var tile_shape_val = map.tile_set.tile_shape

offsets

var offsets = CollisionGeometryUtils.compute_polygon_tile_offsets(world_points, tile_size, center_tile, tile_shape_val, map)

tile_shape_val

var tile_shape_val = map.tile_set.tile_shape

offsets

var offsets = CollisionGeometryUtils.compute_polygon_tile_offsets(world_points, tile_size, center_tile, tile_shape_val, map)

is_convex

var is_convex: bool = CollisionGeometryUtils.is_polygon_convex(polygon_node.polygon)

did_expand_trapezoid

var did_expand_trapezoid: bool = false

expansion_result

var expansion_result = _apply_trapezoid_expansion(offsets, world_points, tile_size, center_tile)

thresholds

var thresholds = AreaThresholds.new()

min_area

var min_area = tile_size.x * tile_size.y * min_ratio

filtered

var filtered: Array[Vector2i] = []

abs_tile

var abs_tile = center_tile + candidate

tile_rect

var tile_rect = Rect2(tile_center_local - tile_size/2.0, tile_size)

area

var area = get_polygon_tile_overlap_area(world_points, tile_rect)

result

var result: Dictionary = {"expanded": false, "offsets": offsets}

center_tile

var center_tile = map.local_to_map(map.to_local(polygon_node.global_position))

world_points

var world_points = _transform_polygon_world(polygon_node)

tile_size

var tile_size = Vector2(map.tile_set.tile_size)

has_tile_shape

var has_tile_shape = false

tile_shape_val

var tile_shape_val = map.tile_set.tile_shape

offsets

var offsets = CollisionGeometryUtils.compute_polygon_tile_offsets(world_points, tile_size, center_tile, tile_shape_val, map)

expansion_result

var expansion_result = _apply_trapezoid_expansion(offsets, world_points, tile_size, center_tile)

thresholds

var thresholds = AreaThresholds.new()

result

var result: Dictionary = {"expanded": false, "offsets": offsets}

analysis

var analysis: Dictionary = _analyze_offset_pattern(offsets)

hollow_shape

var hollow_shape: bool = PolygonIndicatorHeuristics.is_hollow(offsets)

expanded

var expanded: Array[Vector2i] = PolygonIndicatorHeuristics.generate_trapezoid_offsets()

validated

var validated: Array[Vector2i] = []

min_ratio

var min_ratio = _determine_area_threshold(is_convex, did_expand, thresholds)

min_area

var min_area = tile_size.x * tile_size.y * min_ratio

filtered

var filtered: Array[Vector2i] = []

abs_tile

var abs_tile = center_tile + candidate

tile_rect

var tile_rect = Rect2(tile_center_local - tile_size/2.0, tile_size)

area

var area = get_polygon_tile_overlap_area(world_points, tile_rect)

should_include

var should_include = area >= min_area

tile_center_world

var tile_center_world = map.to_global(tile_center_local)

eps

var eps = 0.0001

xs

var xs: Array[float] = []

ys

var ys: Array[int] = []

found_x

var found_x = false

found_y

var found_y = false

bounds

var bounds: Rect2 = _compute_polygon_bounds(world_points)

height_tiles

var height_tiles: float = bounds.size.y / tile_size.y

max_y

var max_y = filtered[0].y

adjusted

var adjusted: Array[Vector2i] = []

ys

var ys: Array[int] = []

xs_by_y

var xs_by_y: Dictionary = {}

validated

var validated: Array[Vector2i] = []

tile_area

var tile_area = tile_size.x * tile_size.y

min_expansion_area

var min_expansion_area = tile_area * 0.02

abs_tile

var abs_tile = center_tile + candidate

tile_center_local

var tile_center_local = map.map_to_local(abs_tile)

tile_rect

var tile_rect = Rect2(tile_center_local - tile_size/2.0, tile_size)

area

var area = get_polygon_tile_overlap_area(world_points, tile_rect)

original_set

var original_set: Dictionary = {}

tile_center_local

var tile_center_local = map.map_to_local(abs_tile)

tile_center_world

var tile_center_world = map.to_global(tile_center_local)

poly_bounds

var poly_bounds: Rect2 = _compute_polygon_bounds(polygon)

is_rect

var is_rect: bool = true

corners

var corners: Array[Vector2] = [

found

var found: bool = false

min_pt

var min_pt: Vector2 = polygon[0]

max_pt

var max_pt: Vector2 = polygon[0]

Methods

print_diagnostics

func print_diagnostics() -> void:

Returns

compute_tile_offsets

static func compute_tile_offsets(polygon_node: CollisionPolygon2D, map: TileMapLayer) -> Array[Vector2i]:

Parameters

polygon_node
map

Returns

compute_tile_offsets_with_positioner

static func compute_tile_offsets_with_positioner(polygon_node: CollisionPolygon2D, map: TileMapLayer, positioner: Node2D) -> Array[Vector2i]:

Parameters

polygon_node
map
positioner

Returns

process_polygon_with_diagnostics

static func process_polygon_with_diagnostics(polygon_node: CollisionPolygon2D, map: TileMapLayer) -> ProcessingResult:

Parameters

polygon_node
map

Returns

get_polygon_tile_overlap_area

static func get_polygon_tile_overlap_area(polygon: PackedVector2Array, rect: Rect2) -> float:

Parameters

polygon
rect

Returns