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