geom2d 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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