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.
Files changed (52) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +6 -0
  3. data/Gemfile +7 -0
  4. data/LICENSE +21 -0
  5. data/README.markdown +105 -0
  6. data/Rakefile +24 -0
  7. data/aurora-geometry.gemspec +23 -0
  8. data/lib/geometry.rb +22 -0
  9. data/lib/geometry/arc.rb +94 -0
  10. data/lib/geometry/circle.rb +122 -0
  11. data/lib/geometry/cluster_factory.rb +15 -0
  12. data/lib/geometry/edge.rb +140 -0
  13. data/lib/geometry/line.rb +154 -0
  14. data/lib/geometry/obround.rb +238 -0
  15. data/lib/geometry/path.rb +67 -0
  16. data/lib/geometry/point.rb +163 -0
  17. data/lib/geometry/point_zero.rb +107 -0
  18. data/lib/geometry/polygon.rb +368 -0
  19. data/lib/geometry/polyline.rb +318 -0
  20. data/lib/geometry/rectangle.rb +378 -0
  21. data/lib/geometry/regular_polygon.rb +136 -0
  22. data/lib/geometry/rotation.rb +190 -0
  23. data/lib/geometry/size.rb +75 -0
  24. data/lib/geometry/size_zero.rb +70 -0
  25. data/lib/geometry/square.rb +113 -0
  26. data/lib/geometry/text.rb +24 -0
  27. data/lib/geometry/transformation.rb +171 -0
  28. data/lib/geometry/transformation/composition.rb +39 -0
  29. data/lib/geometry/triangle.rb +78 -0
  30. data/lib/geometry/vector.rb +34 -0
  31. data/test/geometry.rb +5 -0
  32. data/test/geometry/arc.rb +25 -0
  33. data/test/geometry/circle.rb +112 -0
  34. data/test/geometry/edge.rb +132 -0
  35. data/test/geometry/line.rb +132 -0
  36. data/test/geometry/obround.rb +25 -0
  37. data/test/geometry/path.rb +66 -0
  38. data/test/geometry/point.rb +258 -0
  39. data/test/geometry/point_zero.rb +177 -0
  40. data/test/geometry/polygon.rb +214 -0
  41. data/test/geometry/polyline.rb +266 -0
  42. data/test/geometry/rectangle.rb +154 -0
  43. data/test/geometry/regular_polygon.rb +120 -0
  44. data/test/geometry/rotation.rb +108 -0
  45. data/test/geometry/size.rb +97 -0
  46. data/test/geometry/size_zero.rb +153 -0
  47. data/test/geometry/square.rb +66 -0
  48. data/test/geometry/transformation.rb +169 -0
  49. data/test/geometry/transformation/composition.rb +49 -0
  50. data/test/geometry/triangle.rb +32 -0
  51. data/test/geometry/vector.rb +41 -0
  52. 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