geometry 6 → 6.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.
@@ -7,6 +7,7 @@ end
7
7
 
8
8
  describe Geometry::Edge do
9
9
  Edge = Geometry::Edge
10
+ subject { Geometry::Edge.new [0,0], [1,1] }
10
11
 
11
12
  it "must create an Edge object" do
12
13
  edge = Edge.new([0,0], [1,0])
@@ -14,13 +15,7 @@ describe Geometry::Edge do
14
15
  assert_equal(Geometry::Point[0,0], edge.first)
15
16
  assert_equal(Geometry::Point[1,0], edge.last)
16
17
  end
17
- it "must create swap endpoints in place" do
18
- edge = Edge.new([0,0], [1,0])
19
- assert_kind_of(Edge, edge)
20
- edge.reverse!
21
- assert_equal(Geometry::Point[1,0], edge.first)
22
- assert_equal(Geometry::Point[0,0], edge.last)
23
- end
18
+
24
19
  it "must handle equality" do
25
20
  edge1 = Edge.new([1,0], [0,1])
26
21
  edge2 = Edge.new([1,0], [0,1])
@@ -58,6 +53,18 @@ describe Geometry::Edge do
58
53
  Edge.new([0,0], [2,0]).parallel?(Edge.new([1,-1], [1,1])).must_equal false
59
54
  end
60
55
 
56
+ it "must clone and reverse" do
57
+ reversed = subject.reverse
58
+ reversed.to_a.must_equal subject.to_a.reverse
59
+ reversed.wont_be_same_as subject
60
+ end
61
+
62
+ it "must reverse itself" do
63
+ original = subject.to_a
64
+ subject.reverse!
65
+ subject.to_a.must_equal original.reverse
66
+ end
67
+
61
68
  describe "spaceship" do
62
69
  it "ascending with a Point" do
63
70
  edge = Edge.new [0,0], [1,1]
@@ -106,3 +106,27 @@ describe Geometry::Line do
106
106
  assert_equal('Line(Point[0, 0], Point[10, 10])', line.to_s)
107
107
  end
108
108
  end
109
+
110
+ describe Geometry::PointSlopeLine do
111
+ subject { Geometry::PointSlopeLine.new [1,2], 3 }
112
+
113
+ it "must have a slope attribute" do
114
+ subject.slope.must_equal 3
115
+ end
116
+ end
117
+
118
+ describe Geometry::SlopeInterceptLine do
119
+ subject { Geometry::SlopeInterceptLine.new 3, 2 }
120
+
121
+ it "must have a slope attribute" do
122
+ subject.slope.must_equal 3
123
+ end
124
+ end
125
+
126
+ describe Geometry::TwoPointLine do
127
+ subject { Geometry::TwoPointLine.new [1,2], [3,4] }
128
+
129
+ it "must have a slope attribute" do
130
+ subject.slope.must_equal 1
131
+ end
132
+ end
@@ -2,6 +2,18 @@ require 'minitest/autorun'
2
2
  require 'geometry/point'
3
3
 
4
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
+
5
17
  describe "constructor" do
6
18
  it "must return the Point when constructed from a Point" do
7
19
  original_point = Point[3,4]
@@ -19,12 +31,6 @@ describe Geometry::Point do
19
31
  end
20
32
  end
21
33
 
22
- it "create a Point object using list syntax" do
23
- point = Geometry::Point[2,1]
24
- assert_equal(2, point.size)
25
- assert_equal(2, point.x)
26
- assert_equal(1, point.y)
27
- end
28
34
  it "create a Point object from an array" do
29
35
  point = Geometry::Point[[3,4]]
30
36
  assert_equal(2, point.size)
@@ -44,12 +50,6 @@ describe Geometry::Point do
44
50
  assert_equal(4, point.y)
45
51
  end
46
52
 
47
- it "create a Point object from a Vector using list syntax" do
48
- point = Geometry::Point[Vector[3,4]]
49
- assert_equal(2, point.size)
50
- assert_equal(3, point.x)
51
- assert_equal(4, point.y)
52
- end
53
53
  it "create a Point object from a Point using list syntax" do
54
54
  point = Geometry::Point[Geometry::Point[13,14]]
55
55
  assert_equal(2, point.size)
@@ -85,6 +85,16 @@ describe Geometry::Point do
85
85
  Point[1,2][2].must_equal nil
86
86
  end
87
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
+
88
98
  describe "arithmetic" do
89
99
  let(:left) { Point[1,2] }
90
100
  let(:right) { Point[3,4] }
@@ -122,6 +132,13 @@ describe Geometry::Point do
122
132
  (Vector[5,6] + right).must_equal Vector[8,10]
123
133
  end
124
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
125
142
  end
126
143
 
127
144
  describe "when subtracting" do
@@ -141,16 +158,40 @@ describe Geometry::Point do
141
158
  it "must raise an exception when subtracting mismatched sizes" do
142
159
  lambda { left - [1,2,3,4] }.must_raise Geometry::DimensionMismatch
143
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
144
175
  end
145
176
  end
146
177
 
147
178
  describe "coercion" do
179
+ subject { Point[1,2] }
180
+
148
181
  it "must coerce Arrays into Points" do
149
- Point[1,2].coerce([3,4]).must_equal [Point[3,4], Point[1,2]]
182
+ subject.coerce([3,4]).must_equal [Point[3,4], subject]
150
183
  end
151
184
 
152
185
  it "must coerce Vectors into Points" do
153
- Point[1,2].coerce(Vector[3,4]).must_equal [Point[3,4], Point[1,2]]
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
154
195
  end
155
196
  end
156
197
 
@@ -6,6 +6,8 @@ describe Geometry::Polygon do
6
6
 
7
7
  let(:cw_unit_square) { Polygon.new [0,0], [0,1], [1,1], [1,0] }
8
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 }
9
11
 
10
12
  it "must create a Polygon object with no arguments" do
11
13
  polygon = Geometry::Polygon.new
@@ -46,8 +48,7 @@ describe Geometry::Polygon do
46
48
  end
47
49
 
48
50
  it "must create closed polygons" do
49
- polygon = Geometry::Polygon.new([0,0], [1,0], [1,1], [0,1])
50
- assert_equal(polygon.edges.first.first, polygon.edges.last.last)
51
+ subject.closed?.must_equal true
51
52
  end
52
53
 
53
54
  it "must handle already closed polygons" do
@@ -58,13 +59,20 @@ describe Geometry::Polygon do
58
59
  assert_equal(polygon.edges.first.first, polygon.edges.last.last)
59
60
  end
60
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
+
61
69
  describe "orientation" do
62
70
  it "must return true for clockwise" do
63
71
  Polygon.new([0,0], [0,1], [1,1], [1,0]).clockwise?.must_equal true
64
72
  Polygon.new([1,1], [1,3], [3,3], [3,1]).clockwise?.must_equal true
65
73
  end
66
74
 
67
- it "must return fale for counterclockwise" do
75
+ it "must return false for counterclockwise" do
68
76
  Polygon.new([0,0], [1,0], [1,1], [0,1]).clockwise?.must_equal false
69
77
  Polygon.new([1,1], [3,1], [3,3], [1,3]).clockwise?.must_equal false
70
78
  end
@@ -102,6 +110,12 @@ describe Geometry::Polygon do
102
110
  convex_hull.vertices.must_equal [[0,0], [0,1], [2,1], [2,0], [1,-1]].map {|a| Point[*a]}
103
111
  end
104
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
+
105
119
  describe "spaceship" do
106
120
  it "with a Point" do
107
121
  (unit_square <=> Point[2,0]).must_equal -1
@@ -140,10 +154,9 @@ describe Geometry::Polygon do
140
154
  end
141
155
 
142
156
  it "must outset a concave polygon with multiply-intersecting edges" do
143
- skip
144
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]
145
- outset_polygon = concave_polygon.outset(2)
146
- outset_polygon.must_equal Polygon.new [-2,-2], [7,-2], [7,4], [-2,4]
158
+ outset_polygon = concave_polygon.outset(1)
159
+ outset_polygon.must_equal Polygon.new [-1,-1], [6,-1], [6,3], [-1,3]
147
160
  end
148
161
 
149
162
  it "must outset a concave polygon where the first outset edge intersects with the last outset edge" do
@@ -151,6 +164,15 @@ describe Geometry::Polygon do
151
164
  polygon.edges.count.must_equal 8
152
165
  polygon.outset(1).must_equal Polygon.new [3, 0], [3, 3], [-2, 3], [-2, -2], [3, -2]
153
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
154
176
  end
155
177
 
156
178
  describe "set operations" do
@@ -4,7 +4,9 @@ require 'geometry/polyline'
4
4
  describe Geometry::Polyline do
5
5
  Polyline = Geometry::Polyline
6
6
 
7
+ let(:closed_unit_square) { Polyline.new [0,0], [1,0], [1,1], [0,1], [0,0] }
7
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] }
8
10
 
9
11
  it "must create a Polyline object with no arguments" do
10
12
  polyline = Geometry::Polyline.new
@@ -20,6 +22,154 @@ describe Geometry::Polyline do
20
22
  polyline.vertices.count.must_equal 4
21
23
  end
22
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
+
23
173
  describe "when creating a Polyline from an array of Points" do
24
174
  it "must ignore repeated Points" do
25
175
  polyline = Geometry::Polyline.new([0,0], [1,0], [1,1], [1,1], [0,1])
@@ -42,6 +192,12 @@ describe Geometry::Polyline do
42
192
  (unit_square.eql? unit_square).must_equal true
43
193
  end
44
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
+
45
201
  describe "when offsetting" do
46
202
  describe "with a positive offset" do
47
203
  it "must leftset a unit square" do
@@ -85,6 +241,26 @@ describe Geometry::Polyline do
85
241
  offset_polyline = concave_polyline.offset(-2)
86
242
  offset_polyline.must_equal Polyline.new [0,-2], [7,-2], [7,4], [0,4]
87
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
88
264
  end
89
265
  end
90
266
  end