geometry 6.4 → 6.6
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.github/workflows/ruby.yml +21 -0
- data/Gemfile +3 -0
- data/README.markdown +1 -2
- data/geometry.gemspec +5 -1
- data/lib/geometry/annulus.rb +20 -2
- data/lib/geometry/arc.rb +72 -1
- data/lib/geometry/edge.rb +21 -4
- data/lib/geometry/line.rb +84 -18
- data/lib/geometry/obround.rb +1 -2
- data/lib/geometry/path.rb +27 -0
- data/lib/geometry/point.rb +59 -8
- data/lib/geometry/point_iso.rb +12 -2
- data/lib/geometry/point_one.rb +44 -34
- data/lib/geometry/point_zero.rb +12 -1
- data/lib/geometry/polygon.rb +13 -12
- data/lib/geometry/polyline.rb +6 -6
- data/lib/geometry/rectangle.rb +12 -12
- data/lib/geometry/rotation.rb +5 -3
- data/lib/geometry/size.rb +1 -3
- data/lib/geometry/square.rb +13 -11
- data/lib/geometry/transformation/composition.rb +1 -1
- data/lib/geometry/transformation.rb +15 -0
- data/lib/geometry/triangle.rb +1 -1
- data/test/geometry/annulus.rb +12 -0
- data/test/geometry/arc.rb +98 -0
- data/test/geometry/circle.rb +3 -3
- data/test/geometry/edge.rb +16 -2
- data/test/geometry/line.rb +73 -0
- data/test/geometry/path.rb +16 -0
- data/test/geometry/point.rb +78 -2
- data/test/geometry/point_iso.rb +31 -21
- data/test/geometry/point_one.rb +11 -1
- data/test/geometry/point_zero.rb +46 -36
- data/test/geometry/polygon.rb +1 -1
- data/test/geometry/rectangle.rb +6 -1
- data/test/geometry/rotation.rb +1 -1
- data/test/geometry/size_one.rb +1 -1
- data/test/geometry/size_zero.rb +1 -1
- data/test/geometry/square.rb +15 -10
- data/test/geometry/transformation.rb +15 -1
- data/test/geometry/vector.rb +1 -1
- metadata +36 -9
- data/.travis.yml +0 -12
data/lib/geometry/point_one.rb
CHANGED
@@ -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
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
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
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
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
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
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
|
|
data/lib/geometry/point_zero.rb
CHANGED
@@ -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
|
data/lib/geometry/polygon.rb
CHANGED
@@ -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
|
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
|
-
|
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 ==
|
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
|
-
|
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
|
-
|
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
|
-
|
174
|
-
|
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? !=
|
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
|
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
|
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
|
337
|
-
|
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})
|
data/lib/geometry/polyline.rb
CHANGED
@@ -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
|
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
|
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
|
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
|
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
|
372
|
+
@edges.push(*e)
|
373
373
|
@edges.uniq!
|
374
374
|
end
|
375
375
|
|
376
376
|
def push_vertex(*v)
|
377
|
-
@vertices.push
|
377
|
+
@vertices.push(*v)
|
378
378
|
@vertices.uniq!
|
379
379
|
end
|
380
380
|
|
data/lib/geometry/rectangle.rb
CHANGED
@@ -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 (
|
150
|
+
# @return [Array<Point>] The {Rectangle}'s four points (clockwise)
|
159
151
|
def points
|
160
152
|
point0, point2 = *@points
|
161
|
-
point1 = Point[
|
162
|
-
point3 = Point[
|
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
|
data/lib/geometry/rotation.rb
CHANGED
@@ -72,16 +72,18 @@ A generalized representation of a rotation transformation.
|
|
72
72
|
alias :== :eql?
|
73
73
|
|
74
74
|
def identity?
|
75
|
-
|
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
|
-
|
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 = [
|
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
|
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.
|
data/lib/geometry/square.rb
CHANGED
@@ -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
|
-
#
|
105
|
-
#
|
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[
|
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 (
|
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[
|
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 (
|
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[
|
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) {|
|
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
|
data/lib/geometry/triangle.rb
CHANGED
@@ -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
|
33
|
+
ScaleneTriangle.new(*args)
|
34
34
|
elsif args.size == 2
|
35
35
|
RightTriangle.new args[0], args[1], args[1]
|
36
36
|
end
|
data/test/geometry/annulus.rb
CHANGED
@@ -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
|
data/test/geometry/circle.rb
CHANGED
@@ -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
|
|
data/test/geometry/edge.rb
CHANGED
@@ -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
|
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
|
105
|
+
(edge <=> Point[0,1]).must_equal(-1)
|
92
106
|
(edge <=> Point[2,2]).must_equal nil
|
93
107
|
end
|
94
108
|
end
|