aurora-geometry 0.0.2
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.
- checksums.yaml +7 -0
- data/.gitignore +6 -0
- data/Gemfile +7 -0
- data/LICENSE +21 -0
- data/README.markdown +105 -0
- data/Rakefile +24 -0
- data/aurora-geometry.gemspec +23 -0
- data/lib/geometry.rb +22 -0
- data/lib/geometry/arc.rb +94 -0
- data/lib/geometry/circle.rb +122 -0
- data/lib/geometry/cluster_factory.rb +15 -0
- data/lib/geometry/edge.rb +140 -0
- data/lib/geometry/line.rb +154 -0
- data/lib/geometry/obround.rb +238 -0
- data/lib/geometry/path.rb +67 -0
- data/lib/geometry/point.rb +163 -0
- data/lib/geometry/point_zero.rb +107 -0
- data/lib/geometry/polygon.rb +368 -0
- data/lib/geometry/polyline.rb +318 -0
- data/lib/geometry/rectangle.rb +378 -0
- data/lib/geometry/regular_polygon.rb +136 -0
- data/lib/geometry/rotation.rb +190 -0
- data/lib/geometry/size.rb +75 -0
- data/lib/geometry/size_zero.rb +70 -0
- data/lib/geometry/square.rb +113 -0
- data/lib/geometry/text.rb +24 -0
- data/lib/geometry/transformation.rb +171 -0
- data/lib/geometry/transformation/composition.rb +39 -0
- data/lib/geometry/triangle.rb +78 -0
- data/lib/geometry/vector.rb +34 -0
- data/test/geometry.rb +5 -0
- data/test/geometry/arc.rb +25 -0
- data/test/geometry/circle.rb +112 -0
- data/test/geometry/edge.rb +132 -0
- data/test/geometry/line.rb +132 -0
- data/test/geometry/obround.rb +25 -0
- data/test/geometry/path.rb +66 -0
- data/test/geometry/point.rb +258 -0
- data/test/geometry/point_zero.rb +177 -0
- data/test/geometry/polygon.rb +214 -0
- data/test/geometry/polyline.rb +266 -0
- data/test/geometry/rectangle.rb +154 -0
- data/test/geometry/regular_polygon.rb +120 -0
- data/test/geometry/rotation.rb +108 -0
- data/test/geometry/size.rb +97 -0
- data/test/geometry/size_zero.rb +153 -0
- data/test/geometry/square.rb +66 -0
- data/test/geometry/transformation.rb +169 -0
- data/test/geometry/transformation/composition.rb +49 -0
- data/test/geometry/triangle.rb +32 -0
- data/test/geometry/vector.rb +41 -0
- metadata +115 -0
@@ -0,0 +1,258 @@
|
|
1
|
+
require 'minitest/autorun'
|
2
|
+
require 'geometry/point'
|
3
|
+
|
4
|
+
describe Geometry::Point do
|
5
|
+
PointZero = Geometry::PointZero
|
6
|
+
|
7
|
+
describe "class methods" do
|
8
|
+
it "must generate a PointZero" do
|
9
|
+
Point.zero.must_be_instance_of(PointZero)
|
10
|
+
end
|
11
|
+
|
12
|
+
it "must generate a Point full of zeros" do
|
13
|
+
Point.zero(3).must_equal Point[0,0,0]
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
describe "constructor" do
|
18
|
+
it "must return the Point when constructed from a Point" do
|
19
|
+
original_point = Point[3,4]
|
20
|
+
point = Geometry::Point[original_point]
|
21
|
+
point.must_be_same_as original_point
|
22
|
+
point.size.must_equal 2
|
23
|
+
point.x.must_equal 3
|
24
|
+
point.y.must_equal 4
|
25
|
+
end
|
26
|
+
|
27
|
+
it "must return the PointZero when constructed from a PointZero" do
|
28
|
+
original_point = Geometry::PointZero.new
|
29
|
+
point = Geometry::Point[original_point]
|
30
|
+
point.must_be_same_as original_point
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
it "create a Point object from an array" do
|
35
|
+
point = Geometry::Point[[3,4]]
|
36
|
+
assert_equal(2, point.size)
|
37
|
+
assert_equal(3, point.x)
|
38
|
+
assert_equal(4, point.y)
|
39
|
+
end
|
40
|
+
it "create a Point object from individual parameters" do
|
41
|
+
point = Geometry::Point[3,4]
|
42
|
+
assert_equal(2, point.size)
|
43
|
+
assert_equal(3, point.x)
|
44
|
+
assert_equal(4, point.y)
|
45
|
+
end
|
46
|
+
it "create a Point object from a Vector" do
|
47
|
+
point = Geometry::Point[Vector[3,4]]
|
48
|
+
assert_equal(2, point.size)
|
49
|
+
assert_equal(3, point.x)
|
50
|
+
assert_equal(4, point.y)
|
51
|
+
end
|
52
|
+
|
53
|
+
it "create a Point object from a Point using list syntax" do
|
54
|
+
point = Geometry::Point[Geometry::Point[13,14]]
|
55
|
+
assert_equal(2, point.size)
|
56
|
+
assert_equal(13, point.x)
|
57
|
+
assert_equal(14, point.y)
|
58
|
+
end
|
59
|
+
it "allow indexed element access" do
|
60
|
+
point = Geometry::Point[5,6]
|
61
|
+
assert_equal(2, point.size)
|
62
|
+
assert_equal(5, point[0])
|
63
|
+
assert_equal(6, point[1])
|
64
|
+
end
|
65
|
+
it "allow named element access" do
|
66
|
+
point = Geometry::Point[5,6,7]
|
67
|
+
assert_equal(3, point.size)
|
68
|
+
assert_equal(5, point.x)
|
69
|
+
assert_equal(6, point.y)
|
70
|
+
assert_equal(7, point.z)
|
71
|
+
end
|
72
|
+
|
73
|
+
it "implement inspect" do
|
74
|
+
point = Geometry::Point[8,9]
|
75
|
+
assert_equal('Point[8, 9]', point.inspect)
|
76
|
+
end
|
77
|
+
it "implement to_s" do
|
78
|
+
point = Geometry::Point[10,11]
|
79
|
+
assert_equal('Point[10, 11]', point.to_s)
|
80
|
+
end
|
81
|
+
|
82
|
+
it "must support array access" do
|
83
|
+
Point[1,2][0].must_equal 1
|
84
|
+
Point[1,2][1].must_equal 2
|
85
|
+
Point[1,2][2].must_equal nil
|
86
|
+
end
|
87
|
+
|
88
|
+
it "must clone" do
|
89
|
+
Point[1,2].clone.must_be_instance_of(Point)
|
90
|
+
Point[1,2].clone.must_equal Point[1,2]
|
91
|
+
end
|
92
|
+
|
93
|
+
it "must duplicate" do
|
94
|
+
Point[1,2].dup.must_be_instance_of(Point)
|
95
|
+
Point[1,2].dup.must_equal Point[1,2]
|
96
|
+
end
|
97
|
+
|
98
|
+
describe "arithmetic" do
|
99
|
+
let(:left) { Point[1,2] }
|
100
|
+
let(:right) { Point[3,4] }
|
101
|
+
|
102
|
+
it "must have +@" do
|
103
|
+
(+left).must_equal Point[1,2]
|
104
|
+
(+left).must_be_instance_of(Point)
|
105
|
+
end
|
106
|
+
|
107
|
+
it "must have unary negation" do
|
108
|
+
(-left).must_equal Point[-1,-2]
|
109
|
+
(-left).must_be_instance_of(Point)
|
110
|
+
end
|
111
|
+
|
112
|
+
describe "when adding" do
|
113
|
+
it "return a Point when adding two Points" do
|
114
|
+
assert_kind_of(Point, left+right)
|
115
|
+
end
|
116
|
+
|
117
|
+
it "must return a Point when adding an array to a Point" do
|
118
|
+
(left + [5,6]).must_equal Point[6,8]
|
119
|
+
end
|
120
|
+
|
121
|
+
it "must add a Numeric to all elements" do
|
122
|
+
(left + 2).must_equal Point[3,4]
|
123
|
+
(2 + left).must_equal Point[3,4]
|
124
|
+
end
|
125
|
+
|
126
|
+
it "must raise an exception when adding mismatched sizes" do
|
127
|
+
lambda { left + [1,2,3,4] }.must_raise Geometry::DimensionMismatch
|
128
|
+
end
|
129
|
+
|
130
|
+
it "must return a Point when adding a Vector" do
|
131
|
+
(left + Vector[5,6]).must_equal Point[6,8]
|
132
|
+
(Vector[5,6] + right).must_equal Vector[8,10]
|
133
|
+
end
|
134
|
+
|
135
|
+
it "must return self when adding a PointZero" do
|
136
|
+
(left + Point.zero).must_equal left
|
137
|
+
end
|
138
|
+
|
139
|
+
it "must return self when adding a NilClass" do
|
140
|
+
(left + nil).must_equal left
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
describe "when subtracting" do
|
145
|
+
it "return a Point when subtracting two Points" do
|
146
|
+
assert_kind_of(Point, left-right)
|
147
|
+
end
|
148
|
+
|
149
|
+
it "must return a Point when subtracting an array from a Point" do
|
150
|
+
(left - [5,6]).must_equal Point[-4, -4]
|
151
|
+
end
|
152
|
+
|
153
|
+
it "must subtract a Numeric from all elements" do
|
154
|
+
(left - 2).must_equal Point[-1, 0]
|
155
|
+
(2 - left).must_equal Point[1,0]
|
156
|
+
end
|
157
|
+
|
158
|
+
it "must raise an exception when subtracting mismatched sizes" do
|
159
|
+
lambda { left - [1,2,3,4] }.must_raise Geometry::DimensionMismatch
|
160
|
+
end
|
161
|
+
|
162
|
+
it "must return self when subtracting a PointZero" do
|
163
|
+
(left - Point.zero).must_equal left
|
164
|
+
end
|
165
|
+
|
166
|
+
it "must return self when subtracting a NilClass" do
|
167
|
+
(left - nil).must_equal left
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
describe "when multiplying" do
|
172
|
+
it "must return a Point when multiplied by a Matrix" do
|
173
|
+
(Matrix[[1,2],[3,4]]*Point[5,6]).must_equal Point[17, 39]
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
describe "coercion" do
|
179
|
+
subject { Point[1,2] }
|
180
|
+
|
181
|
+
it "must coerce Arrays into Points" do
|
182
|
+
subject.coerce([3,4]).must_equal [Point[3,4], subject]
|
183
|
+
end
|
184
|
+
|
185
|
+
it "must coerce Vectors into Points" do
|
186
|
+
subject.coerce(Vector[3,4]).must_equal [Point[3,4], subject]
|
187
|
+
end
|
188
|
+
|
189
|
+
it "must coerce a Numeric into a Point" do
|
190
|
+
subject.coerce(42).must_equal [Point[42,42], subject]
|
191
|
+
end
|
192
|
+
|
193
|
+
it "must reject anything that can't be coerced" do
|
194
|
+
-> { subject.coerce(NilClass) }.must_raise TypeError
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
describe "comparison" do
|
199
|
+
let(:point) { Point[1,2] }
|
200
|
+
|
201
|
+
it "must compare equal to an equal Array" do
|
202
|
+
point.must_be :==, [1,2]
|
203
|
+
point.must_be :eql?, [1,2]
|
204
|
+
[1,2].must_equal point
|
205
|
+
end
|
206
|
+
|
207
|
+
it "must not compare equal to an unequal Array" do
|
208
|
+
point.wont_equal [3,2]
|
209
|
+
[3,2].wont_equal point
|
210
|
+
end
|
211
|
+
|
212
|
+
it "must compare equal to an equal Point" do
|
213
|
+
point.must_be :==, Point[1,2]
|
214
|
+
point.must_be :eql?, Point[1,2]
|
215
|
+
Point[1,2].must_equal point
|
216
|
+
end
|
217
|
+
|
218
|
+
it "must not compare equal to an unequal Point" do
|
219
|
+
point.wont_equal Point[3,2]
|
220
|
+
Point[3,2].wont_equal point
|
221
|
+
end
|
222
|
+
|
223
|
+
it "must compare equal to an equal Vector" do
|
224
|
+
point.must_equal Vector[1,2]
|
225
|
+
Vector[1,2].must_equal point
|
226
|
+
end
|
227
|
+
|
228
|
+
it "must not compare equal to an unequal Vector" do
|
229
|
+
point.wont_equal Vector[3,2]
|
230
|
+
Vector[3,2].wont_equal point
|
231
|
+
end
|
232
|
+
|
233
|
+
it "must think that floats == ints" do
|
234
|
+
Point[1,2].must_be :==, Point[1.0,2.0]
|
235
|
+
Point[1.0,2.0].must_be :==, Point[1,2]
|
236
|
+
end
|
237
|
+
|
238
|
+
it "must not think that floats eql? ints" do
|
239
|
+
Point[1,2].wont_be :eql?, Point[1.0,2.0]
|
240
|
+
Point[1.0,2.0].wont_be :eql?, Point[1,2]
|
241
|
+
end
|
242
|
+
|
243
|
+
describe "spaceship" do
|
244
|
+
it "must spaceship with another Point of the same length" do
|
245
|
+
(Point[1,2] <=> Point[0,3]).must_equal Point[1,-1]
|
246
|
+
end
|
247
|
+
|
248
|
+
it "must spaceship with another Point of different length" do
|
249
|
+
(Point[1,2] <=> Point[0,3,4]).must_equal Point[1,-1]
|
250
|
+
(Point[1,2,4] <=> Point[0,3]).must_equal Point[1,-1]
|
251
|
+
end
|
252
|
+
|
253
|
+
it "must spaceship with an Array" do
|
254
|
+
(Point[1,2] <=> [0,3]).must_equal Point[1,-1]
|
255
|
+
end
|
256
|
+
end
|
257
|
+
end
|
258
|
+
end
|
@@ -0,0 +1,177 @@
|
|
1
|
+
require 'minitest/autorun'
|
2
|
+
require 'geometry/point_zero'
|
3
|
+
|
4
|
+
describe Geometry::PointZero do
|
5
|
+
let(:zero) { Geometry::PointZero.new }
|
6
|
+
|
7
|
+
describe "arithmetic" do
|
8
|
+
let(:left) { Point[1,2] }
|
9
|
+
let(:right) { Point[3,4] }
|
10
|
+
|
11
|
+
it "must have +@" do
|
12
|
+
(+zero).must_be :eql?, 0
|
13
|
+
(+zero).must_be_instance_of(Geometry::PointZero)
|
14
|
+
end
|
15
|
+
|
16
|
+
it "must have unary negation" do
|
17
|
+
(-zero).must_be :eql?, 0
|
18
|
+
(-zero).must_be_instance_of(Geometry::PointZero)
|
19
|
+
end
|
20
|
+
|
21
|
+
describe "Accessors" do
|
22
|
+
it "must return 0 for array access" do
|
23
|
+
zero[3].must_equal 0
|
24
|
+
end
|
25
|
+
|
26
|
+
it "must return 0 for named element access" do
|
27
|
+
zero.x.must_equal 0
|
28
|
+
zero.y.must_equal 0
|
29
|
+
zero.z.must_equal 0
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
describe "when adding" do
|
34
|
+
it "must return a number" do
|
35
|
+
(zero + 3).must_equal 3
|
36
|
+
(3 + zero).must_equal 3
|
37
|
+
end
|
38
|
+
|
39
|
+
it "return a Point when adding two Points" do
|
40
|
+
(zero + right).must_be_kind_of Point
|
41
|
+
(left + zero).must_be_kind_of Point
|
42
|
+
end
|
43
|
+
|
44
|
+
it "must return an Array when adding an array" do
|
45
|
+
(zero + [5,6]).must_equal [5,6]
|
46
|
+
# ([5,6] + zero).must_equal [5,6]
|
47
|
+
end
|
48
|
+
|
49
|
+
it "must return a Point when adding a Size" do
|
50
|
+
(zero + Size[5,6]).must_be_instance_of(Point)
|
51
|
+
(zero + Size[5,6]).must_equal Point[5,6]
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
describe "when subtracting" do
|
56
|
+
it "must return a number" do
|
57
|
+
(zero - 3).must_equal -3
|
58
|
+
(3 - zero).must_equal 3
|
59
|
+
end
|
60
|
+
|
61
|
+
it "return a Point when subtracting two Points" do
|
62
|
+
(zero - right).must_equal Point[-3,-4]
|
63
|
+
(left - zero).must_equal Point[1,2]
|
64
|
+
end
|
65
|
+
|
66
|
+
it "must return a Point when subtracting an array" do
|
67
|
+
(zero - [5,6]).must_equal [-5, -6]
|
68
|
+
# ([5,6] - zero).must_equal [5,6]
|
69
|
+
end
|
70
|
+
|
71
|
+
it "must return a Point when subtracting a Size" do
|
72
|
+
(zero - Size[5,6]).must_be_instance_of(Point)
|
73
|
+
(zero - Size[5,6]).must_equal Point[-5,-6]
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
describe "multiplication" do
|
78
|
+
it "must return 0 for scalars" do
|
79
|
+
(zero * 3).must_equal 0
|
80
|
+
(zero * 3.0).must_equal 0.0
|
81
|
+
end
|
82
|
+
|
83
|
+
it "must return 0 for Points" do
|
84
|
+
(zero * Point[1,2]).must_equal 0
|
85
|
+
end
|
86
|
+
|
87
|
+
it "must return 0 for Vectors" do
|
88
|
+
(zero * Vector[2,3]).must_equal 0
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
describe "division" do
|
93
|
+
it "must return 0 for non-zero scalars" do
|
94
|
+
(zero / 3).must_equal 0
|
95
|
+
(zero / 4.0).must_equal 0
|
96
|
+
end
|
97
|
+
|
98
|
+
it "must raise an exception when divided by 0" do
|
99
|
+
lambda { zero / 0 }.must_raise ZeroDivisionError
|
100
|
+
end
|
101
|
+
|
102
|
+
it "must raise an exception for Points" do
|
103
|
+
lambda { zero / Point[1,2] }.must_raise Geometry::OperationNotDefined
|
104
|
+
end
|
105
|
+
|
106
|
+
it "must raise an exception for Vectors" do
|
107
|
+
lambda { zero / Vector[1,2] }.must_raise Geometry::OperationNotDefined
|
108
|
+
end
|
109
|
+
|
110
|
+
end
|
111
|
+
|
112
|
+
end
|
113
|
+
|
114
|
+
describe "coercion" do
|
115
|
+
it "must coerce Arrays into Points" do
|
116
|
+
zero.coerce([3,4]).must_equal [Point[3,4], Point[0,0]]
|
117
|
+
end
|
118
|
+
|
119
|
+
it "must coerce Vectors into Vectors" do
|
120
|
+
zero.coerce(Vector[3,4]).must_equal [Vector[3,4], Vector[0,0]]
|
121
|
+
end
|
122
|
+
|
123
|
+
it "must coerce Points into Points" do
|
124
|
+
zero.coerce(Point[5,6]).must_equal [Point[5,6], Point[0,0]]
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
describe "comparison" do
|
129
|
+
subject { Geometry::PointZero.new }
|
130
|
+
|
131
|
+
it "must be equal to 0 and 0.0" do
|
132
|
+
zero.must_be :eql?, 0
|
133
|
+
zero.must_be :eql?, 0.0
|
134
|
+
end
|
135
|
+
|
136
|
+
it "must not be equal to a non-zero number" do
|
137
|
+
1.wont_equal zero
|
138
|
+
3.14.wont_equal zero
|
139
|
+
end
|
140
|
+
|
141
|
+
it "must be equal to an Array of zeros" do
|
142
|
+
zero.must_be :==, [0,0]
|
143
|
+
zero.must_be :eql?, [0,0]
|
144
|
+
zero.must_be :===, [0,0]
|
145
|
+
[0,0].must_equal zero
|
146
|
+
subject.must_equal [0,0]
|
147
|
+
end
|
148
|
+
|
149
|
+
it "must not be equal to a non-zero Array" do
|
150
|
+
zero.wont_equal [3,2]
|
151
|
+
[3,2].wont_equal zero
|
152
|
+
end
|
153
|
+
|
154
|
+
it "must be equal to a Point at the origin" do
|
155
|
+
zero.must_be :==, Point[0,0]
|
156
|
+
zero.must_be :eql?, Point[0,0]
|
157
|
+
zero.must_be :===, Point[0,0]
|
158
|
+
Point[0,0].must_equal zero
|
159
|
+
subject.must_equal Point[0,0]
|
160
|
+
end
|
161
|
+
|
162
|
+
it "must not be equal to a Point not at the origin" do
|
163
|
+
zero.wont_equal Point[3,2]
|
164
|
+
Point[3,2].wont_equal zero
|
165
|
+
end
|
166
|
+
|
167
|
+
it "must be equal to an Vector of zeroes" do
|
168
|
+
zero.must_be :eql?, Vector[0,0]
|
169
|
+
Vector[0,0].must_equal zero
|
170
|
+
end
|
171
|
+
|
172
|
+
it "must not be equal to a non-zero Vector" do
|
173
|
+
zero.wont_equal Vector[3,2]
|
174
|
+
Vector[3,2].wont_equal zero
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|
@@ -0,0 +1,214 @@
|
|
1
|
+
require 'minitest/autorun'
|
2
|
+
require 'geometry/polygon'
|
3
|
+
|
4
|
+
describe Geometry::Polygon do
|
5
|
+
Polygon = Geometry::Polygon
|
6
|
+
|
7
|
+
let(:cw_unit_square) { Polygon.new [0,0], [0,1], [1,1], [1,0] }
|
8
|
+
let(:unit_square) { Polygon.new [0,0], [1,0], [1,1], [0,1] }
|
9
|
+
let(:simple_concave) { Polygon.new [0,0], [4,0], [4,2], [3,2], [3,1], [1,1], [1,2], [0,2] }
|
10
|
+
subject { unit_square }
|
11
|
+
|
12
|
+
it "must create a Polygon object with no arguments" do
|
13
|
+
polygon = Geometry::Polygon.new
|
14
|
+
assert_kind_of(Geometry::Polygon, polygon)
|
15
|
+
assert_equal(0, polygon.edges.size)
|
16
|
+
assert_equal(0, polygon.vertices.size)
|
17
|
+
end
|
18
|
+
|
19
|
+
it "must create a Polygon object from array arguments" do
|
20
|
+
polygon = Geometry::Polygon.new([0,0], [1,0], [1,1], [0,1])
|
21
|
+
assert_kind_of(Geometry::Polygon, polygon)
|
22
|
+
assert_equal(4, polygon.edges.size)
|
23
|
+
assert_equal(4, polygon.vertices.size)
|
24
|
+
end
|
25
|
+
|
26
|
+
describe "when creating a Polygon from an array of Points" do
|
27
|
+
it "must ignore repeated Points" do
|
28
|
+
polygon = Geometry::Polygon.new([0,0], [1,0], [1,1], [1,1], [0,1])
|
29
|
+
polygon.must_be_kind_of Geometry::Polygon
|
30
|
+
polygon.edges.size.must_equal 4
|
31
|
+
polygon.vertices.size.must_equal 4
|
32
|
+
polygon.must_equal Geometry::Polygon.new([0,0], [1,0], [1,1], [0,1])
|
33
|
+
end
|
34
|
+
|
35
|
+
it "must collapse collinear Edges" do
|
36
|
+
polygon = Geometry::Polygon.new([0,0], [1,0], [1,1], [0.5,1], [0,1])
|
37
|
+
polygon.must_equal Geometry::Polygon.new([0,0], [1,0], [1,1], [0,1])
|
38
|
+
end
|
39
|
+
|
40
|
+
it "must collapse backtracking Edges" do
|
41
|
+
polygon = Geometry::Polygon.new [0,0], [2,0], [2,2], [1,2], [1,1], [1,2], [0,2]
|
42
|
+
polygon.must_equal Geometry::Polygon.new([0,0], [2,0], [2,2], [0,2])
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
it "must compare identical polygons as equal" do
|
47
|
+
(unit_square.eql? unit_square).must_equal true
|
48
|
+
end
|
49
|
+
|
50
|
+
it "must create closed polygons" do
|
51
|
+
subject.closed?.must_equal true
|
52
|
+
end
|
53
|
+
|
54
|
+
it "must handle already closed polygons" do
|
55
|
+
polygon = Geometry::Polygon.new([0,0], [1,0], [1,1], [0,1], [0,0])
|
56
|
+
assert_kind_of(Geometry::Polygon, polygon)
|
57
|
+
assert_equal(4, polygon.edges.size)
|
58
|
+
assert_equal(4, polygon.vertices.size)
|
59
|
+
assert_equal(polygon.edges.first.first, polygon.edges.last.last)
|
60
|
+
end
|
61
|
+
|
62
|
+
it "must return itself on close" do
|
63
|
+
closed = subject.close
|
64
|
+
closed.closed?.must_equal true
|
65
|
+
closed.must_equal subject
|
66
|
+
closed.must_be_same_as subject
|
67
|
+
end
|
68
|
+
|
69
|
+
describe "orientation" do
|
70
|
+
it "must return true for clockwise" do
|
71
|
+
Polygon.new([0,0], [0,1], [1,1], [1,0]).clockwise?.must_equal true
|
72
|
+
Polygon.new([1,1], [1,3], [3,3], [3,1]).clockwise?.must_equal true
|
73
|
+
end
|
74
|
+
|
75
|
+
it "must return false for counterclockwise" do
|
76
|
+
Polygon.new([0,0], [1,0], [1,1], [0,1]).clockwise?.must_equal false
|
77
|
+
Polygon.new([1,1], [3,1], [3,3], [1,3]).clockwise?.must_equal false
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
it "must gift wrap a square polygon" do
|
82
|
+
polygon = Polygon.new [0,0], [1,0], [1,1], [0,1]
|
83
|
+
convex_hull = polygon.wrap
|
84
|
+
convex_hull.must_be_kind_of Geometry::Polygon
|
85
|
+
convex_hull.edges.size.must_equal 4
|
86
|
+
convex_hull.vertices.must_equal [[0,0], [0,1], [1,1], [1,0]].map {|a| Point[*a]}
|
87
|
+
end
|
88
|
+
|
89
|
+
it "must gift wrap another square polygon" do
|
90
|
+
polygon = Polygon.new [0,1], [0,0], [1,0], [1,1]
|
91
|
+
convex_hull = polygon.wrap
|
92
|
+
convex_hull.must_be_kind_of Geometry::Polygon
|
93
|
+
convex_hull.edges.size.must_equal 4
|
94
|
+
convex_hull.vertices.must_equal [[0,0], [0,1], [1,1], [1,0]].map {|a| Point[*a]}
|
95
|
+
end
|
96
|
+
|
97
|
+
it "must gift wrap a concave polygon" do
|
98
|
+
polygon = Polygon.new [0,0], [1,-1], [2,0], [1,1], [2,2], [0,1]
|
99
|
+
convex_hull = polygon.wrap
|
100
|
+
convex_hull.must_be_kind_of Geometry::Polygon
|
101
|
+
convex_hull.edges.size.must_equal 5
|
102
|
+
convex_hull.vertices.must_equal [Point[0, 0], Point[0, 1], Point[2, 2], Point[2, 0], Point[1, -1]]
|
103
|
+
end
|
104
|
+
|
105
|
+
it "must gift wrap a polygon" do
|
106
|
+
polygon = Polygon.new [0,0], [1,-1], [2,0], [2,1], [0,1]
|
107
|
+
convex_hull = polygon.wrap
|
108
|
+
convex_hull.must_be_kind_of Geometry::Polygon
|
109
|
+
convex_hull.edges.size.must_equal 5
|
110
|
+
convex_hull.vertices.must_equal [[0,0], [0,1], [2,1], [2,0], [1,-1]].map {|a| Point[*a]}
|
111
|
+
end
|
112
|
+
|
113
|
+
it "must generate spokes" do
|
114
|
+
unit_square.spokes.must_equal [Vector[-1,-1], Vector[1,-1], Vector[1,1], Vector[-1,1]]
|
115
|
+
cw_unit_square.spokes.must_equal [Vector[-1,-1], Vector[-1,1], Vector[1,1], Vector[1,-1]]
|
116
|
+
simple_concave.spokes.must_equal [Vector[-1,-1], Vector[1,-1], Vector[1,1], Vector[-1,1], Vector[-1,1], Vector[1,1], Vector[1,1], Vector[-1,1]]
|
117
|
+
end
|
118
|
+
|
119
|
+
describe "spaceship" do
|
120
|
+
it "with a Point" do
|
121
|
+
(unit_square <=> Point[2,0]).must_equal -1
|
122
|
+
(unit_square <=> Point[1,0]).must_equal 0
|
123
|
+
(unit_square <=> Point[0.5,0.5]).must_equal 1
|
124
|
+
end
|
125
|
+
|
126
|
+
it "with a Point that lies on a horizontal edge" do
|
127
|
+
(unit_square <=> Point[0.5,0]).must_equal 0
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
describe "when outsetting" do
|
132
|
+
it "must outset a unit square" do
|
133
|
+
outset_polygon = unit_square.outset(1)
|
134
|
+
expected_polygon = Polygon.new [-1.0,-1.0], [2.0,-1.0], [2.0,2.0], [-1.0,2.0]
|
135
|
+
outset_polygon.must_equal expected_polygon
|
136
|
+
end
|
137
|
+
|
138
|
+
it "must outset a simple concave polygon" do
|
139
|
+
concave_polygon = Polygon.new [0,0], [4,0], [4,2], [3,2], [3,1], [1,1], [1,2], [0,2]
|
140
|
+
outset_polygon = concave_polygon.outset(1)
|
141
|
+
outset_polygon.must_equal Polygon.new [-1,-1], [5,-1], [5,3], [-1,3]
|
142
|
+
end
|
143
|
+
|
144
|
+
it "must outset a concave polygon" do
|
145
|
+
concave_polygon = Polygon.new [0,0], [4,0], [4,2], [3,2], [3,1], [1,1], [1,2], [0,2]
|
146
|
+
outset_polygon = concave_polygon.outset(2)
|
147
|
+
outset_polygon.must_equal Polygon.new [-2,-2], [6,-2], [6,4], [-2,4]
|
148
|
+
end
|
149
|
+
|
150
|
+
it "must outset an asymetric concave polygon" do
|
151
|
+
concave_polygon = Polygon.new [0,0], [4,0], [4,3], [3,3], [3,1], [1,1], [1,2], [0,2]
|
152
|
+
outset_polygon = concave_polygon.outset(2)
|
153
|
+
outset_polygon.must_equal Polygon.new [-2,-2], [6,-2], [6,5], [1,5], [1,4], [-2,4]
|
154
|
+
end
|
155
|
+
|
156
|
+
it "must outset a concave polygon with multiply-intersecting edges" do
|
157
|
+
concave_polygon = Polygon.new [0,0], [5,0], [5,2], [4,2], [4,1], [3,1], [3,2], [2,2], [2,1], [1,1], [1,2], [0,2]
|
158
|
+
outset_polygon = concave_polygon.outset(1)
|
159
|
+
outset_polygon.must_equal Polygon.new [-1,-1], [6,-1], [6,3], [-1,3]
|
160
|
+
end
|
161
|
+
|
162
|
+
it "must outset a concave polygon where the first outset edge intersects with the last outset edge" do
|
163
|
+
polygon = Polygon.new [0,0], [0,1], [2,1], [2,2], [-1,2], [-1,-1], [2,-1], [2,0]
|
164
|
+
polygon.edges.count.must_equal 8
|
165
|
+
polygon.outset(1).must_equal Polygon.new [3, 0], [3, 3], [-2, 3], [-2, -2], [3, -2]
|
166
|
+
end
|
167
|
+
|
168
|
+
# Naturally, this test is very sensitive to the input coordinate values. This is a painfully contrived example that
|
169
|
+
# checks for sensitivity to edges that are very close to horizontal, but not quite.
|
170
|
+
# When the test fails, the first point of the offset polygon is at [0,-1]
|
171
|
+
it "must not be sensitive to floating point rounding errors" do
|
172
|
+
polygon = Polygon.new [0, 0], [0, -2], [10, -2], [10, 10], [-100, 10], [-100, -22], [-69, -22], [-69, 3.552713678800501e-15], [0,0]
|
173
|
+
outset = polygon.outset(1)
|
174
|
+
outset.edges.first.first.must_equal Geometry::Point[-1,-1]
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
describe "set operations" do
|
179
|
+
describe "union" do
|
180
|
+
it "must union two adjacent squares" do
|
181
|
+
polygonA = Polygon.new [0,0], [1,0], [1,1], [0,1]
|
182
|
+
polygonB = Polygon.new [1,0], [2,0], [2,1], [1,1]
|
183
|
+
(polygonA.union polygonB).must_equal Polygon.new [0,0], [2,0], [2,1], [0,1]
|
184
|
+
(polygonA + polygonB).must_equal Polygon.new [0,0], [2,0], [2,1], [0,1]
|
185
|
+
end
|
186
|
+
|
187
|
+
it "must union two overlapping squares" do
|
188
|
+
polygonA = Polygon.new [0,0], [2,0], [2,2], [0,2]
|
189
|
+
polygonB = Polygon.new [1,1], [3,1], [3,3], [1,3]
|
190
|
+
expected_polygon = Polygon.new [0,0], [2,0], [2,1], [3,1], [3,3], [1,3], [1,2], [0,2]
|
191
|
+
union = polygonA.union polygonB
|
192
|
+
union.must_be_kind_of Polygon
|
193
|
+
union.must_equal expected_polygon
|
194
|
+
end
|
195
|
+
|
196
|
+
it "must union two overlapping clockwise squares" do
|
197
|
+
polygonA = Polygon.new [0,0], [0,2], [2,2], [2,0]
|
198
|
+
polygonB = Polygon.new [1,1], [1,3], [3,3], [3,1]
|
199
|
+
expected_polygon = Polygon.new [0, 0], [0, 2], [1, 2], [1, 3], [3, 3], [3, 1], [2, 1], [2, 0]
|
200
|
+
union = polygonA.union polygonB
|
201
|
+
union.must_be_kind_of Polygon
|
202
|
+
union.must_equal expected_polygon
|
203
|
+
end
|
204
|
+
|
205
|
+
it "must union two overlapping squares with collinear edges" do
|
206
|
+
polygonA = Polygon.new [0,0], [2,0], [2,2], [0,2]
|
207
|
+
polygonB = Polygon.new [1,0], [3,0], [3,2], [1,2]
|
208
|
+
union = polygonA + polygonB
|
209
|
+
union.must_be_kind_of Polygon
|
210
|
+
union.must_equal Polygon.new [0,0], [3,0], [3,2], [0,2]
|
211
|
+
end
|
212
|
+
end
|
213
|
+
end
|
214
|
+
end
|