geom2d 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,37 @@
1
+ # -*- frozen_string_literal: true -*-
2
+
3
+ require 'test_helper'
4
+ require 'geom2d/bounding_box'
5
+
6
+ describe Geom2D::BoundingBox do
7
+ before do
8
+ @bbox = Geom2D::BoundingBox.new
9
+ end
10
+
11
+ describe "join" do
12
+ it "combines bounding boxes" do
13
+ result = @bbox + Geom2D::BoundingBox.new(5, 5, 10, 10) +
14
+ Geom2D::BoundingBox.new(-2, 8, 15, 9)
15
+ assert_equal([-2, 0, 15, 10], result.to_a)
16
+ end
17
+
18
+ it "adjust the bounding box to include the point" do
19
+ result = @bbox + Geom2D::Point(5, 10)
20
+ assert_equal([0, 0, 5, 10], result.to_a)
21
+ end
22
+
23
+ it "fails if an invalid argument is given" do
24
+ assert_raises(ArgumentError) { @bbox + "string" }
25
+ end
26
+ end
27
+
28
+ it "returns the width and height" do
29
+ @bbox += Geom2D::Point(10, 5)
30
+ assert_equal(10, @bbox.width)
31
+ assert_equal(5, @bbox.height)
32
+ end
33
+
34
+ it "returns a useful inspection string" do
35
+ assert_equal("BBox[0, 0, 0, 0]", @bbox.inspect)
36
+ end
37
+ end
@@ -0,0 +1,148 @@
1
+ # -*- frozen_string_literal: true -*-
2
+
3
+ require 'test_helper'
4
+ require 'geom2d/point'
5
+
6
+ describe Geom2D::Point do
7
+ before do
8
+ @point = Geom2D::Point(1, 2)
9
+ end
10
+
11
+ describe "Point method" do
12
+ it "creates a point using two numbers" do
13
+ point = Geom2D::Point(1, 2)
14
+ assert_equal([1, 2], point)
15
+ end
16
+
17
+ it "creates a point using an array of two numbers" do
18
+ point = Geom2D::Point([1, 2])
19
+ assert_equal([1, 2], point)
20
+ end
21
+
22
+ it "returns a given point object" do
23
+ p1 = Geom2D::Point(1, 2)
24
+ p2 = Geom2D::Point(p1)
25
+ assert_same(p1, p2)
26
+ end
27
+ end
28
+
29
+ it "returns the x coordinate" do
30
+ assert_equal(1, @point.x)
31
+ end
32
+
33
+ it "returns the y coordinate" do
34
+ assert_equal(2, @point.y)
35
+ end
36
+
37
+ it "returns a bounding box that only encompasses itself" do
38
+ assert_equal([@point.x, @point.y] * 2, @point.bbox.to_a)
39
+ end
40
+
41
+ it "returns the distance from point to point" do
42
+ assert_equal(5, @point.distance(Geom2D::Point(5, 5)))
43
+ end
44
+
45
+ describe "unary +/-" do
46
+ it "unary plus returns self" do
47
+ assert_same(@point, +@point)
48
+ end
49
+
50
+ it "unary minus returns the point reflected in the origin" do
51
+ assert_equal([-1, -2], -@point)
52
+ end
53
+ end
54
+
55
+ describe "+" do
56
+ it "adds a scalar number" do
57
+ assert_equal([3, 4], @point + 2)
58
+ end
59
+
60
+ it "adds a point" do
61
+ assert_equal([2, 4], @point + @point)
62
+ end
63
+
64
+ it "interpretes an array as point" do
65
+ assert_equal([3, 5], @point + [2, 3])
66
+ end
67
+
68
+ it "fails if the argument class is invalid" do
69
+ assert_raises(ArgumentError) { @point + "str" }
70
+ end
71
+ end
72
+
73
+ describe "-" do
74
+ it "subtracts a scalar number" do
75
+ assert_equal([-1, 0], @point - 2)
76
+ end
77
+
78
+ it "subtracts a point" do
79
+ assert_equal([-1, -2], @point - @point * 2)
80
+ end
81
+
82
+ it "interprets an array as point" do
83
+ assert_equal([-1, -1], @point - [2, 3])
84
+ end
85
+
86
+ it "fails if the argument class is invalid" do
87
+ assert_raises(ArgumentError) { @point - "str" }
88
+ end
89
+ end
90
+
91
+ describe "*" do
92
+ it "multiplies a scalar number" do
93
+ assert_equal([5, 10], @point * 5)
94
+ end
95
+
96
+ it "performs the dot product with another point" do
97
+ assert_equal(5, @point * @point)
98
+ end
99
+
100
+ it "interprets an array as point" do
101
+ assert_equal(8, @point * [2, 3])
102
+ end
103
+
104
+ it "fails if the argument class is invalid" do
105
+ assert_raises(ArgumentError) { @point * "str" }
106
+ end
107
+ end
108
+
109
+ it "has a dot product which is just the multiplication" do
110
+ assert_equal(@point * [2, 3], @point.dot([2, 3]))
111
+ end
112
+
113
+ it "has a wedge product" do
114
+ assert_equal(-1, @point.wedge([2, 3]))
115
+ end
116
+
117
+ describe "/" do
118
+ it "divides by a scalar number" do
119
+ assert_equal([0.5, 1], @point / 2)
120
+ end
121
+
122
+ it "fails if the argument class is invalid" do
123
+ assert_raises(ArgumentError) { @point / "str" }
124
+ end
125
+ end
126
+
127
+ describe "==" do
128
+ it "compares with a point" do
129
+ assert(@point == Geom2D::Point(1, 2))
130
+ end
131
+
132
+ it "interpretes an array as point" do
133
+ assert(@point == [1, 2])
134
+ end
135
+
136
+ it "returns false for objects with incompatible classes" do
137
+ refute(@point == 5)
138
+ end
139
+ end
140
+
141
+ it "destructures like an array" do
142
+ assert_equal(@point, Geom2D::Point(*@point))
143
+ end
144
+
145
+ it "returns a useful inspection string" do
146
+ assert_equal("(1, 2)", @point.inspect)
147
+ end
148
+ end
@@ -0,0 +1,69 @@
1
+ # -*- frozen_string_literal: true -*-
2
+
3
+ require 'test_helper'
4
+ require 'geom2d/polygon'
5
+
6
+ describe Geom2D::Polygon do
7
+ before do
8
+ @vertices = [[0, 0], [1, 0], [1, 1], [0, 1]]
9
+ @polygon = Geom2D::Polygon(*@vertices)
10
+ end
11
+
12
+ it "creates a polygon using multiple point-like objects" do
13
+ polygon = Geom2D::Polygon(*@vertices)
14
+ assert_equal(@vertices, polygon.to_a)
15
+ end
16
+
17
+ it "returns one for the number of contours" do
18
+ assert_equal(1, @polygon.nr_of_contours)
19
+ end
20
+
21
+ it "returns the number of vertices" do
22
+ assert_equal(4, @polygon.nr_of_vertices)
23
+ end
24
+
25
+ it "returns the i-th vertex" do
26
+ assert_equal([1, 1], @polygon[2])
27
+ end
28
+
29
+ it "enumerates the vertices" do
30
+ assert_equal(@vertices, @polygon.each_vertex.to_a)
31
+ end
32
+
33
+ it "allows adding points to the end" do
34
+ polygon = Geom2D::Polygon.new
35
+ polygon << [0, 0] << [1, 0]
36
+ assert_equal([[0, 0], [1, 0]], polygon.to_a)
37
+ end
38
+
39
+ it "allows removing the last point" do
40
+ @polygon.pop
41
+ assert_equal([[0, 0], [1, 0], [1, 1]], @polygon.to_a)
42
+ end
43
+
44
+ it "iterates over each segment" do
45
+ segments = [Geom2D::Segment([0, 0], [1, 0]), Geom2D::Segment([1, 0], [1, 1]),
46
+ Geom2D::Segment([1, 1], [0, 1]), Geom2D::Segment([0, 1], [0, 0])]
47
+ assert_equal(segments, @polygon.each_segment.to_a)
48
+ end
49
+
50
+ it "returns the bounding box" do
51
+ assert_equal([5, 5, 10, 10], Geom2D::Polygon([5, 5], [10, 5], [10, 10]).bbox.to_a)
52
+ assert_equal([0, 0, 0, 0], Geom2D::Polygon().bbox.to_a)
53
+ end
54
+
55
+ it "returns whether the polygon's vertices are counterclockwise ordered" do
56
+ assert(@polygon.ccw?)
57
+ @polygon.reverse!
58
+ refute(@polygon.ccw?)
59
+ end
60
+
61
+ it "reverses the vertex list" do
62
+ @polygon.reverse!
63
+ assert_equal([[0, 1], [1, 1], [1, 0], [0, 0]], @polygon.to_a)
64
+ end
65
+
66
+ it "returns a useful inspection string" do
67
+ assert_equal("Polygon[(0, 0), (1, 0), (1, 1), (0, 1)]", @polygon.inspect)
68
+ end
69
+ end
@@ -0,0 +1,41 @@
1
+ # -*- frozen_string_literal: true -*-
2
+
3
+ require 'test_helper'
4
+ require 'geom2d/polygon_set'
5
+
6
+ describe Geom2D::PolygonSet do
7
+ before do
8
+ @polygon = Geom2D::Polygon([0, 0], [1, 0], [1, 1], [0, 1])
9
+ @ps = Geom2D::PolygonSet(@polygon)
10
+ @polygon2 = Geom2D::Polygon([10, 10], [10, 15], [15, 10])
11
+ end
12
+
13
+ it "allows adding polygons" do
14
+ @ps << @polygon2
15
+ assert_equal(2, @ps.nr_of_contours)
16
+ end
17
+
18
+ it "allows joining another polygon set" do
19
+ other = Geom2D::PolygonSet(@polygon2)
20
+ result = @ps + other
21
+ assert_equal(2, result.nr_of_contours)
22
+ end
23
+
24
+ it "iterates over each segment" do
25
+ @ps << @polygon2
26
+ segments = [Geom2D::Segment([0, 0], [1, 0]), Geom2D::Segment([1, 0], [1, 1]),
27
+ Geom2D::Segment([1, 1], [0, 1]), Geom2D::Segment([0, 1], [0, 0]),
28
+ Geom2D::Segment([10, 10], [10, 15]), Geom2D::Segment([10, 15], [15, 10]),
29
+ Geom2D::Segment([15, 10], [10, 10])]
30
+ assert_equal(segments, @ps.each_segment.to_a)
31
+ end
32
+
33
+ it "returns the bounding box" do
34
+ assert_equal([0, 0, 1, 1], @polygon.bbox.to_a)
35
+ assert_equal([0, 0, 0, 0], Geom2D::PolygonSet.new.bbox.to_a)
36
+ end
37
+
38
+ it "returns a useful inspection string" do
39
+ assert_equal("PolygonSet[Polygon[(0, 0), (1, 0), (1, 1), (0, 1)]]", @ps.inspect)
40
+ end
41
+ end
@@ -0,0 +1,253 @@
1
+ # -*- frozen_string_literal: true -*-
2
+
3
+ require 'test_helper'
4
+ require 'geom2d/segment'
5
+
6
+ describe Geom2D::Segment do
7
+ before do
8
+ @point = Geom2D::Point(1, 2)
9
+ @line = Geom2D::Segment(@point, [3, 4])
10
+ end
11
+
12
+ describe "Segment method" do
13
+ it "creates a line using two point-like objects" do
14
+ line = Geom2D::Segment(@point, [3, 4])
15
+ assert_equal(@point, line.start_point)
16
+ assert_equal([3, 4], line.end_point)
17
+ end
18
+
19
+ it "creates a line using a point and a vector" do
20
+ line = Geom2D::Segment(@point, vector: [5, 5])
21
+ assert_equal(@point, line.start_point)
22
+ assert_equal(@point + [5, 5], line.end_point)
23
+ end
24
+
25
+ it "fails if only one argument is given" do
26
+ assert_raises(ArgumentError) { Geom2D::Segment(@point) }
27
+ end
28
+ end
29
+
30
+ it "returns the start point" do
31
+ assert_equal(@point, @line.start_point)
32
+ end
33
+
34
+ it "returns the end point" do
35
+ assert_equal([3, 4], @line.end_point)
36
+ end
37
+
38
+ it "checks whether the segment is degenerate, i.e. only a point" do
39
+ refute(@line.degenerate?)
40
+ assert(Geom2D::Segment([5, 5], [5, 5]))
41
+ end
42
+
43
+ it "checks whether the segment is vertical" do
44
+ refute(@line.vertical?)
45
+ assert(Geom2D::Segment([5, 5], [5, 10]).vertical?)
46
+ end
47
+
48
+ it "checks whether the segment is horizontal" do
49
+ refute(@line.horizontal?)
50
+ assert(Geom2D::Segment([5, 5], [10, 5]).horizontal?)
51
+ end
52
+
53
+ it "returns the minimum (left bottom) point" do
54
+ assert_equal(@line.start_point, @line.min)
55
+ @line.reverse!
56
+ assert_equal(@line.end_point, @line.min)
57
+ end
58
+
59
+ it "returns the maximum (right top) point" do
60
+ assert_equal(@line.end_point, @line.max)
61
+ @line.reverse!
62
+ assert_equal(@line.start_point, @line.max)
63
+ end
64
+
65
+ it "returns the length" do
66
+ assert_equal(Math.sqrt(8), @line.length)
67
+ end
68
+
69
+ it "returns the direction" do
70
+ assert_equal([2, 2], @line.direction)
71
+ end
72
+
73
+ it "returns the slope" do
74
+ assert_equal(1, @line.slope)
75
+ end
76
+
77
+ it "returns the y-axes intercept" do
78
+ assert_equal(1, @line.y_intercept)
79
+ end
80
+
81
+ describe "for vertical segments" do
82
+ before do
83
+ @line = Geom2D::Segment([0, 0], [0, 2])
84
+ end
85
+
86
+ it "returns infinity for the slope" do
87
+ assert_equal(Float::INFINITY, @line.slope)
88
+ end
89
+
90
+ it "returns nil for the y-axes intercept" do
91
+ assert_nil(@line.y_intercept)
92
+ end
93
+ end
94
+
95
+ it "reverses the direction of the segment" do
96
+ @line.reverse!
97
+ assert_equal(@point, @line.end_point)
98
+ assert_equal([3, 4], @line.start_point)
99
+ end
100
+
101
+ describe "intersect" do
102
+ def assert_intersection(expected, result)
103
+ case expected
104
+ when nil then assert_nil(expected)
105
+ when Array, Geom2D::Point then assert_equal(expected, result)
106
+ else
107
+ assert_equal(expected.min, result.start_point)
108
+ assert_equal(expected.max, result.end_point)
109
+ end
110
+ end
111
+
112
+ def check_intersection(result, line1, line2)
113
+ assert_intersection(result, line1.intersect(line2))
114
+ assert_intersection(result, line2.intersect(line1))
115
+ line2.reverse!
116
+ assert_intersection(result, line1.intersect(line2))
117
+ assert_intersection(result, line2.intersect(line1))
118
+ line1.reverse!
119
+ assert_intersection(result, line1.intersect(line2))
120
+ assert_intersection(result, line2.intersect(line1))
121
+ line2.reverse!
122
+ assert_intersection(result, line1.intersect(line2))
123
+ assert_intersection(result, line2.intersect(line1))
124
+ end
125
+
126
+ describe "general lines" do
127
+ it "returns nil for non-intersecting lines" do
128
+ line1 = Geom2D::Segment([0, 0], [10, 0])
129
+ line2 = Geom2D::Segment([3, 2], [10, 10])
130
+ check_intersection(nil, line1, line2)
131
+ end
132
+
133
+ it "returns one point for lines with a common endpoint" do
134
+ line1 = Geom2D::Segment([0, 0], [10, 0])
135
+ line2 = Geom2D::Segment([0, 0], [10, 10])
136
+ check_intersection([0, 0], line1, line2)
137
+ end
138
+
139
+ it "returns one point for lines where an endpoint lies inside the other line" do
140
+ line1 = Geom2D::Segment([0, 0], [10, 0])
141
+ line2 = Geom2D::Segment([5, 0], [10, 10])
142
+ check_intersection([5, 0], line1, line2)
143
+ end
144
+
145
+ it "returns one point for general intersection" do
146
+ line1 = Geom2D::Segment([0, 0], [10, 0])
147
+ line2 = Geom2D::Segment([5, 5], [5, -5])
148
+ check_intersection([5, 0], line1, line2)
149
+ end
150
+ end
151
+
152
+ describe "parallel lines" do
153
+ it "returns nil for non collinear lines" do
154
+ line1 = Geom2D::Segment([0, 0], [10, 0])
155
+ line2 = Geom2D::Segment([0, 2], [10, 2])
156
+ check_intersection(nil, line1, line2)
157
+ end
158
+
159
+ it "returns nil for collinear lines with no overlap" do
160
+ line1 = Geom2D::Segment([0, 0], [10, 0])
161
+ line2 = Geom2D::Segment([12, 0], [20, 0])
162
+ check_intersection(nil, line1, line2)
163
+ end
164
+
165
+ it "returns one point for collinear lines which have one common end point" do
166
+ line1 = Geom2D::Segment([0, 0], [10, 0])
167
+ line2 = Geom2D::Segment([10, 0], [20, 0])
168
+ check_intersection([10, 0], line1, line2)
169
+ end
170
+
171
+ it "returns inside segment for lines with no common end/start points" do
172
+ line1 = Geom2D::Segment([0, 0], [10, 0])
173
+ line2 = Geom2D::Segment([5, 0], [8, 0])
174
+ check_intersection(line2, line1, line2)
175
+ end
176
+
177
+ it "returns inside segment for lines with common start points" do
178
+ line1 = Geom2D::Segment([0, 0], [10, 0])
179
+ line2 = Geom2D::Segment([0, 0], [8, 0])
180
+ check_intersection(line2, line1, line2)
181
+ end
182
+
183
+ it "returns inside segment for lines with common end points" do
184
+ line1 = Geom2D::Segment([0, 0], [10, 0])
185
+ line2 = Geom2D::Segment([2, 0], [10, 0])
186
+ check_intersection(line2, line1, line2)
187
+ end
188
+
189
+ it "returns inside segment for identical lines" do
190
+ line1 = Geom2D::Segment([0, 0], [10, 0])
191
+ check_intersection(line1, line1, line1)
192
+ end
193
+
194
+ it "returns overlapping segment for lines with no common end/start points" do
195
+ line1 = Geom2D::Segment([0, 0], [10, 0])
196
+ line2 = Geom2D::Segment([5, 0], [15, 0])
197
+ result = Geom2D::Segment([5, 0], [10, 0])
198
+ check_intersection(result, line1, line2)
199
+ end
200
+ end
201
+ end
202
+
203
+ describe "unary +/-" do
204
+ it "unary plus returns self" do
205
+ assert_same(@line, +@line)
206
+ end
207
+
208
+ it "unary minus returns the line reflected in the origin" do
209
+ reflection = -@line
210
+ assert_equal(-@line.start_point, reflection.start_point)
211
+ assert_equal(-@line.end_point, reflection.end_point)
212
+ end
213
+ end
214
+
215
+ describe "+" do
216
+ it "adds a vector to translate the line" do
217
+ translated = @line + @point
218
+ assert_equal([2, 4], translated.start_point)
219
+ assert_equal([4, 6], translated.end_point)
220
+ end
221
+
222
+ it "fails if the argument class is invalid" do
223
+ assert_raises(ArgumentError) { @line + 5 }
224
+ end
225
+ end
226
+
227
+ describe "-" do
228
+ it "subtracts a vector to translate the line" do
229
+ translated = @line - @point
230
+ assert_equal([0, 0], translated.start_point)
231
+ assert_equal([2, 2], translated.end_point)
232
+ end
233
+
234
+ it "fails if the argument class is invalid" do
235
+ assert_raises(ArgumentError) { @line - 5 }
236
+ end
237
+ end
238
+
239
+ describe "==" do
240
+ it "compares to segments by comparing their endpoints" do
241
+ assert_equal(Geom2D::Segment([0, 0], [0, 1]), Geom2D::Segment([0, 0], [0, 1]))
242
+ refute_equal(Geom2D::Segment([0, 0], [0, 1]), Geom2D::Segment([0, 1], [0, 0]))
243
+ end
244
+
245
+ it "returns false for objects with incompatible classes" do
246
+ refute_equal(@line, @point)
247
+ end
248
+ end
249
+
250
+ it "returns a useful inspection string" do
251
+ assert_equal("Segment[(1, 2)-(3, 4)]", @line.inspect)
252
+ end
253
+ end