Skip to content

Commit d0c4afb

Browse files
authored
Include line moment when constructing a plane from a line and point (#281)
Plane3.LinePoint currently computes the normal as w cross p, omitting the line moment. This only gives the intended plane when the line passes through the origin. For example a line through [1,2,3] in direction [1,0,0] and a point [1,4,3] should define z=3; the current result does not contain the supplied point or line. Subtract the moment v from w cross p, obtaining w cross (p-anchor). The constant term v dot p remains unchanged. Regression tests check the plane equation directly, independently of the separate Plane3.contains fix in #280, using translated and origin-passing lines and rescaled directions. Validation: six new cases fail and three pass on unchanged code. With this fix the full tests directory reports 482 passed, 3 skipped, 63 subtests passed (Python 3.11, NumPy 2). No other pending geometry fixes are included. AI assistance was used to investigate, implement and test this change.
1 parent feaffc3 commit d0c4afb

2 files changed

Lines changed: 27 additions & 1 deletion

File tree

‎spatialmath/geom3d.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ def LinePoint(cls, l: Line3, p: ArrayLike3) -> Self:
9595
9696
:seealso: :meth:`PointNormal` :meth:`ThreePoints`
9797
"""
98-
n = np.cross(l.w, p)
98+
n = np.cross(l.w, p) - l.v
9999
d = np.dot(l.v, p)
100100

101101
return cls(np.r_[n, d])

‎tests/test_plane_line_point.py‎

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import numpy as np
2+
import pytest
3+
4+
from spatialmath import Line3, Plane3
5+
6+
7+
@pytest.mark.parametrize(
8+
"anchor,direction,point",
9+
[
10+
([1, 2, 3], [1, 0, 0], [1, 4, 3]),
11+
([2, -1, 4], [1, 2, -1], [3, 2, 5]),
12+
([0, 0, 0], [0, 0, 1], [2, 3, 4]),
13+
],
14+
)
15+
@pytest.mark.parametrize("scale", [1, -2, 0.25])
16+
def test_line_point_plane_contains_translated_line(anchor, direction, point, scale):
17+
anchor = np.asarray(anchor, dtype=float)
18+
direction = np.asarray(direction, dtype=float) * scale
19+
line = Line3.PointDir(anchor, direction)
20+
plane = Plane3.LinePoint(line, np.asarray(point, dtype=float))
21+
22+
# Use the plane equation directly, independent of Plane3.contains.
23+
for p in [point, anchor, anchor + direction, anchor - 3 * direction]:
24+
np.testing.assert_allclose(plane.n @ p + plane.d, 0, atol=1e-12)
25+
expected_normal = np.cross(direction, np.asarray(point) - anchor)
26+
np.testing.assert_allclose(plane.n, expected_normal, atol=1e-12)

0 commit comments

Comments
 (0)