geometry 6.4 → 6.6

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 (44) hide show
  1. checksums.yaml +5 -5
  2. data/.github/workflows/ruby.yml +21 -0
  3. data/Gemfile +3 -0
  4. data/README.markdown +1 -2
  5. data/geometry.gemspec +5 -1
  6. data/lib/geometry/annulus.rb +20 -2
  7. data/lib/geometry/arc.rb +72 -1
  8. data/lib/geometry/edge.rb +21 -4
  9. data/lib/geometry/line.rb +84 -18
  10. data/lib/geometry/obround.rb +1 -2
  11. data/lib/geometry/path.rb +27 -0
  12. data/lib/geometry/point.rb +59 -8
  13. data/lib/geometry/point_iso.rb +12 -2
  14. data/lib/geometry/point_one.rb +44 -34
  15. data/lib/geometry/point_zero.rb +12 -1
  16. data/lib/geometry/polygon.rb +13 -12
  17. data/lib/geometry/polyline.rb +6 -6
  18. data/lib/geometry/rectangle.rb +12 -12
  19. data/lib/geometry/rotation.rb +5 -3
  20. data/lib/geometry/size.rb +1 -3
  21. data/lib/geometry/square.rb +13 -11
  22. data/lib/geometry/transformation/composition.rb +1 -1
  23. data/lib/geometry/transformation.rb +15 -0
  24. data/lib/geometry/triangle.rb +1 -1
  25. data/test/geometry/annulus.rb +12 -0
  26. data/test/geometry/arc.rb +98 -0
  27. data/test/geometry/circle.rb +3 -3
  28. data/test/geometry/edge.rb +16 -2
  29. data/test/geometry/line.rb +73 -0
  30. data/test/geometry/path.rb +16 -0
  31. data/test/geometry/point.rb +78 -2
  32. data/test/geometry/point_iso.rb +31 -21
  33. data/test/geometry/point_one.rb +11 -1
  34. data/test/geometry/point_zero.rb +46 -36
  35. data/test/geometry/polygon.rb +1 -1
  36. data/test/geometry/rectangle.rb +6 -1
  37. data/test/geometry/rotation.rb +1 -1
  38. data/test/geometry/size_one.rb +1 -1
  39. data/test/geometry/size_zero.rb +1 -1
  40. data/test/geometry/square.rb +15 -10
  41. data/test/geometry/transformation.rb +15 -1
  42. data/test/geometry/vector.rb +1 -1
  43. metadata +36 -9
  44. data/.travis.yml +0 -12
@@ -1,5 +1,3 @@
1
- require_relative 'point'
2
-
3
1
  module Geometry
4
2
  =begin rdoc
5
3
  An object repesenting a {Point} that is one unit away from the origin, along each
@@ -67,43 +65,55 @@ everything else, regardless of size. It's similar to the
67
65
  end
68
66
  # @endgroup
69
67
 
70
- # @override max()
71
- # @return [Number] The maximum value of the {Point}'s elements
72
- # @override max(point)
73
- # @return [Point] The element-wise maximum values of the receiver and the given {Point}
74
- def max(*args)
75
- if args.empty?
76
- 1
77
- else
78
- args = args.first if 1 == args.size
79
- Point[Array.new(args.size, 1).zip(args).map(&:max)]
68
+ # @override max()
69
+ # @return [Number] The maximum value of the {Point}'s elements
70
+ # @override max(point)
71
+ # @return [Point] The element-wise maximum values of the receiver and the given {Point}
72
+ def max(*args)
73
+ if args.empty?
74
+ 1
75
+ else
76
+ args = args.first if 1 == args.size
77
+ Point[Array.new(args.size, 1).zip(args).map(&:max)]
78
+ end
80
79
  end
81
- end
82
80
 
83
- # @override min()
84
- # @return [Number] The minimum value of the {Point}'s elements
85
- # @override min(point)
86
- # @return [Point] The element-wise minimum values of the receiver and the given {Point}
87
- def min(*args)
88
- if args.empty?
89
- 1
90
- else
91
- args = args.first if 1 == args.size
92
- Point[Array.new(args.size, 1).zip(args).map(&:min)]
81
+ # @override min()
82
+ # @return [Number] The minimum value of the {Point}'s elements
83
+ # @override min(point)
84
+ # @return [Point] The element-wise minimum values of the receiver and the given {Point}
85
+ def min(*args)
86
+ if args.empty?
87
+ 1
88
+ else
89
+ args = args.first if 1 == args.size
90
+ Point[Array.new(args.size, 1).zip(args).map(&:min)]
91
+ end
93
92
  end
94
- end
95
93
 
96
- # @override minmax()
97
- # @return [Array<Number>] The minimum value of the {Point}'s elements
98
- # @override min(point)
99
- # @return [Array<Point>] The element-wise minimum values of the receiver and the given {Point}
100
- def minmax(*args)
101
- if args.empty?
102
- [1, 1]
103
- else
104
- [min(*args), max(*args)]
94
+ # @override minmax()
95
+ # @return [Array<Number>] The minimum value of the {Point}'s elements
96
+ # @override min(point)
97
+ # @return [Array<Point>] The element-wise minimum values of the receiver and the given {Point}
98
+ def minmax(*args)
99
+ if args.empty?
100
+ [1, 1]
101
+ else
102
+ [min(*args), max(*args)]
103
+ end
104
+ end
105
+
106
+ # Returns a new {Point} with the given number of elements removed from the end
107
+ # @return [Point] the popped elements
108
+ def pop(count=1)
109
+ Point[Array.new(count, 1)]
110
+ end
111
+
112
+ # Removes the first element and returns it
113
+ # @return [Point] the shifted elements
114
+ def shift(count=1)
115
+ Point[Array.new(count, 1)]
105
116
  end
106
- end
107
117
 
108
118
  # @group Arithmetic
109
119
 
@@ -1,4 +1,3 @@
1
- require_relative 'point'
2
1
  require_relative 'point_iso'
3
2
 
4
3
  module Geometry
@@ -105,6 +104,18 @@ everything else, regardless of size. You can think of it as an application of th
105
104
  end
106
105
  end
107
106
 
107
+ # Returns a new {Point} with the given number of elements removed from the end
108
+ # @return [Point] the popped elements
109
+ def pop(count=1)
110
+ Point[Array.new(count, 0)]
111
+ end
112
+
113
+ # Removes the first element and returns it
114
+ # @return [Point] the shifted elements
115
+ def shift(count=1)
116
+ Point[Array.new(count, 0)]
117
+ end
118
+
108
119
  # @group Arithmetic
109
120
 
110
121
  # @group Unary operators
@@ -46,7 +46,7 @@ but there's currently nothing that enforces simplicity.
46
46
 
47
47
  # @return [Polygon] A new {Polygon} with orientation that's the opposite of the receiver
48
48
  def reverse
49
- self.class.new *(self.vertices.reverse)
49
+ self.class.new(*(self.vertices.reverse))
50
50
  end
51
51
 
52
52
  # Reverse the receiver and return it
@@ -68,7 +68,7 @@ but there's currently nothing that enforces simplicity.
68
68
  # @param [Point] point The {Point} to test
69
69
  # @return [Number] 1 if the {Point} is inside the {Polygon}, -1 if it's outside, and 0 if it's on an {Edge}
70
70
  def <=>(point)
71
- sum = edges.reduce(0) do |sum, e|
71
+ winding = edges.reduce(0) do |sum, e|
72
72
  direction = e.last.y <=> e.first.y
73
73
  # Ignore edges that don't cross the point's x coordinate
74
74
  next sum unless ((point.y <=> e.last.y) + (point.y <=> e.first.y)).abs <= 1
@@ -83,7 +83,7 @@ but there's currently nothing that enforces simplicity.
83
83
  sum += 0 <=> (direction + is_left)
84
84
  end
85
85
  end
86
- (0 == sum) ? -1 : 1
86
+ (0 == winding) ? -1 : 1
87
87
  end
88
88
 
89
89
  # Create a new {Polygon} that's the union of the receiver and a passed {Polygon}
@@ -152,11 +152,11 @@ but there's currently nothing that enforces simplicity.
152
152
  edgeFragments = edgeFragments.reject {|f| edgeFragments.find {|f2| (f[:first] == f2[:last]) and (f[:last] == f2[:first])} }
153
153
 
154
154
  # Construct the output polygons
155
- output = edgeFragments.reduce([Array.new]) do |output, fragment|
155
+ output_polygons = edgeFragments.reduce([Array.new]) do |output, fragment|
156
156
  next output if fragment.empty?
157
157
  polygon = output.last
158
158
  polygon.push fragment[:first], fragment[:last] if polygon.empty?
159
- while 1 do
159
+ loop do
160
160
  adjacent_fragment = edgeFragments.find {|f| fragment[:last] == f[:first]}
161
161
  break unless adjacent_fragment
162
162
 
@@ -170,13 +170,13 @@ but there's currently nothing that enforces simplicity.
170
170
  end
171
171
 
172
172
  # If everything worked properly there should be only one output Polygon
173
- output.reject! {|a| a.empty?}
174
- output = Polygon.new *(output[0])
173
+ output_polygons.reject! {|a| a.empty?}
174
+ output_polygons = Polygon.new(*(output_polygons[0]))
175
175
 
176
176
  # Table 4: Both input polygons are "island" type and the operation
177
177
  # is union, so the output polygon's orientation should be the same
178
178
  # as the input polygon's orientation
179
- (self.clockwise? != output.clockwise?) ? output.reverse : output
179
+ (self.clockwise? != output_polygons.clockwise?) ? output_polygons.reverse : output_polygons
180
180
  end
181
181
  alias :+ :union
182
182
 
@@ -213,7 +213,7 @@ but there's currently nothing that enforces simplicity.
213
213
  break if current_point == hull_points.first
214
214
  hull_points << min_point
215
215
  end
216
- Polygon.new *hull_points
216
+ Polygon.new(*hull_points)
217
217
  end
218
218
 
219
219
  # @endgroup
@@ -278,7 +278,7 @@ but there's currently nothing that enforces simplicity.
278
278
  redo # Recheck the modified edges
279
279
  end
280
280
  end
281
- Polygon.new *(active_edges.map {|e| e[:edge]}.compact.map {|e| [e.first, e.last]}.flatten)
281
+ Polygon.new(*(active_edges.map {|e| e[:edge]}.compact.map {|e| [e.first, e.last]}.flatten))
282
282
  end
283
283
 
284
284
  # Vertex bisectors suitable for outsetting
@@ -332,9 +332,10 @@ but there's currently nothing that enforces simplicity.
332
332
  # @param [Integer] index The index to insert the new {Point} before
333
333
  # @param [Point] point The {Point} to insert
334
334
  # @param [Integer] type The vertex type: 1 is inside, 0 is boundary, -1 is outside
335
+ # @return [Bool] true if the {Point} was inserted, false otherwise
335
336
  def insert(index, point, type)
336
- if v = @vertices.find {|v| v[:vertex] == point }
337
- v[:type] = type
337
+ if vertex = @vertices.find {|v| v[:vertex] == point }
338
+ vertex[:type] = type
338
339
  false
339
340
  else
340
341
  @vertices.insert(index, {:vertex => point, :type => type})
@@ -41,7 +41,7 @@ also like a {Path} in that it isn't necessarily closed.
41
41
  @vertices.push first
42
42
  elsif first.is_a?(Edge)
43
43
  @edges.push first
44
- @vertices.push *(first.to_a)
44
+ @vertices.push(*(first.to_a))
45
45
  end
46
46
 
47
47
  args.reduce(@vertices.last) do |previous,n|
@@ -82,7 +82,7 @@ also like a {Path} in that it isn't necessarily closed.
82
82
  else
83
83
  e = Edge.new(previous, n.first)
84
84
  push_edge e, n
85
- push_vertex *(e.to_a), *(n.to_a)
85
+ push_vertex(*(e.to_a), *(n.to_a))
86
86
  end
87
87
  n.last
88
88
  end
@@ -167,7 +167,7 @@ also like a {Path} in that it isn't necessarily closed.
167
167
  # Clone the receiver, reverse it, then return it
168
168
  # @return [Polyline] the reversed clone
169
169
  def reverse
170
- self.class.new *(edges.reverse.map! {|edge| edge.reverse! })
170
+ self.class.new(*(edges.reverse.map! {|edge| edge.reverse! }))
171
171
  end
172
172
 
173
173
  # Reverse the receiver and return it
@@ -261,7 +261,7 @@ also like a {Path} in that it isn't necessarily closed.
261
261
  redo # Recheck the modified edges
262
262
  end
263
263
  end
264
- Polyline.new *(active_edges.map {|e| e[:edge]}.compact.map {|e| [e.first, e.last]}.flatten)
264
+ Polyline.new(*(active_edges.map {|e| e[:edge]}.compact.map {|e| [e.first, e.last]}.flatten))
265
265
  end
266
266
  alias :leftset :offset
267
267
 
@@ -369,12 +369,12 @@ also like a {Path} in that it isn't necessarily closed.
369
369
  end
370
370
 
371
371
  def push_edge(*e)
372
- @edges.push *e
372
+ @edges.push(*e)
373
373
  @edges.uniq!
374
374
  end
375
375
 
376
376
  def push_vertex(*v)
377
- @vertices.push *v
377
+ @vertices.push(*v)
378
378
  @vertices.uniq!
379
379
  end
380
380
 
@@ -25,16 +25,8 @@ The {Rectangle} class cluster represents your typical arrangement of 4 corners a
25
25
  class Rectangle
26
26
  include ClusterFactory
27
27
 
28
- # @return [Point] The {Rectangle}'s center
29
- attr_reader :center
30
- # @return [Number] Height of the {Rectangle}
31
- attr_reader :height
32
- # @return [Point] The {Rectangle}'s origin
33
- attr_reader :origin
34
28
  # @return [Size] The {Size} of the {Rectangle}
35
29
  attr_reader :size
36
- # @return [Number] Width of the {Rectangle}
37
- attr_reader :width
38
30
 
39
31
  # @overload new(width, height)
40
32
  # Creates a {Rectangle} of the given width and height, centered on the origin
@@ -120,7 +112,7 @@ The {Rectangle} class cluster represents your typical arrangement of 4 corners a
120
112
  # @return [Point] The {Rectangle}'s center
121
113
  def center
122
114
  min, max = @points.minmax {|a,b| a.y <=> b.y}
123
- Point[(max.x+min.x)/2, (max.y+min.y)/2]
115
+ Point[(max.x+min.x).to_r/2, (max.y+min.y).to_r/2]
124
116
  end
125
117
 
126
118
  # @!attribute closed?
@@ -155,25 +147,28 @@ The {Rectangle} class cluster represents your typical arrangement of 4 corners a
155
147
  [self.min, self.max]
156
148
  end
157
149
 
158
- # @return [Array<Point>] The {Rectangle}'s four points (counterclockwise)
150
+ # @return [Array<Point>] The {Rectangle}'s four points (clockwise)
159
151
  def points
160
152
  point0, point2 = *@points
161
- point1 = Point[point2.x, point0.y]
162
- point3 = Point[point0.x, point2.y]
153
+ point1 = Point[point0.x, point2.y]
154
+ point3 = Point[point2.x, point0.y]
163
155
  [point0, point1, point2, point3]
164
156
  end
165
157
 
158
+ # @return [Point] The {Rectangle}'s origin
166
159
  def origin
167
160
  minx = @points.min {|a,b| a.x <=> b.x}
168
161
  miny = @points.min {|a,b| a.y <=> b.y}
169
162
  Point[minx.x, miny.y]
170
163
  end
171
164
 
165
+ # @return [Number] Height of the {Rectangle}
172
166
  def height
173
167
  min, max = @points.minmax {|a,b| a.y <=> b.y}
174
168
  max.y - min.y
175
169
  end
176
170
 
171
+ # @return [Number] Width of the {Rectangle}
177
172
  def width
178
173
  min, max = @points.minmax {|a,b| a.x <=> b.x}
179
174
  max.x - min.x
@@ -212,6 +207,11 @@ The {Rectangle} class cluster represents your typical arrangement of 4 corners a
212
207
  Rectangle.new from:(min + Point[options[:left], options[:bottom]]), to:(max - Point[options[:right], options[:top]])
213
208
  end
214
209
  end
210
+
211
+ # @return [Path] A closed {Path} that traces the boundary of the {Rectangle} clockwise, starting from the lower-left
212
+ def path
213
+ Path.new(*self.points, self.points.first)
214
+ end
215
215
  end
216
216
 
217
217
  class CenteredRectangle < Rectangle
@@ -72,16 +72,18 @@ A generalized representation of a rotation transformation.
72
72
  alias :== :eql?
73
73
 
74
74
  def identity?
75
- (!@x && !@y && !@z) || ([@x, @y, @z].select {|a| a}.all? {|a| a.respond_to?(:magnitude) ? (1 == a.magnitude) : (1 == a.size)})
75
+ x, y, z = self.x, self.y, self.z
76
+ (!x && !y && !z) || ([x, y, z].select {|a| a}.all? {|a| a.respond_to?(:magnitude) ? (1 == a.magnitude) : (1 == a.size)})
76
77
  end
77
78
 
78
79
  # @attribute [r] matrix
79
80
  # @return [Matrix] the transformation {Matrix} representing the {Rotation}
80
81
  def matrix
81
- return nil unless [@x, @y, @z].compact.size >= 2
82
+ x, y, z = self.x, self.y, self.z
83
+ return nil unless [x, y, z].compact.size >= 2
82
84
 
83
85
  # Force all axes to be Vectors
84
- x,y,z = [@x, @y, @z].map {|a| a.is_a?(Array) ? Vector[*a] : a}
86
+ x,y,z = [x, y, z].map {|a| a.is_a?(Array) ? Vector[*a] : a}
85
87
 
86
88
  # Force all axes to exist
87
89
  if x and y
data/lib/geometry/size.rb CHANGED
@@ -17,8 +17,6 @@ methods (width, height and depth).
17
17
  =end
18
18
 
19
19
  class Size < Vector
20
- attr_reader :x, :y, :z
21
-
22
20
  # Allow vector-style initialization, but override to support copy-init
23
21
  # from Vector, Point or another Size
24
22
  #
@@ -30,7 +28,7 @@ methods (width, height and depth).
30
28
  def self.[](*array)
31
29
  array.map! {|a| a.respond_to?(:to_a) ? a.to_a : a }
32
30
  array.flatten!
33
- super *array
31
+ super(*array)
34
32
  end
35
33
 
36
34
  # Creates and returns a new {SizeOne} instance. Or, a {Size} full of ones if the size argument is given.
@@ -16,10 +16,6 @@ The {Square} class cluster is like the {Rectangle} class cluster, but not longer
16
16
  class Square
17
17
  include ClusterFactory
18
18
 
19
- # @!attribute origin
20
- # @return [Point] The {Square}'s origin
21
- attr_reader :origin
22
-
23
19
  # @!attribute points
24
20
  # @return [Array<Point>] the corner {Point}s of the {Square} in counter-clockwise order
25
21
  attr_reader :points
@@ -101,15 +97,16 @@ The {Square} class cluster is like the {Rectangle} class cluster, but not longer
101
97
  [self.min, self.max]
102
98
  end
103
99
 
104
- # @attribute [r] origin
105
- # @return [Point] The lower left corner
100
+ # @!attribute origin
101
+ # @return [Point] The {Square}'s origin (lower-left corner)
106
102
  def origin
107
103
  @points.first
108
104
  end
109
105
 
106
+ # @return [Array<Point>] The {Square}'s four points (clockwise)
110
107
  def points
111
108
  p0, p1 = *@points
112
- [p0, Point[p1.x, p0.y], p1, Point[p0.x, p1.y]]
109
+ [p0, Point[p0.x, p1.y], p1, Point[p1.x, p0.y]]
113
110
  end
114
111
 
115
112
  def height
@@ -122,6 +119,11 @@ The {Square} class cluster is like the {Rectangle} class cluster, but not longer
122
119
  max.x - min.x
123
120
  end
124
121
  # @endgroup
122
+
123
+ # @return [Path] A closed {Path} that traces the boundary of the {Square} clockwise, starting from the lower-left
124
+ def path
125
+ Path.new(*self.points, self.points.first)
126
+ end
125
127
  end
126
128
 
127
129
  # A {Square} created with a center point and a size
@@ -166,7 +168,7 @@ The {Square} class cluster is like the {Rectangle} class cluster, but not longer
166
168
  end
167
169
 
168
170
  # @attribute [r] points
169
- # @return [Array<Point>] The {Square}'s four points (counterclockwise)
171
+ # @return [Array<Point>] The {Square}'s four points (clockwise)
170
172
  def points
171
173
  half_size = @size/2
172
174
  minx = @center.x - half_size
@@ -174,7 +176,7 @@ The {Square} class cluster is like the {Rectangle} class cluster, but not longer
174
176
  miny = @center.y - half_size
175
177
  maxy = @center.y + half_size
176
178
 
177
- [Point[minx,miny], Point[maxx, miny], Point[maxx, maxy], Point[minx,maxy]]
179
+ [Point[minx,miny], Point[minx, maxy], Point[maxx, maxy], Point[maxx,miny]]
178
180
  end
179
181
 
180
182
  def height
@@ -224,14 +226,14 @@ The {Square} class cluster is like the {Rectangle} class cluster, but not longer
224
226
  end
225
227
 
226
228
  # @attribute [r] points
227
- # @return [Array<Point>] The {Square}'s four points (counterclockwise)
229
+ # @return [Array<Point>] The {Square}'s four points (clockwise)
228
230
  def points
229
231
  minx = origin.x
230
232
  maxx = origin.x + size
231
233
  miny = origin.y
232
234
  maxy = origin.y + size
233
235
 
234
- [origin, Point[maxx, miny], Point[maxx, maxy], Point[minx,maxy]]
236
+ [origin, Point[minx, maxy], Point[maxx, maxy], Point[maxx,miny]]
235
237
  end
236
238
 
237
239
  # @return [Number] The size of the {Square} along the y-axis
@@ -32,7 +32,7 @@ module Geometry
32
32
  # @endgroup
33
33
 
34
34
  def transform(point)
35
- transformations.reverse.reduce(point) {|point, transformation| transformation.transform(point) }
35
+ transformations.reverse.reduce(point) {|_point, transformation| transformation.transform(_point) }
36
36
  end
37
37
  end
38
38
  end
@@ -180,4 +180,19 @@ system's X-axis:
180
180
  @translation ? (@translation + point) : point
181
181
  end
182
182
  end
183
+
184
+ # @override translation(x, y, z)
185
+ # Construct a new translation from the given values
186
+ # @param x [Number] The distance to translate along the X-axis
187
+ # @param y [Number] The distance to translate along the Y-axis
188
+ # @param z [Number] The distance to translate along the Z-axis
189
+ # @override translation([x, y, z])
190
+ # Construct a new translation from an array of values
191
+ # @override translation(point)
192
+ # Construct a new translation from a {Point}
193
+ # @param point [Point] A {Point}
194
+ def self.translation(*args)
195
+ args = *args if args[0].is_a? Array
196
+ Transformation.new translate:args
197
+ end
183
198
  end
@@ -30,7 +30,7 @@ An isoscoles right {Triangle} created with an origin and leg length
30
30
  # @param [Number] height The length of the {Triangle}'s vertical leg
31
31
  def self.new(*args)
32
32
  if args.size == 3
33
- ScaleneTriangle.new *args
33
+ ScaleneTriangle.new(*args)
34
34
  elsif args.size == 2
35
35
  RightTriangle.new args[0], args[1], args[1]
36
36
  end
@@ -16,6 +16,18 @@ describe Geometry::Annulus do
16
16
  it 'must have a center' do
17
17
  subject.center.must_equal Point[1,2]
18
18
  end
19
+
20
+ it 'must have a max' do
21
+ subject.max.must_equal Point[11, 12]
22
+ end
23
+
24
+ it 'must have a min' do
25
+ subject.min.must_equal Point[-9, -8]
26
+ end
27
+
28
+ it 'must have a min and a max' do
29
+ subject.minmax.must_equal [subject.min, subject.max]
30
+ end
19
31
  end
20
32
 
21
33
  describe 'when constructed with a center, inner_radius and radius' do
data/test/geometry/arc.rb CHANGED
@@ -23,3 +23,101 @@ describe Geometry::Arc do
23
23
  end
24
24
  end
25
25
  end
26
+
27
+ describe Geometry::ThreePointArc do
28
+ it 'must have an ending angle' do
29
+ arc = Geometry::ThreePointArc.new([0,0], [1,0], [0,1])
30
+ arc.end_angle.must_equal Math::PI/2
31
+ end
32
+
33
+ it 'must have a radius' do
34
+ arc = Geometry::ThreePointArc.new([0,0], [1,0], [0,1])
35
+ arc.radius.must_equal 1
36
+ end
37
+
38
+ it 'must have an starting angle' do
39
+ arc = Geometry::ThreePointArc.new([0,0], [1,0], [0,1])
40
+ arc.start_angle.must_equal 0
41
+ end
42
+
43
+ describe 'max' do
44
+ # Cosine and sine of a 22.5 degree angle
45
+ let(:cos) { 0.9239556995 }
46
+ let(:sin) { 0.3824994973 }
47
+ let(:radius) { 1.0000000000366434 }
48
+
49
+ it 'must handle an Arc entirely in quadrant 1' do
50
+ arc = Geometry::ThreePointArc.new([0,0], [cos,sin], [sin,cos])
51
+ arc.max.must_equal Point[cos,cos]
52
+ end
53
+
54
+ it 'must handle a counterclockwise Arc from quadrant 1 to quadrant 2' do
55
+ arc = Geometry::ThreePointArc.new([0,0], [cos,sin], [-cos,sin])
56
+ arc.max.must_equal Point[cos,radius]
57
+ end
58
+
59
+ it 'must handle a counterclockwise Arc from quadrant 4 to quadrant 1' do
60
+ arc = Geometry::ThreePointArc.new([0,0], [sin,-cos], [sin,cos])
61
+ arc.max.must_equal Point[radius,cos]
62
+ end
63
+
64
+ it 'must handle a counterclockwise Arc from quadrant 3 to quadrant 2' do
65
+ arc = Geometry::ThreePointArc.new([0,0], [-cos,-sin], [-cos,sin])
66
+ arc.max.must_equal Point[radius,radius]
67
+ end
68
+ end
69
+
70
+ describe 'min' do
71
+ # Cosine and sine of a 22.5 degree angle
72
+ let(:cos) { 0.9239556995 }
73
+ let(:sin) { 0.3824994973 }
74
+ let(:radius) { 1.0000000000366434 }
75
+
76
+ it 'must handle an Arc entirely in quadrant 1' do
77
+ arc = Geometry::ThreePointArc.new([0,0], [cos,sin], [sin,cos])
78
+ arc.min.must_equal Point[sin,sin]
79
+ end
80
+
81
+ it 'must handle a counterclockwise Arc from quadrant 1 to quadrant 2' do
82
+ arc = Geometry::ThreePointArc.new([0,0], [cos,sin], [-cos,sin])
83
+ arc.min.must_equal Point[-cos,sin]
84
+ end
85
+
86
+ it 'must handle a counterclockwise Arc from quadrant 4 to quadrant 1' do
87
+ arc = Geometry::ThreePointArc.new([0,0], [sin,-cos], [sin,cos])
88
+ arc.min.must_equal Point[sin,-cos]
89
+ end
90
+
91
+ it 'must handle a counterclockwise Arc from quadrant 3 to quadrant 2' do
92
+ arc = Geometry::ThreePointArc.new([0,0], [-cos,-sin], [-cos,sin])
93
+ arc.min.must_equal Point[-cos,-radius]
94
+ end
95
+ end
96
+
97
+ describe 'minmax' do
98
+ # Cosine and sine of a 22.5 degree angle
99
+ let(:cos) { 0.9239556995 }
100
+ let(:sin) { 0.3824994973 }
101
+ let(:radius) { 1.0000000000366434 }
102
+
103
+ it 'must handle an Arc entirely in quadrant 1' do
104
+ arc = Geometry::ThreePointArc.new([0,0], [cos,sin], [sin,cos])
105
+ arc.minmax.must_equal [Point[sin,sin], Point[cos,cos]]
106
+ end
107
+
108
+ it 'must handle a counterclockwise Arc from quadrant 1 to quadrant 2' do
109
+ arc = Geometry::ThreePointArc.new([0,0], [cos,sin], [-cos,sin])
110
+ arc.minmax.must_equal [Point[-cos,sin], Point[cos,radius]]
111
+ end
112
+
113
+ it 'must handle a counterclockwise Arc from quadrant 4 to quadrant 1' do
114
+ arc = Geometry::ThreePointArc.new([0,0], [sin,-cos], [sin,cos])
115
+ arc.minmax.must_equal [Point[sin,-cos], Point[radius,cos]]
116
+ end
117
+
118
+ it 'must handle a counterclockwise Arc from quadrant 3 to quadrant 2' do
119
+ arc = Geometry::ThreePointArc.new([0,0], [-cos,-sin], [-cos,sin])
120
+ arc.minmax.must_equal [Point[-cos,-radius], Point[radius,radius]]
121
+ end
122
+ end
123
+ end
@@ -108,17 +108,17 @@ describe Geometry::Circle do
108
108
  let(:circle) { Circle.new :diameter => Rational(5,3) }
109
109
 
110
110
  it 'must have the correct min values' do
111
- circle.min.must_equal Point[-5/6, -5/6]
111
+ circle.min.must_equal Point[-5.to_r/6, -5.to_r/6]
112
112
  circle.min.must_be_instance_of Geometry::PointIso
113
113
  end
114
114
 
115
115
  it 'must have the correct max values' do
116
- circle.max.must_equal Point[5/6, 5/6]
116
+ circle.max.must_equal Point[5.to_r/6, 5.to_r/6]
117
117
  circle.max.must_be_instance_of Geometry::PointIso
118
118
  end
119
119
 
120
120
  it 'must have the correct minmax values' do
121
- circle.minmax.must_equal [Point[-5/6, -5/6], Point[5/6,5/6]]
121
+ circle.minmax.must_equal [Point[-5.to_r/6, -5.to_r/6], Point[5.to_r/6,5.to_r/6]]
122
122
  end
123
123
  end
124
124
 
@@ -75,11 +75,25 @@ describe Geometry::Edge do
75
75
  subject.to_a.must_equal original.reverse
76
76
  end
77
77
 
78
+ describe 'attributes' do
79
+ it 'must have a maximum' do
80
+ Edge([0,0], [1,1]).max.must_equal Point[1,1]
81
+ end
82
+
83
+ it 'must have a minimum' do
84
+ Edge([0,0], [1,1]).min.must_equal Point[0,0]
85
+ end
86
+
87
+ it 'must have a minmax' do
88
+ Edge([0,0], [1,1]).minmax.must_equal [Point[0,0], Point[1,1]]
89
+ end
90
+ end
91
+
78
92
  describe "spaceship" do
79
93
  it "ascending with a Point" do
80
94
  edge = Edge.new [0,0], [1,1]
81
95
  (edge <=> Point[0,0]).must_equal 0
82
- (edge <=> Point[1,0]).must_equal -1
96
+ (edge <=> Point[1,0]).must_equal(-1)
83
97
  (edge <=> Point[0,1]).must_equal 1
84
98
  (edge <=> Point[2,2]).must_equal nil
85
99
  end
@@ -88,7 +102,7 @@ describe Geometry::Edge do
88
102
  edge = Edge.new [1,1], [0,0]
89
103
  (edge <=> Point[0,0]).must_equal 0
90
104
  (edge <=> Point[1,0]).must_equal 1
91
- (edge <=> Point[0,1]).must_equal -1
105
+ (edge <=> Point[0,1]).must_equal(-1)
92
106
  (edge <=> Point[2,2]).must_equal nil
93
107
  end
94
108
  end