geometry-in-ruby 0.0.1

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 (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