geometry-in-ruby 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (51) hide show
  1. data/.gitignore +6 -0
  2. data/Gemfile +7 -0
  3. data/LICENSE +21 -0
  4. data/README.markdown +105 -0
  5. data/Rakefile +24 -0
  6. data/geometry-in-ruby.gemspec +23 -0
  7. data/lib/geometry/arc.rb +94 -0
  8. data/lib/geometry/circle.rb +122 -0
  9. data/lib/geometry/cluster_factory.rb +15 -0
  10. data/lib/geometry/edge.rb +140 -0
  11. data/lib/geometry/line.rb +154 -0
  12. data/lib/geometry/obround.rb +238 -0
  13. data/lib/geometry/path.rb +67 -0
  14. data/lib/geometry/point.rb +163 -0
  15. data/lib/geometry/point_zero.rb +107 -0
  16. data/lib/geometry/polygon.rb +368 -0
  17. data/lib/geometry/polyline.rb +318 -0
  18. data/lib/geometry/rectangle.rb +378 -0
  19. data/lib/geometry/regular_polygon.rb +136 -0
  20. data/lib/geometry/rotation.rb +190 -0
  21. data/lib/geometry/size.rb +75 -0
  22. data/lib/geometry/size_zero.rb +70 -0
  23. data/lib/geometry/square.rb +113 -0
  24. data/lib/geometry/text.rb +24 -0
  25. data/lib/geometry/transformation/composition.rb +39 -0
  26. data/lib/geometry/transformation.rb +171 -0
  27. data/lib/geometry/triangle.rb +78 -0
  28. data/lib/geometry/vector.rb +34 -0
  29. data/lib/geometry.rb +22 -0
  30. data/test/geometry/arc.rb +25 -0
  31. data/test/geometry/circle.rb +112 -0
  32. data/test/geometry/edge.rb +132 -0
  33. data/test/geometry/line.rb +132 -0
  34. data/test/geometry/obround.rb +25 -0
  35. data/test/geometry/path.rb +66 -0
  36. data/test/geometry/point.rb +258 -0
  37. data/test/geometry/point_zero.rb +177 -0
  38. data/test/geometry/polygon.rb +214 -0
  39. data/test/geometry/polyline.rb +266 -0
  40. data/test/geometry/rectangle.rb +154 -0
  41. data/test/geometry/regular_polygon.rb +120 -0
  42. data/test/geometry/rotation.rb +108 -0
  43. data/test/geometry/size.rb +97 -0
  44. data/test/geometry/size_zero.rb +153 -0
  45. data/test/geometry/square.rb +66 -0
  46. data/test/geometry/transformation/composition.rb +49 -0
  47. data/test/geometry/transformation.rb +169 -0
  48. data/test/geometry/triangle.rb +32 -0
  49. data/test/geometry/vector.rb +41 -0
  50. data/test/geometry.rb +5 -0
  51. metadata +117 -0
@@ -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
@@ -0,0 +1,266 @@
1
+ require 'minitest/autorun'
2
+ require 'geometry/polyline'
3
+
4
+ describe Geometry::Polyline do
5
+ Polyline = Geometry::Polyline
6
+
7
+ let(:closed_unit_square) { Polyline.new [0,0], [1,0], [1,1], [0,1], [0,0] }
8
+ let(:unit_square) { Polyline.new [0,0], [1,0], [1,1], [0,1] }
9
+ let(:reverse_unit_square) { Polyline.new [0,1], [1,1], [1,0], [0,0] }
10
+
11
+ it "must create a Polyline object with no arguments" do
12
+ polyline = Geometry::Polyline.new
13
+ polyline.must_be_kind_of Polyline
14
+ polyline.edges.count.must_equal 0
15
+ polyline.vertices.count.must_equal 0
16
+ end
17
+
18
+ it "must create a Polyline object from array arguments" do
19
+ polyline = Geometry::Polyline.new([0,0], [1,0], [1,1], [0,1])
20
+ polyline.must_be_kind_of Polyline
21
+ polyline.edges.count.must_equal 3
22
+ polyline.vertices.count.must_equal 4
23
+ end
24
+
25
+ describe "when the Polyline is closed" do
26
+ let(:closed_concave_polyline) { Polyline.new [-2,0], [0,0], [0,-2], [2,-2], [2,2], [-2,2], [-2,0] }
27
+ subject { closed_concave_polyline }
28
+
29
+ it "must be closed" do
30
+ closed_unit_square.closed?.must_equal true
31
+ end
32
+
33
+ it "must clone and close" do
34
+ closed = subject.close
35
+ closed.closed?.must_equal true
36
+ closed.must_equal subject
37
+ closed.wont_be_same_as subject
38
+ end
39
+
40
+ it "must be able to close itself" do
41
+ subject.close!
42
+ subject.closed?.must_equal true
43
+ subject.must_equal subject
44
+ end
45
+
46
+ it "must clone and reverse" do
47
+ vertices = subject.vertices
48
+ vertices.push vertices.shift
49
+ reversed = subject.reverse
50
+ reversed.vertices.must_equal vertices.reverse
51
+ reversed.wont_be_same_as subject
52
+ reversed.closed?.must_equal true
53
+ end
54
+
55
+ it "must reverse itself" do
56
+ original = subject.vertices.dup
57
+ subject.reverse!
58
+ subject.vertices.to_a.must_equal original.reverse
59
+ subject.closed?.must_equal true
60
+ end
61
+
62
+ it "must generate bisectors" do
63
+ closed_unit_square.bisectors.must_equal [Vector[1, 1], Vector[-1, 1], Vector[-1, -1], Vector[1, -1]]
64
+ end
65
+
66
+ it "must generate bisectors with an inside corner" do
67
+ closed_concave_polyline.bisectors.must_equal [Vector[1,1], Vector[-1,-1], Vector[1,1], Vector[-1,1], Vector[-1,-1], Vector[1,-1]]
68
+ end
69
+
70
+ it "must generate left bisectors" do
71
+ closed_unit_square.left_bisectors.must_equal [Vector[1, 1], Vector[-1, 1], Vector[-1, -1], Vector[1, -1]]
72
+ end
73
+
74
+ it "must generate left bisectors with an inside corner" do
75
+ closed_concave_polyline.left_bisectors.must_equal [Vector[1,1], Vector[1,1], Vector[1,1], Vector[-1,1], Vector[-1,-1], Vector[1,-1]]
76
+ end
77
+
78
+ it "must generate right bisectors" do
79
+ closed_unit_square.right_bisectors.must_equal [Vector[-1,-1], Vector[1,-1], Vector[1,1], Vector[-1,1]]
80
+ end
81
+
82
+ it "must generate right bisectors with an inside corner" do
83
+ closed_concave_polyline.right_bisectors.must_equal [Vector[-1,-1], Vector[-1,-1], Vector[-1,-1], Vector[1,-1], Vector[1,1], Vector[-1,1]]
84
+ end
85
+
86
+ it "must generate spokes" do
87
+ closed_unit_square.spokes.must_equal [Vector[-1,-1], Vector[1,-1], Vector[1,1], Vector[-1,1]]
88
+ end
89
+
90
+ it "must rightset a closed concave polyline where the first outset edge intersects with the last outset edge" do
91
+ skip
92
+ polyline = Polyline.new [0,0], [0,1], [2,1], [2,2], [-1,2], [-1,-1], [2,-1], [2,0], [0,0]
93
+ polyline.offset(-1).must_equal Polyline.new [1, 0], [3, 0], [3, 3], [-2, 3], [-2, -2], [3, -2]
94
+ end
95
+ end
96
+
97
+ describe "when the Polyline is open" do
98
+ let(:concave_polyline) { Polyline.new [-2,0], [0,0], [0,-2], [2,-2], [2,2], [-2,2] }
99
+ subject { concave_polyline }
100
+
101
+ it "must not be closed" do
102
+ unit_square.closed?.must_equal false
103
+ end
104
+
105
+ it "must clone and close" do
106
+ closed = subject.close
107
+ closed.closed?.must_equal true
108
+ closed.must_equal subject
109
+ closed.wont_be_same_as subject
110
+ end
111
+
112
+ it "must be able to close it" do
113
+ closed = subject.close!
114
+ closed.closed?.must_equal true
115
+ closed.must_equal subject
116
+ closed.must_be_same_as subject
117
+ end
118
+
119
+ it "must clone and reverse" do
120
+ reversed = subject.reverse
121
+ reversed.vertices.must_equal subject.vertices.reverse
122
+ reversed.wont_be_same_as subject
123
+ reversed.closed?.wont_equal true
124
+ end
125
+
126
+ it "must reverse itself" do
127
+ original = subject.vertices.dup
128
+ subject.reverse!
129
+ subject.vertices.to_a.must_equal original.reverse
130
+ subject.closed?.wont_equal true
131
+ end
132
+
133
+ it "must generate bisectors" do
134
+ unit_square.bisectors.must_equal [Vector[0, 1], Vector[-1, 1], Vector[-1, -1], Vector[0, -1]]
135
+ end
136
+
137
+ it "must generate bisectors with an inside corner" do
138
+ concave_polyline.bisectors.must_equal [Vector[0,1], Vector[-1,-1], Vector[1,1], Vector[-1,1], Vector[-1,-1], Vector[0,-1]]
139
+ end
140
+
141
+ it "must generate left bisectors" do
142
+ unit_square.left_bisectors.must_equal [Vector[0, 1], Vector[-1, 1], Vector[-1, -1], Vector[0, -1]]
143
+ reverse_unit_square.left_bisectors.must_equal [Vector[0, 1], Vector[1, 1], Vector[1, -1], Vector[0, -1]]
144
+ end
145
+
146
+ it "must generate right bisectors" do
147
+ unit_square.right_bisectors.must_equal [Vector[0,-1], Vector[1,-1], Vector[1,1], Vector[0,1]]
148
+ end
149
+
150
+ it "must generate right bisectors with an inside corner" do
151
+ concave_polyline.right_bisectors.must_equal [Vector[0,-1], Vector[-1,-1], Vector[-1,-1], Vector[1,-1], Vector[1,1], Vector[0,1]]
152
+ end
153
+
154
+ it "must generate left bisectors with an inside corner" do
155
+ concave_polyline.left_bisectors.must_equal [Vector[0,1], Vector[1,1], Vector[1,1], Vector[-1,1], Vector[-1,-1], Vector[0,-1]]
156
+ end
157
+
158
+ it "must generate spokes" do
159
+ unit_square.spokes.must_equal [Vector[0,-1], Vector[1,-1], Vector[1,1], Vector[0,1]]
160
+ end
161
+ end
162
+
163
+ describe "when checking for closedness" do
164
+ it "must be closed when it is closed" do
165
+ closed_unit_square.closed?.must_equal true
166
+ end
167
+
168
+ it "must not be closed when it is not closed" do
169
+ unit_square.closed?.must_equal false
170
+ end
171
+ end
172
+
173
+ describe "when creating a Polyline from an array of Points" do
174
+ it "must ignore repeated Points" do
175
+ polyline = Geometry::Polyline.new([0,0], [1,0], [1,1], [1,1], [0,1])
176
+ polyline.must_be_kind_of Geometry::Polyline
177
+ polyline.must_equal Geometry::Polyline.new([0,0], [1,0], [1,1], [0,1])
178
+ end
179
+
180
+ it "must collapse collinear Edges" do
181
+ polyline = Geometry::Polyline.new([0,0], [1,0], [1,1], [0.5,1], [0,1])
182
+ polyline.must_equal Geometry::Polyline.new([0,0], [1,0], [1,1], [0,1])
183
+ end
184
+
185
+ it "must collapse backtracking Edges" do
186
+ polyline = Geometry::Polyline.new [0,0], [2,0], [2,2], [1,2], [1,1], [1,2], [0,2]
187
+ polyline.must_equal Geometry::Polyline.new([0,0], [2,0], [2,2], [0,2])
188
+ end
189
+ end
190
+
191
+ it "must compare identical polylines as equal" do
192
+ (unit_square.eql? unit_square).must_equal true
193
+ end
194
+
195
+ it "must rightset a closed concave polyline where the first outset edge intersects with the last outset edge" do
196
+ skip
197
+ polyline = Polyline.new [0,0], [0,1], [2,1], [2,2], [-1,2], [-1,-1], [2,-1], [2,0], [0,0]
198
+ polyline.offset(-1).must_equal Polyline.new [1, 0], [3, 0], [3, 3], [-2, 3], [-2, -2], [3, -2]
199
+ end
200
+
201
+ describe "when offsetting" do
202
+ describe "with a positive offset" do
203
+ it "must leftset a unit square" do
204
+ expected_polyline = Polyline.new [0,0.1], [0.9,0.1], [0.9,0.9], [0,0.9]
205
+ unit_square.offset(0.1).must_equal expected_polyline
206
+ end
207
+
208
+ it "must leftset a simple concave polyline" do
209
+ concave_polyline = Polyline.new [0,0], [4,0], [4,4], [3,4], [3,3], [1,3], [1,4], [0,4]
210
+ offset_polyline = concave_polyline.offset(1)
211
+ offset_polyline.must_equal Polyline.new [0,1], [3,1], [3,2], [0,2], [0,3]
212
+ end
213
+ end
214
+
215
+ describe "with a negative offset" do
216
+ it "must rightset a unit square" do
217
+ expected_polyline = Polyline.new [0,-1.0], [2.0,-1.0], [2.0,2.0], [0,2.0]
218
+ unit_square.offset(-1).must_equal expected_polyline
219
+ end
220
+
221
+ it "must rightset a simple concave polyline" do
222
+ concave_polyline = Polyline.new [0,0], [4,0], [4,2], [3,2], [3,1], [1,1], [1,2], [0,2]
223
+ offset_polyline = concave_polyline.offset(-1)
224
+ offset_polyline.must_equal Polyline.new [0,-1], [5,-1], [5,3], [0,3]
225
+ end
226
+
227
+ it "must rightset a concave polyline" do
228
+ concave_polyline = Polyline.new [0,0], [4,0], [4,2], [3,2], [3,1], [1,1], [1,2], [0,2]
229
+ offset_polyline = concave_polyline.offset(-2)
230
+ offset_polyline.must_equal Polyline.new [0,-2], [6,-2], [6,4], [0,4]
231
+ end
232
+
233
+ it "must rightset an asymetric concave polyline" do
234
+ concave_polyline = Polyline.new [0,0], [4,0], [4,3], [3,3], [3,1], [1,1], [1,2], [0,2]
235
+ offset_polyline = concave_polyline.offset(-2)
236
+ offset_polyline.must_equal Polyline.new [0,-2], [6,-2], [6,5], [1,5], [1,4], [0,4]
237
+ end
238
+
239
+ it "must rightset a concave polyline with multiply-intersecting edges" do
240
+ concave_polyline = Polyline.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]
241
+ offset_polyline = concave_polyline.offset(-2)
242
+ offset_polyline.must_equal Polyline.new [0,-2], [7,-2], [7,4], [0,4]
243
+ end
244
+
245
+ it "must rightset a closed concave polyline with multiply-intersecting edges" do
246
+ concave_polyline = Polyline.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], [0,0]
247
+ offset_polyline = concave_polyline.offset(-2)
248
+ offset_polyline.must_equal Polyline.new [-2,-2], [7,-2], [7,4], [-2,4]
249
+ end
250
+
251
+ it "must rightset a concave polyline where the first outset edge intersects with the last outset edge" do
252
+ polyline = Polyline.new [0,0], [0,1], [2,1], [2,2], [-1,2], [-1,-1], [2,-1], [2,0]
253
+ polyline.offset(-1).must_equal Polyline.new [1, 0], [3, 0], [3, 3], [-2, 3], [-2, -2], [3, -2]
254
+ end
255
+
256
+ # Naturally, this test is very sensitive to the input coordinate values. This is a painfully contrived example that
257
+ # checks for sensitivity to edges that are very close to horizontal, but not quite.
258
+ # When the test fails, the first point of the offset polyline is at [0,-1]
259
+ it "must not be sensitive to floating point rounding errors" do
260
+ polyline = Polyline.new [0, 0], [0, -2], [10, -2], [10, 10], [-100, 10], [-100, -22], [-69, -22], [-69, 3.552713678800501e-15], [0,0]
261
+ outset = polyline.offset(-1)
262
+ outset.edges.first.first.must_equal Geometry::Point[-1,-1]
263
+ end
264
+ end
265
+ end
266
+ end
@@ -0,0 +1,154 @@
1
+ require 'minitest/autorun'
2
+ require 'geometry/rectangle'
3
+
4
+ def Rectangle(*args)
5
+ Geometry::Rectangle.new(*args)
6
+ end
7
+
8
+ describe Geometry::Rectangle do
9
+ Rectangle = Geometry::Rectangle
10
+
11
+ describe "when initializing" do
12
+ it "must accept two corners as Arrays" do
13
+ rectangle = Rectangle.new [1,2], [2,3]
14
+ rectangle.must_be_kind_of Geometry::Rectangle
15
+ rectangle.height.must_equal 1
16
+ rectangle.width.must_equal 1
17
+ rectangle.origin.must_equal Point[1,2]
18
+ end
19
+
20
+ it "must accept two named corners as Arrays" do
21
+ rectangle = Rectangle.new from:[1,2], to:[2,3]
22
+ rectangle.must_be_kind_of Geometry::Rectangle
23
+ rectangle.height.must_equal 1
24
+ rectangle.width.must_equal 1
25
+ rectangle.origin.must_equal Point[1,2]
26
+ end
27
+
28
+ it "must accept named center point and size arguments" do
29
+ rectangle = Rectangle.new center:[1,2], size:[3,4]
30
+ rectangle.must_be_kind_of Geometry::Rectangle
31
+ rectangle.height.must_equal 4
32
+ rectangle.width.must_equal 3
33
+ rectangle.center.must_equal Point[1,2]
34
+ end
35
+
36
+ it "must reject a named center argument with no size argument" do
37
+ -> { Rectangle.new center:[1,2] }.must_raise ArgumentError
38
+ end
39
+
40
+ it "must accept named origin point and size arguments" do
41
+ rectangle = Rectangle.new origin:[1,2], size:[3,4]
42
+ rectangle.must_be_kind_of Geometry::Rectangle
43
+ rectangle.height.must_equal 4
44
+ rectangle.width.must_equal 3
45
+ rectangle.origin.must_equal Point[1,2]
46
+ end
47
+
48
+ it "must reject a named origin argument with no size argument" do
49
+ -> { Rectangle.new origin:[1,2] }.must_raise ArgumentError
50
+ end
51
+
52
+ it "must accept a sole named size argument that is an Array" do
53
+ rectangle = Rectangle.new size:[1,2]
54
+ rectangle.must_be_kind_of Geometry::Rectangle
55
+ rectangle.origin.must_equal Point[0,0]
56
+ rectangle.height.must_equal 2
57
+ rectangle.width.must_equal 1
58
+ end
59
+
60
+ it "must accept a sole named size argument that is a Size" do
61
+ rectangle = Rectangle.new size:Size[1,2]
62
+ rectangle.must_be_kind_of Geometry::Rectangle
63
+ rectangle.origin.must_equal Point[0,0]
64
+ rectangle.height.must_equal 2
65
+ rectangle.width.must_equal 1
66
+ end
67
+
68
+ it "must accept named width and height arguments" do
69
+ rectangle = Rectangle.new width:1, height:3
70
+ rectangle.must_be_kind_of Geometry::Rectangle
71
+ rectangle.height.must_equal 3
72
+ rectangle.width.must_equal 1
73
+ end
74
+
75
+ it "must reject width or height by themselves" do
76
+ -> { Rectangle.new height:1 }.must_raise ArgumentError
77
+ -> { Rectangle.new width:1 }.must_raise ArgumentError
78
+ end
79
+ end
80
+
81
+ describe "comparison" do
82
+ it "must compare equal" do
83
+ rectangle = Rectangle [1,2], [3,4]
84
+ rectangle.must_equal Rectangle([1,2], [3, 4])
85
+ end
86
+ end
87
+
88
+ describe "inset" do
89
+ subject { Rectangle.new [0,0], [10,10] }
90
+
91
+ it "must inset equally" do
92
+ subject.inset(1).must_equal Rectangle.new [1,1], [9,9]
93
+ end
94
+
95
+ it "must inset vertically and horizontally" do
96
+ subject.inset(1,2).must_equal Rectangle.new [1,2], [9,8]
97
+ subject.inset(x:1, y:2).must_equal Rectangle.new [1,2], [9,8]
98
+ end
99
+
100
+ it "must inset from individual sides" do
101
+ subject.inset(1,2,3,4).must_equal Rectangle.new [2,3], [6,9]
102
+ subject.inset(top:1, left:2, bottom:3, right:4).must_equal Rectangle.new [2,3], [6,9]
103
+ end
104
+ end
105
+
106
+ describe "properties" do
107
+ subject { Rectangle.new [1,2], [3,4] }
108
+ let(:rectangle) { Rectangle [1,2], [3,4] }
109
+
110
+ it "have a center point property" do
111
+ rectangle.center.must_equal Point[2,3]
112
+ end
113
+
114
+ it "have a width property" do
115
+ assert_equal(2, rectangle.width)
116
+ end
117
+
118
+ it "have a height property" do
119
+ assert_equal(2, rectangle.height)
120
+ end
121
+
122
+ it "have an origin property" do
123
+ assert_equal(Point[1,2], rectangle.origin)
124
+ end
125
+
126
+ it "have an edges property that returns 4 edges" do
127
+ edges = rectangle.edges
128
+ assert_equal(4, edges.size)
129
+ edges.each {|edge| assert_kind_of(Geometry::Edge, edge)}
130
+ end
131
+
132
+ it "have a points property that returns 4 points" do
133
+ points = rectangle.points
134
+ assert_equal(4, points.size)
135
+ points.each {|point| assert_kind_of(Geometry::Point, point)}
136
+ end
137
+
138
+ it "must have a bounds property that returns a Rectangle" do
139
+ subject.bounds.must_equal Rectangle.new([1,2], [3,4])
140
+ end
141
+
142
+ it "must have a minmax property that returns the corners of the bounding rectangle" do
143
+ subject.minmax.must_equal [Point[1,2], Point[3,4]]
144
+ end
145
+
146
+ it "must have a max property that returns the upper right corner of the bounding rectangle" do
147
+ subject.max.must_equal Point[3,4]
148
+ end
149
+
150
+ it "must have a min property that returns the lower left corner of the bounding rectangle" do
151
+ subject.min.must_equal Point[1,2]
152
+ end
153
+ end
154
+ end