Ports IntBox, the simplest concrete convex tile (RegularTileShape): exact integer-corner rectangle with contains (border-inclusive and interior), intersection, union, intersects/overlaps, offset (round half up), horizontal/vertical offset, shrink, box containment, translate, and dimension. Adds the package __init__ exporting the geometry API. The general convex machinery beyond IntBox — TileShape/Simplex/IntOctagon and polygon split_to_convex — is deferred to the next geometry phase. Tests cover boundary vs interior containment, degenerate tiles (empty/point/segment), edge-touching intersects-vs-overlaps, rational point on a border, and half-up offset rounding.
132 lines
3.9 KiB
Python
132 lines
3.9 KiB
Python
"""Tests for IntBox.
|
|
|
|
Oracles hand-derived from ``geometry/planar/IntBox.java``. Emphasis on boundary
|
|
containment and degenerate (empty / point / segment) tiles.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from freeroute.geometry import IntBox, IntPoint, IntVector, RationalPoint
|
|
|
|
|
|
def test_measures():
|
|
b = IntBox(1, 2, 5, 8)
|
|
assert b.width() == 4
|
|
assert b.height() == 6
|
|
assert b.area() == 24
|
|
assert b.circumference() == 20
|
|
assert b.max_width() == 6
|
|
assert b.min_width() == 4
|
|
|
|
|
|
def test_corners_counter_clockwise():
|
|
b = IntBox(0, 0, 4, 3)
|
|
assert b.corners() == [
|
|
IntPoint(0, 0),
|
|
IntPoint(4, 0),
|
|
IntPoint(4, 3),
|
|
IntPoint(0, 3),
|
|
]
|
|
|
|
|
|
def test_dimension_variants():
|
|
assert IntBox(0, 0, 4, 3).dimension() == 2
|
|
assert IntBox(0, 0, 0, 3).dimension() == 1 # vertical segment
|
|
assert IntBox(0, 0, 4, 0).dimension() == 1 # horizontal segment
|
|
assert IntBox(2, 2, 2, 2).dimension() == 0 # single point
|
|
assert IntBox.empty().dimension() == -1
|
|
|
|
|
|
def test_boundary_containment_inclusive_vs_interior():
|
|
b = IntBox(0, 0, 10, 10)
|
|
corner = IntPoint(0, 0)
|
|
edge = IntPoint(0, 5)
|
|
inside = IntPoint(5, 5)
|
|
outside = IntPoint(11, 5)
|
|
# border-inclusive contains
|
|
assert b.contains(corner)
|
|
assert b.contains(edge)
|
|
assert b.contains(inside)
|
|
assert not b.contains(outside)
|
|
# strict interior
|
|
assert not b.contains_inside(corner)
|
|
assert not b.contains_inside(edge)
|
|
assert b.contains_inside(inside)
|
|
|
|
|
|
def test_contains_rational_point_on_border():
|
|
b = IntBox(0, 0, 3, 3)
|
|
# (3, 1.5) sits exactly on the right edge -> contained (inclusive)
|
|
assert b.contains(RationalPoint(6, 3, 2))
|
|
# (3.5, 1.5) just outside
|
|
assert not b.contains(RationalPoint(7, 3, 2))
|
|
|
|
|
|
def test_intersection_overlap_and_touch():
|
|
a = IntBox(0, 0, 10, 10)
|
|
assert a.intersection(IntBox(5, 5, 20, 20)) == IntBox(5, 5, 10, 10)
|
|
# touching along an edge -> 1-dimensional intersection, still non-empty
|
|
touch = a.intersection(IntBox(10, 0, 20, 10))
|
|
assert touch == IntBox(10, 0, 10, 10)
|
|
assert touch.dimension() == 1
|
|
# disjoint -> empty
|
|
assert a.intersection(IntBox(20, 20, 30, 30)).is_empty()
|
|
|
|
|
|
def test_intersects_vs_overlaps_on_touching_edge():
|
|
a = IntBox(0, 0, 10, 10)
|
|
edge_neighbor = IntBox(10, 0, 20, 10)
|
|
assert a.intersects(edge_neighbor) # closed-interval: touching counts
|
|
assert not a.overlaps(edge_neighbor) # open-interval: no 2D overlap
|
|
|
|
|
|
def test_union():
|
|
assert IntBox(0, 0, 2, 2).union(IntBox(5, 5, 8, 8)) == IntBox(0, 0, 8, 8)
|
|
|
|
|
|
def test_offset_expands_and_contracts():
|
|
b = IntBox(0, 0, 10, 10)
|
|
assert b.offset(2) == IntBox(-2, -2, 12, 12)
|
|
assert b.offset(-3) == IntBox(3, 3, 7, 7)
|
|
assert b.offset(0) is b
|
|
assert b.horizontal_offset(2) == IntBox(-2, 0, 12, 10)
|
|
assert b.vertical_offset(2) == IntBox(0, -2, 10, 12)
|
|
|
|
|
|
def test_offset_rounds_half_up():
|
|
b = IntBox(0, 0, 10, 10)
|
|
assert b.offset(2.5) == IntBox(-3, -3, 13, 13) # 2.5 -> 3 (half up)
|
|
|
|
|
|
def test_shrink_collapses_to_center_not_past():
|
|
b = IntBox(0, 0, 10, 10)
|
|
assert b.shrink(3) == IntBox(3, 3, 7, 7)
|
|
# over-shrink collapses to the center line rather than inverting
|
|
collapsed = b.shrink(20)
|
|
assert collapsed == IntBox(5, 5, 5, 5)
|
|
assert collapsed.dimension() == 0
|
|
|
|
|
|
def test_containment_between_boxes():
|
|
outer = IntBox(0, 0, 10, 10)
|
|
inner = IntBox(2, 2, 8, 8)
|
|
assert inner.is_contained_in(outer)
|
|
assert outer.contains_box(inner)
|
|
assert outer.contains_in_interior(inner)
|
|
edge = IntBox(0, 2, 8, 8) # touches left edge
|
|
assert edge.is_contained_in(outer)
|
|
assert not outer.contains_in_interior(edge)
|
|
|
|
|
|
def test_translate_by():
|
|
b = IntBox(0, 0, 4, 4)
|
|
assert b.translate_by(IntVector(3, -2)) == IntBox(3, -2, 7, 2)
|
|
assert b.translate_by(IntVector(0, 0)) is b
|
|
|
|
|
|
def test_empty_box_predicates():
|
|
e = IntBox.empty()
|
|
assert e.is_empty()
|
|
assert e.is_contained_in(IntBox(0, 0, 1, 1))
|
|
assert e.offset(5) is e
|