Skip to content

Commit 9de9a7c

Browse files
scpetersmergify[bot]
authored andcommitted
Line2: increase test coverage (#807)
1. Set with Vector2 parameters: Verified setting start/end points using Vector2 instances. 2. CrossProduct: Added explicit tests for both Line2 and Vector2 cross products. Expect CrossProduct of parallel lines is 0. 3. OnSegment and Within: Tested points on segment, collinear points outside bounds, non-collinear points within bounding box, and endpoints. 4. Intersect Branch Coverage: • Parallel collinear non-overlapping line segments. • Collinear lines where only the second endpoint lies on the segment. • Line extensions that intersect outside X or Y bounding ranges. • Intersect overload taking no output point. 5. Operators: Expanded operator== and operator!= self-comparison checks. 6. Template Types: Added test cases for Line2i (int) and Line2f (float). Assisted-by: Gemini 3.6 Flash Fix type conversion warnings * Set: use static_cast<T> * Length: use simpler approach from Line3::Length along with static_cast<T> Signed-off-by: Steve Peters <scpeters@intrinsic.ai> (cherry picked from commit 268039c)
1 parent 647c632 commit 9de9a7c

3 files changed

Lines changed: 172 additions & 6 deletions

File tree

include/gz/math/Line2.hh

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -67,8 +67,8 @@ namespace gz::math
6767
/// \param[in] _y2 Y coordinate of the end point.
6868
public: void Set(double _x1, double _y1, double _x2, double _y2)
6969
{
70-
this->pts[0].Set(_x1, _y1);
71-
this->pts[1].Set(_x2, _y2);
70+
this->pts[0].Set(static_cast<T>(_x1), static_cast<T>(_y1));
71+
this->pts[1].Set(static_cast<T>(_x2), static_cast<T>(_y2));
7272
}
7373

7474
/// \brief Return the cross product of this line and the given line.
@@ -253,10 +253,7 @@ namespace gz::math
253253
/// \return The length of the line.
254254
public: T Length() const
255255
{
256-
return sqrt((this->pts[0].X() - this->pts[1].X()) *
257-
(this->pts[0].X() - this->pts[1].X()) +
258-
(this->pts[0].Y() - this->pts[1].Y()) *
259-
(this->pts[0].Y() - this->pts[1].Y()));
256+
return static_cast<T>(this->pts[0].Distance(this->pts[1]));
260257
}
261258

262259
/// \brief Get the slope of the line

src/Line2_TEST.cc

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,49 @@ TEST(Line2Test, Constructor)
4040
EXPECT_NO_THROW(lineB[2].X());
4141
EXPECT_DOUBLE_EQ(lineB[2].X(), lineB[1].X());
4242
EXPECT_NO_THROW(lineA[0].X());
43+
44+
// Test Set method with Vector2
45+
lineA.Set(math::Vector2d(5, 6), math::Vector2d(7, 8));
46+
EXPECT_EQ(lineA[0], math::Vector2d(5, 6));
47+
EXPECT_EQ(lineA[1], math::Vector2d(7, 8));
48+
}
49+
50+
/////////////////////////////////////////////////
51+
TEST(Line2Test, CrossProduct)
52+
{
53+
math::Line2d lineA(0, 0, 10, 0);
54+
math::Line2d lineB(0, 0, 0, 10);
55+
EXPECT_DOUBLE_EQ(lineA.CrossProduct(lineB), 100.0);
56+
57+
math::Vector2d pt(5, 5);
58+
EXPECT_DOUBLE_EQ(lineA.CrossProduct(pt), 50.0);
59+
}
60+
61+
/////////////////////////////////////////////////
62+
TEST(Line2Test, OnSegmentAndWithin)
63+
{
64+
math::Line2d line(0, 0, 10, 10);
65+
66+
// Point on segment
67+
math::Vector2d pt1(5, 5);
68+
EXPECT_TRUE(line.Within(pt1));
69+
EXPECT_TRUE(line.OnSegment(pt1));
70+
71+
// Point collinear with line, but outside segment bounds
72+
math::Vector2d pt2(15, 15);
73+
EXPECT_FALSE(line.Within(pt2));
74+
EXPECT_FALSE(line.OnSegment(pt2));
75+
76+
// Point within bounding box of segment, but not collinear
77+
math::Vector2d pt3(5, 6);
78+
EXPECT_TRUE(line.Within(pt3));
79+
EXPECT_FALSE(line.OnSegment(pt3));
80+
81+
// Endpoints
82+
EXPECT_TRUE(line.Within(line[0]));
83+
EXPECT_TRUE(line.OnSegment(line[0]));
84+
EXPECT_TRUE(line.Within(line[1]));
85+
EXPECT_TRUE(line.OnSegment(line[1]));
4386
}
4487

4588
/////////////////////////////////////////////////
@@ -83,29 +126,34 @@ TEST(Line2Test, ParallelLine)
83126
// Line is always parallel with itself
84127
math::Line2d line(0, 0, 10, 0);
85128
EXPECT_TRUE(line.Parallel(line, 1e-10));
129+
EXPECT_DOUBLE_EQ(line.CrossProduct(line), 0.0);
86130
}
87131

88132
{
89133
// Degenerate line segment
90134
// Still expect Line is parallel with itself
91135
math::Line2d line(0, 0, 0, 0);
92136
EXPECT_TRUE(line.Parallel(line, 1e-10));
137+
EXPECT_DOUBLE_EQ(line.CrossProduct(line), 0.0);
93138
}
94139

95140
math::Line2d lineA(0, 0, 10, 0);
96141
math::Line2d lineB(0, 0, 10, 0);
97142
EXPECT_TRUE(lineA.Parallel(lineB, 1e-10));
143+
EXPECT_DOUBLE_EQ(lineA.CrossProduct(lineB), 0.0);
98144

99145
lineB.Set(0, 0, 0, 10);
100146
EXPECT_FALSE(lineA.Parallel(lineB));
101147

102148
lineB.Set(0, 10, 10, 10);
103149
EXPECT_TRUE(lineA.Parallel(lineB));
150+
EXPECT_DOUBLE_EQ(lineA.CrossProduct(lineB), 0.0);
104151

105152
lineB.Set(0, 10, 10, 10.00001);
106153
EXPECT_FALSE(lineA.Parallel(lineB, 1e-10));
107154
EXPECT_FALSE(lineA.Parallel(lineB));
108155
EXPECT_TRUE(lineA.Parallel(lineB, 1e-3));
156+
EXPECT_NEAR(lineA.CrossProduct(lineB), 0.0, 1e-3);
109157
}
110158

111159
/////////////////////////////////////////////////
@@ -234,6 +282,28 @@ TEST(Line2Test, Intersect)
234282
lineB.Set(0, 10, 10, 0);
235283
EXPECT_TRUE(lineA.Intersect(lineB, pt));
236284
EXPECT_EQ(pt, math::Vector2d(5, 5));
285+
286+
// Collinear parallel non-overlapping lines
287+
lineA.Set(0, 0, 10, 0);
288+
lineB.Set(20, 0, 30, 0);
289+
EXPECT_FALSE(lineA.Intersect(lineB, pt));
290+
EXPECT_FALSE(lineA.Intersect(lineB));
291+
292+
// Collinear lines where lineB end point is within lineA but start point isn't
293+
lineA.Set(0, 0, 10, 0);
294+
lineB.Set(-5, 0, 5, 0);
295+
EXPECT_TRUE(lineA.Intersect(lineB, pt));
296+
EXPECT_EQ(pt, math::Vector2d(5, 0));
297+
298+
// Lines whose extensions intersect, but intersection Y is outside bounds
299+
lineA.Set(-10, 0, 10, 0);
300+
lineB.Set(0, 2, 0, 10);
301+
EXPECT_FALSE(lineA.Intersect(lineB, pt));
302+
303+
// Lines whose extensions intersect, but intersection X is outside bounds
304+
lineA.Set(0, -10, 0, 10);
305+
lineB.Set(2, 0, 10, 0);
306+
EXPECT_FALSE(lineA.Intersect(lineB, pt));
237307
}
238308

239309
/////////////////////////////////////////////////
@@ -243,7 +313,9 @@ TEST(Line2Test, Equality)
243313
math::Line2d lineB(1, 2, 2, 2);
244314

245315
EXPECT_TRUE(lineA != lineB);
316+
EXPECT_FALSE(lineA == lineB);
246317
EXPECT_TRUE(lineA == lineA);
318+
EXPECT_FALSE(lineA != lineA);
247319

248320
lineB.Set(1, 1, 2, 1.1);
249321
EXPECT_FALSE(lineA == lineB);
@@ -266,3 +338,18 @@ TEST(Line2Test, OperatorStreamOut)
266338
stream << line;
267339
EXPECT_EQ(stream.str(), "0 1 2 3");
268340
}
341+
342+
/////////////////////////////////////////////////
343+
TEST(Line2Test, TemplateTypes)
344+
{
345+
math::Line2i lineI(0, 0, 10, 10);
346+
EXPECT_EQ(lineI[0], math::Vector2i(0, 0));
347+
EXPECT_EQ(lineI[1], math::Vector2i(10, 10));
348+
EXPECT_EQ(lineI.Length(), 14);
349+
350+
math::Line2f lineF(0.0f, 0.0f, 10.0f, 10.0f);
351+
EXPECT_FLOAT_EQ(lineF[0].X(), 0.0f);
352+
EXPECT_FLOAT_EQ(lineF[1].Y(), 10.0f);
353+
EXPECT_NEAR(lineF.Length(), 14.1421356f, 1e-4f);
354+
}
355+

src/python_pybind11/test/Line2_TEST.py

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,11 @@
1515
import math
1616
import unittest
1717
from gz.math import Line2d
18+
from gz.math import Line2f
19+
from gz.math import Line2i
1820
from gz.math import Vector2d
21+
from gz.math import Vector2f
22+
from gz.math import Vector2i
1923

2024

2125
class TestLine2d(unittest.TestCase):
@@ -35,6 +39,43 @@ def test_construction(self):
3539

3640
self.assertAlmostEqual(line_b[2].x(), line_b[1].x())
3741

42+
# Test set method with Vector2d
43+
line_a.set(Vector2d(5, 6), Vector2d(7, 8))
44+
self.assertEqual(line_a[0], Vector2d(5, 6))
45+
self.assertEqual(line_a[1], Vector2d(7, 8))
46+
47+
def test_cross_product(self):
48+
line_a = Line2d(0, 0, 10, 0)
49+
line_b = Line2d(0, 0, 0, 10)
50+
self.assertAlmostEqual(line_a.cross_product(line_b), 100.0)
51+
52+
pt = Vector2d(5, 5)
53+
self.assertAlmostEqual(line_a.cross_product(pt), 50.0)
54+
55+
def test_on_segment_and_within(self):
56+
line = Line2d(0, 0, 10, 10)
57+
58+
# Point on segment
59+
pt1 = Vector2d(5, 5)
60+
self.assertTrue(line.within(pt1, 1e-6))
61+
self.assertTrue(line.on_segment(pt1, 1e-6))
62+
63+
# Point collinear with line, but outside segment bounds
64+
pt2 = Vector2d(15, 15)
65+
self.assertFalse(line.within(pt2, 1e-6))
66+
self.assertFalse(line.on_segment(pt2, 1e-6))
67+
68+
# Point within bounding box of segment, but not collinear
69+
pt3 = Vector2d(5, 6)
70+
self.assertTrue(line.within(pt3, 1e-6))
71+
self.assertFalse(line.on_segment(pt3, 1e-6))
72+
73+
# Endpoints
74+
self.assertTrue(line.within(line[0], 1e-6))
75+
self.assertTrue(line.on_segment(line[0], 1e-6))
76+
self.assertTrue(line.within(line[1], 1e-6))
77+
self.assertTrue(line.on_segment(line[1], 1e-6))
78+
3879
def test_length(self):
3980
line_a = Line2d(0, 0, 10, 10)
4081
self.assertAlmostEqual(line_a.length(), math.sqrt(200), delta=1e-10)
@@ -53,26 +94,31 @@ def test_parallel_line(self):
5394
# Line is always parallel with itself
5495
line = Line2d(0, 0, 10, 0)
5596
self.assertTrue(line.parallel(line, 1e-10))
97+
self.assertAlmostEqual(line.cross_product(line), 0.0)
5698

5799
# Degenerate line segment
58100
# Still expect Line is parallel with itself
59101
line = Line2d(0, 0, 0, 0)
60102
self.assertTrue(line.parallel(line, 1e-10))
103+
self.assertAlmostEqual(line.cross_product(line), 0.0)
61104

62105
line_a = Line2d(0, 0, 10, 0)
63106
line_b = Line2d(0, 0, 10, 0)
64107
self.assertTrue(line_a.parallel(line_b, 1e-10))
108+
self.assertAlmostEqual(line_a.cross_product(line_b), 0.0)
65109

66110
line_b.set(0, 0, 0, 10)
67111
self.assertFalse(line_a.parallel(line_b))
68112

69113
line_b.set(0, 10, 10, 10)
70114
self.assertTrue(line_a.parallel(line_b))
115+
self.assertAlmostEqual(line_a.cross_product(line_b), 0.0)
71116

72117
line_b.set(0, 10, 10, 10.00001)
73118
self.assertFalse(line_a.parallel(line_b, 1e-10))
74119
self.assertFalse(line_a.parallel(line_b))
75120
self.assertTrue(line_a.parallel(line_b, 1e-3))
121+
self.assertAlmostEqual(line_a.cross_product(line_b), 0.0, delta=1e-3)
76122

77123
def test_collinear_line(self):
78124
# Line is always collinear with itself
@@ -187,12 +233,36 @@ def test_intersect(self):
187233
self.assertTrue(line_a.intersect(line_b, pt))
188234
self.assertEqual(pt, Vector2d(5, 5))
189235

236+
# Collinear parallel non-overlapping lines
237+
line_a.set(0, 0, 10, 0)
238+
line_b.set(20, 0, 30, 0)
239+
self.assertFalse(line_a.intersect(line_b, pt))
240+
self.assertFalse(line_a.intersect(line_b))
241+
242+
# Collinear lines where line_b end point is within line_a but start point is not
243+
line_a.set(0, 0, 10, 0)
244+
line_b.set(-5, 0, 5, 0)
245+
self.assertTrue(line_a.intersect(line_b, pt))
246+
self.assertEqual(pt, Vector2d(5, 0))
247+
248+
# Lines whose extensions intersect, but intersection Y is outside bounds
249+
line_a.set(-10, 0, 10, 0)
250+
line_b.set(0, 2, 0, 10)
251+
self.assertFalse(line_a.intersect(line_b, pt))
252+
253+
# Lines whose extensions intersect, but intersection X is outside bounds
254+
line_a.set(0, -10, 0, 10)
255+
line_b.set(2, 0, 10, 0)
256+
self.assertFalse(line_a.intersect(line_b, pt))
257+
190258
def test_equality(self):
191259
line_a = Line2d(1, 1, 2, 1)
192260
line_b = Line2d(1, 2, 2, 2)
193261

194262
self.assertTrue(line_a != line_b)
263+
self.assertFalse(line_a == line_b)
195264
self.assertTrue(line_a == line_a)
265+
self.assertFalse(line_a != line_a)
196266

197267
line_b.set(1, 1, 2, 1.1)
198268
self.assertFalse(line_a == line_b)
@@ -210,6 +280,18 @@ def test_serialization(self):
210280
line = Line2d(0, 1, 2, 3)
211281
self.assertEqual(str(line), "0 1 2 3")
212282

283+
def test_template_types(self):
284+
line_i = Line2i(0, 0, 10, 10)
285+
self.assertEqual(line_i[0], Vector2i(0, 0))
286+
self.assertEqual(line_i[1], Vector2i(10, 10))
287+
self.assertEqual(line_i.length(), 14)
288+
289+
line_f = Line2f(0.0, 0.0, 10.0, 10.0)
290+
self.assertAlmostEqual(line_f[0].x(), 0.0)
291+
self.assertAlmostEqual(line_f[1].y(), 10.0)
292+
self.assertAlmostEqual(line_f.length(), 14.1421356, delta=1e-4)
293+
213294

214295
if __name__ == '__main__':
215296
unittest.main()
297+

0 commit comments

Comments
 (0)