Skip to content

Commit 6f641a0

Browse files
committed
Merge PR #273: fix(geom3d): return the intersection point of two lines
2 parents 68e086b + 5c4a3d1 commit 6f641a0

2 files changed

Lines changed: 41 additions & 7 deletions

File tree

‎spatialmath/geom3d.py‎

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -830,15 +830,12 @@ def intersects(
830830
``l1.intersects(l2)`` is the point of intersection of the two lines, or
831831
``None`` if the lines do not intersect or are equivalent.
832832
833-
:seealso: :meth:`commonperp :meth:`eq` :meth:`__xor__`
833+
:seealso: :meth:`commonperp` :meth:`isintersecting` :meth:`closest_to_line`
834834
"""
835835
if l1 ^ l2:
836-
# lines do intersect
837-
return -(
838-
np.dot(l1.v, l2.w) * np.eye(3, 3)
839-
+ l1.w.reshape((3, 1)) @ l2.v.reshape((1, 3))
840-
- l2.w.reshape((3, 1)) @ l1.v.reshape((1, 3))
841-
) * base.unitvec(np.cross(l1.w, l2.w))
836+
# For intersecting lines, the nearest point is their intersection.
837+
point, _ = l1.closest_to_line(l2)
838+
return point
842839
else:
843840
# lines don't intersect
844841
return None

‎tests/test_geom3d.py‎

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,43 @@ def test_line_distance_parallel_tolerance():
4747
assert line1.distance(line2, tol=1e8) == pytest.approx(np.sqrt(5))
4848

4949

50+
@pytest.mark.parametrize(
51+
"point, direction1, direction2",
52+
[
53+
([0, 0, 0], [1, 0, 0], [0, 1, 0]),
54+
([1, 2, 3], [1, 0, 0], [1, 1, 0]),
55+
([-3, 1, 2], [1, 2, 3], [-2, 1, 1]),
56+
],
57+
ids=["origin", "oblique", "spatial"],
58+
)
59+
@pytest.mark.parametrize(
60+
"scale1, scale2", [(1, 1), (2, 5), (-2, 0.25), (1e-8, 1e-8), (1e-8, 1e8)]
61+
)
62+
def test_line_intersection_point(point, direction1, direction2, scale1, scale2):
63+
point = np.array(point)
64+
direction1, direction2 = np.array(direction1), np.array(direction2)
65+
line1 = Line3.PointDir(point + 2 * direction1, scale1 * direction1)
66+
line2 = Line3.PointDir(point - 3 * direction2, scale2 * direction2)
67+
68+
for first, second in [(line1, line2), (line2, line1)]:
69+
intersection = first.intersects(second)
70+
assert intersection is not None
71+
assert intersection.shape == (3,)
72+
nt.assert_allclose(intersection, point, rtol=0, atol=1e-12)
73+
74+
75+
@pytest.mark.parametrize(
76+
"point, direction",
77+
[([0, 1, 0], [1, 0, 0]), ([2, 0, 0], [-2, 0, 0]), ([0, 0, 1], [0, 1, 0])],
78+
ids=["parallel", "coincident", "skew"],
79+
)
80+
def test_line_intersection_none(point, direction):
81+
line1 = Line3.PointDir([0, 0, 0], [1, 0, 0])
82+
line2 = Line3.PointDir(point, direction)
83+
assert line1.intersects(line2) is None
84+
assert line2.intersects(line1) is None
85+
86+
5087
class Line3Test(unittest.TestCase):
5188
# Primitives
5289
def test_constructor1(self):

0 commit comments

Comments
 (0)