Skip to content

Commit feaffc3

Browse files
authored
Fix the constant coefficient sign in Plane3.contains (#280)
`Plane3.contains` subtracts the constant coefficient, although planes and their constructors use `n @ p + d = 0`. For example, `Plane3([1, 0, 0, -2]).contains([2, 0, 0])` returns false, while the point `[-2, 0, 0]` is incorrectly accepted. Use the same sign as the plane equation. Regression tests cover translated planes constructed from coefficients, point/normal and three points, positive/negative coefficient scaling, off-plane points, tolerance, and the origin-plane case. Validation: 10 failures before the one-line fix; all 11 new cases pass afterward. Full suite: **484 passed, 3 skipped, 63 subtests passed** (Windows, Python 3.12, Matplotlib Agg). Black check for the new test file passes. Prepared with AI assistance; reproductions and tests were executed locally.
1 parent 9d64824 commit feaffc3

2 files changed

Lines changed: 40 additions & 1 deletion

File tree

‎spatialmath/geom3d.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,7 @@ def contains(self, p: ArrayLike3, tol: float = 20) -> bool:
190190
:return: if the point is in the plane
191191
:rtype: bool
192192
"""
193-
return abs(np.dot(self.n, p) - self.d) < tol * _eps
193+
return abs(np.dot(self.n, p) + self.d) < tol * _eps
194194

195195
def plot(
196196
self,

‎tests/test_plane_contains.py‎

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import numpy as np
2+
import pytest
3+
4+
from spatialmath import Plane3
5+
6+
7+
@pytest.mark.parametrize("scale", [1, -1, 2.5])
8+
@pytest.mark.parametrize("factory", ["coefficients", "point-normal", "three-points"])
9+
def test_translated_plane_contains_points(factory, scale):
10+
point = np.array([2.0, 3.0, 4.0])
11+
tangent1 = np.array([1.0, -1.0, 0.0])
12+
tangent2 = np.array([0.0, 1.0, -1.0])
13+
normal = np.cross(tangent1, tangent2)
14+
if factory == "coefficients":
15+
plane = Plane3(scale * np.r_[normal, -normal @ point])
16+
elif factory == "point-normal":
17+
plane = Plane3.PointNormal(point, scale * normal)
18+
else:
19+
plane = Plane3.ThreePoints(
20+
np.column_stack((point, point + scale * tangent1, point + tangent2))
21+
)
22+
23+
for candidate in (point, point + tangent1, point + tangent2):
24+
assert plane.contains(candidate)
25+
assert not plane.contains(point + normal)
26+
# The old sign incorrectly accepted the reflected plane.
27+
assert not plane.contains(-point)
28+
29+
30+
def test_plane_contains_tolerance():
31+
plane = Plane3([1, 0, 0, -2])
32+
assert plane.contains([2 + 1e-10, 0, 0], tol=1e6)
33+
assert not plane.contains([2 + 1e-10, 0, 0])
34+
35+
36+
def test_plane_through_origin():
37+
plane = Plane3([0, 0, 1, 0])
38+
assert plane.contains([2, 3, 0])
39+
assert not plane.contains([2, 3, 1])

0 commit comments

Comments
 (0)