Ports Direction/IntDirection (equivalence classes of vectors, gcd- normalized, exact angular compare) and Line. Line.side_of uses an exact integer determinant; Line.intersection returns an IntPoint when the crossing is integral and a RationalPoint otherwise, with the orthogonal and 45-degree fast paths from the source preserved. Parallel lines yield a point at infinity (z=0). BigIntDirection is folded into IntDirection since unbounded int always fits. Tests cover integral and rational intersections, parallel-line infinity, exactness beyond double precision (verified via exact collinearity of the result), and direction normalization/ordering.
62 lines
2.0 KiB
Python
62 lines
2.0 KiB
Python
"""Tests for directions.
|
|
|
|
Oracles hand-derived from ``geometry/planar/{Direction,IntDirection}.java``.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from freeroute.geometry import Direction, IntDirection, IntPoint, IntVector, Side
|
|
|
|
|
|
def test_normalized_direction_reduces_by_gcd():
|
|
d = IntVector(2, 4).to_normalized_direction()
|
|
assert isinstance(d, IntDirection)
|
|
assert (d.x, d.y) == (1, 2)
|
|
|
|
|
|
def test_normalized_direction_beyond_java_int():
|
|
# gcd reduction stays exact for coordinates far past Java's int range.
|
|
d = IntVector(6 * 10**12, 9 * 10**12).to_normalized_direction()
|
|
assert (d.x, d.y) == (2, 3)
|
|
|
|
|
|
def test_turn_45_degree_cycles():
|
|
right = IntDirection(1, 0)
|
|
assert (right.turn_45_degree(1).x, right.turn_45_degree(1).y) == (1, 1)
|
|
assert (right.turn_45_degree(2).x, right.turn_45_degree(2).y) == (0, 1)
|
|
assert (right.turn_45_degree(4).x, right.turn_45_degree(4).y) == (-1, 0)
|
|
assert (right.turn_45_degree(8).x, right.turn_45_degree(8).y) == (1, 0)
|
|
|
|
|
|
def test_opposite():
|
|
o = IntDirection(1, 2).opposite()
|
|
assert (o.x, o.y) == (-1, -2)
|
|
|
|
|
|
def test_direction_equals_ignores_magnitude_not_sign():
|
|
a = IntDirection(1, 1)
|
|
assert a.equals(IntDirection(3, 3)) # same ray
|
|
assert not a.equals(IntDirection(-1, -1)) # opposite ray
|
|
assert not a.equals(IntDirection(1, 0))
|
|
|
|
|
|
def test_from_points_returns_none_for_equal():
|
|
assert Direction.from_points(IntPoint(1, 1), IntPoint(1, 1)) is None
|
|
d = Direction.from_points(IntPoint(0, 0), IntPoint(4, 4))
|
|
assert (d.x, d.y) == (1, 1)
|
|
|
|
|
|
def test_compare_to_angular_order():
|
|
right = IntDirection(1, 0)
|
|
up = IntDirection(0, 1)
|
|
down = IntDirection(0, -1)
|
|
# angle(right)=0, angle(up)=90, angle(down)=270
|
|
assert right.compare_to(up) == -1
|
|
assert up.compare_to(right) == 1
|
|
assert up.compare_to(down) == -1
|
|
assert right.compare_to(IntDirection(2, 0)) == 0
|
|
|
|
|
|
def test_side_of_direction():
|
|
assert IntDirection(0, 1).side_of(IntDirection(1, 0)) == Side.ON_THE_LEFT
|