geometry 6.1 → 6.2
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 +4 -4
- data/geometry.gemspec +1 -1
- data/lib/geometry/edge.rb +1 -1
- data/lib/geometry/point.rb +27 -0
- data/lib/geometry/polyline.rb +78 -3
- data/lib/geometry/size.rb +70 -1
- data/lib/geometry/square.rb +118 -5
- data/lib/geometry/transformation.rb +12 -0
- data/test/geometry/polyline.rb +45 -0
- data/test/geometry/size.rb +26 -0
- data/test/geometry/square.rb +99 -1
- data/test/geometry/transformation.rb +8 -0
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c1d1683621a91df0a25df4e3f2cc4fe6ecdf3dfb
|
4
|
+
data.tar.gz: 597979326e97270c5fd7f037615f391e7f50fc18
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2fbe85bbe712dff07a728326e3aafc40325849f03cb995221b8a6f65e6f4b3a6ba6b10de21f13e0ed4b18b92664f92ecb1b99a67b013430173494a35449354b2
|
7
|
+
data.tar.gz: 5f1e1ddc4670af310745a74951eb6280bd4d515122ade09df4bc9cbbc40c6247fc5131c3ee7ef46f9d9ea4e707850e8462c03e458e6443e1793f374f950344d4
|
data/geometry.gemspec
CHANGED
data/lib/geometry/edge.rb
CHANGED
data/lib/geometry/point.rb
CHANGED
@@ -156,6 +156,33 @@ geometry class (x, y, z).
|
|
156
156
|
end
|
157
157
|
end
|
158
158
|
|
159
|
+
def *(other)
|
160
|
+
case other
|
161
|
+
when NilClass
|
162
|
+
nil
|
163
|
+
when Numeric
|
164
|
+
Point[@elements.map {|e| e * other}]
|
165
|
+
when PointZero
|
166
|
+
Point.zero
|
167
|
+
else
|
168
|
+
if other.respond_to?(:[])
|
169
|
+
raise OperationNotDefined, "#{other.class} must respond to :size" unless other.respond_to?(:size)
|
170
|
+
raise DimensionMismatch, "Can't multiply #{self} by #{other}" if size != other.size
|
171
|
+
Point[Array.new(size) {|i| @elements[i] * other[i] }]
|
172
|
+
else
|
173
|
+
Point[@elements.map {|e| e * other}]
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
def /(other)
|
179
|
+
case other
|
180
|
+
when Matrix, Vector, Point, Size, NilClass, PointZero, SizeZero
|
181
|
+
raise OperationNotDefined, "Can't divide #{self} by #{other}"
|
182
|
+
else
|
183
|
+
Point[@elements.map {|e| e / other}]
|
184
|
+
end
|
185
|
+
end
|
159
186
|
# @endgroup
|
160
187
|
|
161
188
|
end
|
data/lib/geometry/polyline.rb
CHANGED
@@ -48,8 +48,7 @@ also like a {Path} in that it isn't necessarily closed.
|
|
48
48
|
if @edges.last
|
49
49
|
new_edge = Edge.new(previous, n)
|
50
50
|
if @edges.last.parallel?(new_edge)
|
51
|
-
popped_edge =
|
52
|
-
@vertices.pop(@edges.size ? 1 : 2) # Remove the now unused vertex, or vertices
|
51
|
+
popped_edge = pop_edge # Remove the previous Edge
|
53
52
|
if n == popped_edge.first
|
54
53
|
popped_edge.first
|
55
54
|
else
|
@@ -94,6 +93,25 @@ also like a {Path} in that it isn't necessarily closed.
|
|
94
93
|
end
|
95
94
|
alias :== :eql?
|
96
95
|
|
96
|
+
# @group Attributes
|
97
|
+
|
98
|
+
# @return [Point] The upper-right corner of the bounding rectangle that encloses the {Polyline}
|
99
|
+
def max
|
100
|
+
vertices.reduce {|memo, vertex| Point[[memo.x, vertex.x].max, [memo.y, vertex.y].max] }
|
101
|
+
end
|
102
|
+
|
103
|
+
# @return [Point] The lower-left corner of the bounding rectangle that encloses the {Polyline}
|
104
|
+
def min
|
105
|
+
vertices.reduce {|memo, vertex| Point[[memo.x, vertex.x].min, [memo.y, vertex.y].min] }
|
106
|
+
end
|
107
|
+
|
108
|
+
# @return [Array<Point>] The lower-left and upper-right corners of the enclosing bounding rectangle
|
109
|
+
def minmax
|
110
|
+
vertices.reduce([vertices.first, vertices.first]) {|memo, vertex| [Point[[memo.first.x, vertex.x].min, [memo.first.y, vertex.y].min], Point[[memo.last.x, vertex.x].max, [memo.last.y, vertex.y].max]] }
|
111
|
+
end
|
112
|
+
|
113
|
+
# @endgroup
|
114
|
+
|
97
115
|
# Clone the receiver, close it, then return it
|
98
116
|
# @return [Polyline] the closed clone of the receiver
|
99
117
|
def close
|
@@ -103,7 +121,36 @@ also like a {Path} in that it isn't necessarily closed.
|
|
103
121
|
# Close the receiver and return it
|
104
122
|
# @return [Polyline] the receiver after closing
|
105
123
|
def close!
|
106
|
-
|
124
|
+
unless @edges.empty?
|
125
|
+
# NOTE: parallel? is use here instead of collinear? because the
|
126
|
+
# edges are connected, and will therefore be collinear if
|
127
|
+
# they're parallel
|
128
|
+
|
129
|
+
if closed?
|
130
|
+
if @edges.first.parallel?(@edges.last)
|
131
|
+
unshift_edge Edge.new(@edges.last.first, shift_edge.last)
|
132
|
+
end
|
133
|
+
elsif
|
134
|
+
closing_edge = Edge.new(@edges.last.last, @edges.first.first)
|
135
|
+
|
136
|
+
# If the closing edge is collinear with the last edge, then
|
137
|
+
# simply extened the last edge to fill the gap
|
138
|
+
if @edges.last.parallel?(closing_edge)
|
139
|
+
closing_edge = Edge.new(pop_edge.first, @edges.first.first)
|
140
|
+
end
|
141
|
+
|
142
|
+
# Check that the new closing_edge isn't zero-length
|
143
|
+
if closing_edge.first != closing_edge.last
|
144
|
+
# If the closing edge is collinear with the first edge, then
|
145
|
+
# extend the first edge "backwards" to fill the gap
|
146
|
+
if @edges.first.parallel?(closing_edge)
|
147
|
+
unshift_edge Edge.new(closing_edge.first, shift_edge.last)
|
148
|
+
else
|
149
|
+
push_edge closing_edge
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
107
154
|
self
|
108
155
|
end
|
109
156
|
|
@@ -305,6 +352,18 @@ also like a {Path} in that it isn't necessarily closed.
|
|
305
352
|
end
|
306
353
|
# @endgroup
|
307
354
|
|
355
|
+
# Pop the last edge, and its associated vertices
|
356
|
+
# @return [Edge] the popped {Edge}
|
357
|
+
def pop_edge
|
358
|
+
old = @edges.pop # Remove the last Edge
|
359
|
+
if 0 == @edges.length # Remove all vertices if the only Edge was popped
|
360
|
+
@vertices.clear
|
361
|
+
elsif old.last == @vertices.last # Remove the last vertex if it was used by the popped Edge
|
362
|
+
@vertices.pop
|
363
|
+
end
|
364
|
+
old
|
365
|
+
end
|
366
|
+
|
308
367
|
def push_edge(*e)
|
309
368
|
@edges.push *e
|
310
369
|
@edges.uniq!
|
@@ -314,5 +373,21 @@ also like a {Path} in that it isn't necessarily closed.
|
|
314
373
|
@vertices.push *v
|
315
374
|
@vertices.uniq!
|
316
375
|
end
|
376
|
+
|
377
|
+
# Remove the first {Edge} and its associated vertices
|
378
|
+
# @return [Edge] the shifted {Edge}
|
379
|
+
def shift_edge
|
380
|
+
@vertices.shift((@edges.size > 1) ? 1 : 2)
|
381
|
+
@edges.shift
|
382
|
+
end
|
383
|
+
|
384
|
+
# Prepend an {Edge} and its vertices
|
385
|
+
# @param edge [Edge] the {Edge} to unshift
|
386
|
+
def unshift_edge(edge)
|
387
|
+
@vertices.unshift(edge.last) unless edge.last == @edges.first.first
|
388
|
+
@vertices.unshift(edge.first) # unless edge.first == @edges.last.last
|
389
|
+
@vertices.pop if @vertices.last == @vertices.first
|
390
|
+
@edges.unshift(edge)
|
391
|
+
end
|
317
392
|
end
|
318
393
|
end
|
data/lib/geometry/size.rb
CHANGED
@@ -25,7 +25,8 @@ methods (width, height and depth).
|
|
25
25
|
# @overload [](Vector)
|
26
26
|
# @return [Size] A new {Size} object
|
27
27
|
def self.[](*array)
|
28
|
-
array
|
28
|
+
array.map! {|a| a.respond_to?(:to_a) ? a.to_a : a }
|
29
|
+
array.flatten!
|
29
30
|
super *array
|
30
31
|
end
|
31
32
|
|
@@ -71,5 +72,73 @@ methods (width, height and depth).
|
|
71
72
|
def z
|
72
73
|
@elements[2]
|
73
74
|
end
|
75
|
+
|
76
|
+
# Create a new {Size} that is smaller than the receiver by the specified amounts
|
77
|
+
# @overload inset(x,y)
|
78
|
+
# @param x [Number] the horizontal inset
|
79
|
+
# @param y [Number] the vertical inset
|
80
|
+
# @overload inset(options)
|
81
|
+
# @option options [Number] :left the left inset
|
82
|
+
# @option options [Number] :right the right inset
|
83
|
+
# @option options [Number] :top the top inset
|
84
|
+
# @option options [Number] :bottom the bottom inset
|
85
|
+
def inset(*args)
|
86
|
+
options, args = args.partition {|a| a.is_a? Hash}
|
87
|
+
options = options.reduce({}, :merge)
|
88
|
+
|
89
|
+
left = right = top = bottom = 0
|
90
|
+
if 1 == args.size
|
91
|
+
left = top = -args.shift
|
92
|
+
right = bottom = 0
|
93
|
+
elsif 2 == args.size
|
94
|
+
left = -args.shift
|
95
|
+
top = -args.shift
|
96
|
+
right = bottom = 0
|
97
|
+
end
|
98
|
+
|
99
|
+
left = -options[:x] if options[:x]
|
100
|
+
top = -options[:y] if options[:y]
|
101
|
+
|
102
|
+
top = -options[:top] if options[:top]
|
103
|
+
left = -options[:left] if options[:left]
|
104
|
+
bottom = -options[:bottom] if options[:bottom]
|
105
|
+
right = -options[:right] if options[:right]
|
106
|
+
|
107
|
+
self.class[left + width + right, top + height + bottom]
|
108
|
+
end
|
109
|
+
|
110
|
+
# Create a new {Size} that is larger than the receiver by the specified amounts
|
111
|
+
# @overload outset(x,y)
|
112
|
+
# @param x [Number] the horizontal inset
|
113
|
+
# @param y [Number] the vertical inset
|
114
|
+
# @overload outset(options)
|
115
|
+
# @option options [Number] :left the left inset
|
116
|
+
# @option options [Number] :right the right inset
|
117
|
+
# @option options [Number] :top the top inset
|
118
|
+
# @option options [Number] :bottom the bottom inset
|
119
|
+
def outset(*args)
|
120
|
+
options, args = args.partition {|a| a.is_a? Hash}
|
121
|
+
options = options.reduce({}, :merge)
|
122
|
+
|
123
|
+
left = right = top = bottom = 0
|
124
|
+
if 1 == args.size
|
125
|
+
left = top = args.shift
|
126
|
+
right = bottom = 0
|
127
|
+
elsif 2 == args.size
|
128
|
+
left = args.shift
|
129
|
+
top = args.shift
|
130
|
+
right = bottom = 0
|
131
|
+
end
|
132
|
+
|
133
|
+
left = options[:x] if options[:x]
|
134
|
+
top = options[:y] if options[:y]
|
135
|
+
|
136
|
+
top = options[:top] if options[:top]
|
137
|
+
left = options[:left] if options[:left]
|
138
|
+
bottom = options[:bottom] if options[:bottom]
|
139
|
+
right = options[:right] if options[:right]
|
140
|
+
|
141
|
+
self.class[left + width + right, top + height + bottom]
|
142
|
+
end
|
74
143
|
end
|
75
144
|
end
|
data/lib/geometry/square.rb
CHANGED
@@ -8,25 +8,53 @@ The {Square} class cluster is like the {Rectangle} class cluster, but not longer
|
|
8
8
|
|
9
9
|
== Constructors
|
10
10
|
|
11
|
-
|
12
|
-
|
11
|
+
Square.new from:[1,2], to:[2,3] # Using two corners
|
12
|
+
Square.new origin:[3,4], size:5 # Using an origin point and a size
|
13
|
+
Square.new center:[5,5], size:5 # Using a center point and a size
|
14
|
+
Square.new size:5 # Centered on the origin
|
13
15
|
=end
|
14
16
|
class Square
|
17
|
+
include ClusterFactory
|
18
|
+
|
19
|
+
# @!attribute origin
|
20
|
+
# @return [Point] The {Square}'s origin
|
15
21
|
attr_reader :origin
|
16
22
|
|
23
|
+
# @overload new(:origin, :size)
|
24
|
+
# Creates a {Square} with the given origin and size
|
25
|
+
# @option [Point] :origin The lower-left corner
|
26
|
+
# @option [Number] :size Bigness
|
27
|
+
# @return [CenteredSquare]
|
28
|
+
def self.new(*args)
|
29
|
+
options, args = args.partition {|a| a.is_a? Hash}
|
30
|
+
options = options.reduce({}, :merge)
|
31
|
+
|
32
|
+
if options.key?(:size)
|
33
|
+
unless options[:size].is_a? Numeric
|
34
|
+
raise NotSquareError, 'Size must be a square' unless options[:size].all? {|a| a == options[:size].first}
|
35
|
+
options[:size] = options[:size].first
|
36
|
+
end
|
37
|
+
|
38
|
+
if options.key? :origin
|
39
|
+
SizedSquare.new(options[:origin], options[:size])
|
40
|
+
else
|
41
|
+
CenteredSquare.new(options[:center] || PointZero.new, options[:size])
|
42
|
+
end
|
43
|
+
elsif options.key?(:from) and options.key?(:to)
|
44
|
+
original_new(from: options[:from], to: options[:to])
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
17
48
|
# Creates a {Square} given two {Point}s
|
18
49
|
# @option options [Point] :from A corner (ie. bottom-left)
|
19
50
|
# @option options [Point] :to The other corner (ie. top-right)
|
20
51
|
# @option options [Point] :origin The lower left corner
|
21
|
-
# @option options [Number] :size Bigness
|
22
52
|
def initialize(options={})
|
23
53
|
origin = options[:from] || options[:origin]
|
24
54
|
origin = origin ? Point[origin] : PointZero.new
|
25
55
|
|
26
56
|
if options.has_key? :to
|
27
57
|
point1 = options[:to]
|
28
|
-
elsif options.has_key? :size
|
29
|
-
point1 = origin + options[:size]
|
30
58
|
end
|
31
59
|
|
32
60
|
point1 = Point[point1]
|
@@ -41,6 +69,21 @@ The {Square} class cluster is like the {Rectangle} class cluster, but not longer
|
|
41
69
|
end
|
42
70
|
|
43
71
|
# !@group Accessors
|
72
|
+
# @return [Point] The upper right corner of the bounding {Rectangle}
|
73
|
+
def max
|
74
|
+
@points.last
|
75
|
+
end
|
76
|
+
|
77
|
+
# @return [Point] The lower left corner of the bounding {Rectangle}
|
78
|
+
def min
|
79
|
+
@points.first
|
80
|
+
end
|
81
|
+
|
82
|
+
# @return [Array<Point>] The lower left and upper right corners of the bounding {Rectangle}
|
83
|
+
def minmax
|
84
|
+
[self.min, self.max]
|
85
|
+
end
|
86
|
+
|
44
87
|
# @attribute [r] origin
|
45
88
|
# @return [Point] The lower left corner
|
46
89
|
def origin
|
@@ -65,6 +108,10 @@ The {Square} class cluster is like the {Rectangle} class cluster, but not longer
|
|
65
108
|
# @return [Point] The center of the {Square}
|
66
109
|
attr_reader :center
|
67
110
|
|
111
|
+
# @!attribute size
|
112
|
+
# @return [Size] The {Size} of the {Square}
|
113
|
+
attr_accessor :size
|
114
|
+
|
68
115
|
# @param [Point] center The center point
|
69
116
|
# @param [Numeric] size The length of each side
|
70
117
|
def initialize(center, size)
|
@@ -73,6 +120,23 @@ The {Square} class cluster is like the {Rectangle} class cluster, but not longer
|
|
73
120
|
end
|
74
121
|
|
75
122
|
# @group Accessors
|
123
|
+
# @return [Point] The upper right corner of the bounding {Rectangle}
|
124
|
+
def max
|
125
|
+
half_size = @size/2
|
126
|
+
Point[@center.x + half_size, @center.y + half_size]
|
127
|
+
end
|
128
|
+
|
129
|
+
# @return [Point] The lower left corner of the bounding {Rectangle}
|
130
|
+
def min
|
131
|
+
half_size = @size/2
|
132
|
+
Point[@center.x - half_size, @center.y - half_size]
|
133
|
+
end
|
134
|
+
|
135
|
+
# @return [Array<Point>] The lower left and upper right corners of the bounding {Rectangle}
|
136
|
+
def minmax
|
137
|
+
[self.min, self.max]
|
138
|
+
end
|
139
|
+
|
76
140
|
# @attribute [r] origin
|
77
141
|
# @return [Point] The lower left corner
|
78
142
|
def origin
|
@@ -103,11 +167,60 @@ The {Square} class cluster is like the {Rectangle} class cluster, but not longer
|
|
103
167
|
|
104
168
|
# A {Square} created with an origin point and a size
|
105
169
|
class SizedSquare < Square
|
170
|
+
# @!attribute size
|
171
|
+
# @return [Size] The {Size} of the {Square}
|
172
|
+
attr_accessor :size
|
173
|
+
|
106
174
|
# @param [Point] origin The origin point (bottom-left corner)
|
107
175
|
# @param [Numeric] size The length of each side
|
108
176
|
def initialize(origin, size)
|
109
177
|
@origin = Point[origin]
|
110
178
|
@size = size
|
111
179
|
end
|
180
|
+
|
181
|
+
# @group Accessors
|
182
|
+
# @!attribute center
|
183
|
+
# @return [Point] The center of it all
|
184
|
+
def center
|
185
|
+
origin + size/2
|
186
|
+
end
|
187
|
+
|
188
|
+
# @return [Point] The upper right corner of the bounding {Rectangle}
|
189
|
+
def max
|
190
|
+
origin + size
|
191
|
+
end
|
192
|
+
|
193
|
+
# @return [Point] The lower left corner of the bounding {Rectangle}
|
194
|
+
def min
|
195
|
+
origin
|
196
|
+
end
|
197
|
+
|
198
|
+
# @attribute [r] origin
|
199
|
+
# @return [Point] The lower left corner
|
200
|
+
def origin
|
201
|
+
@origin
|
202
|
+
end
|
203
|
+
|
204
|
+
# @attribute [r] points
|
205
|
+
# @return [Array<Point>] The {Square}'s four points (counterclockwise)
|
206
|
+
def points
|
207
|
+
minx = origin.x
|
208
|
+
maxx = origin.x + size
|
209
|
+
miny = origin.y
|
210
|
+
maxy = origin.y + size
|
211
|
+
|
212
|
+
[origin, Point[maxx, miny], Point[maxx, maxy], Point[minx,maxy]]
|
213
|
+
end
|
214
|
+
|
215
|
+
# @return [Number] The size of the {Square} along the y-axis
|
216
|
+
def height
|
217
|
+
@size
|
218
|
+
end
|
219
|
+
|
220
|
+
# @return [Number] The size of the {Square} along the x-axis
|
221
|
+
def width
|
222
|
+
@size
|
223
|
+
end
|
224
|
+
# @endgroup
|
112
225
|
end
|
113
226
|
end
|
@@ -112,6 +112,18 @@ system's X-axis:
|
|
112
112
|
end
|
113
113
|
alias :== :eql?
|
114
114
|
|
115
|
+
# Update the translation property with the given {Point}
|
116
|
+
# @param point [Point] the distance to translate by
|
117
|
+
# @return [Transformation]
|
118
|
+
def translate(point)
|
119
|
+
if translation
|
120
|
+
@translation += Point[point]
|
121
|
+
else
|
122
|
+
@translation = Point[point]
|
123
|
+
end
|
124
|
+
self
|
125
|
+
end
|
126
|
+
|
115
127
|
# Compose the current {Transformation} with another one
|
116
128
|
def +(other)
|
117
129
|
return self.clone unless other
|
data/test/geometry/polyline.rb
CHANGED
@@ -22,6 +22,51 @@ describe Geometry::Polyline do
|
|
22
22
|
polyline.vertices.count.must_equal 4
|
23
23
|
end
|
24
24
|
|
25
|
+
it 'must know the max' do
|
26
|
+
unit_square.max.must_equal Point[1,1]
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'must know the min' do
|
30
|
+
unit_square.min.must_equal Point[0,0]
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'must know the min and the max' do
|
34
|
+
unit_square.minmax.must_equal [Point[0,0], Point[1,1]]
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'must close when the first and last edges are collinear' do
|
38
|
+
polygon = Polyline.new([1,0], [2,0], [2,1], [-1,1], [-1,0], [0,0]).close!
|
39
|
+
polygon.must_equal Polyline.new([-1,0], [2,0], [2,1], [-1,1])
|
40
|
+
end
|
41
|
+
|
42
|
+
it 'must close when the closing edge is collinear with the last edge' do
|
43
|
+
polygon = Polyline.new([1,0], [1,1], [-1,1], [-1,0], [0,0]).close!
|
44
|
+
polygon.must_equal Polyline.new([1,0], [1,1], [-1,1], [-1,0], [1,0])
|
45
|
+
end
|
46
|
+
|
47
|
+
it 'must close when the closing edge is collinear with the first edge' do
|
48
|
+
polygon = Polyline.new([0,0], [1,0], [1,1], [-1,1], [-1, 0]).close!
|
49
|
+
polygon.must_equal Polyline.new([-1,0], [1,0], [1,1], [-1,1], [-1,0])
|
50
|
+
end
|
51
|
+
|
52
|
+
it 'must close when the closing edge backtracks over the first edge' do
|
53
|
+
polygon = Polyline.new([0,0], [2,0], [2,1], [-1,1], [-1, -1], [1,-1], [1,0]).close!
|
54
|
+
polygon.must_equal Polyline.new([1,0], [2,0], [2,1], [-1,1], [-1,-1], [1,-1])
|
55
|
+
|
56
|
+
polygon = Polyline.new([0,-1], [0,1], [1,1], [1,0], [0,0]).close!
|
57
|
+
polygon.must_equal Polyline.new([0,0], [0,1], [1,1], [1,0], [0,0])
|
58
|
+
end
|
59
|
+
|
60
|
+
it 'must close when already closed and the first and last edges are collinear' do
|
61
|
+
polygon = Polyline.new([1,0], [1,1], [0,1], [0,-1], [1,-1], [1,0]).close!
|
62
|
+
polygon.must_equal Polyline.new([1,-1], [1,1], [0,1], [0,-1])
|
63
|
+
end
|
64
|
+
|
65
|
+
it 'must close when the closing edge exactly backtracks the last edge' do
|
66
|
+
polygon = Polyline.new([0,0], [0,1], [1,1], [1,0], [0,0], [0,1]).close!
|
67
|
+
polygon.must_equal Polyline.new([0,0], [0,1], [1,1], [1,0], [0,0])
|
68
|
+
end
|
69
|
+
|
25
70
|
describe "when the Polyline is closed" do
|
26
71
|
let(:closed_concave_polyline) { Polyline.new [-2,0], [0,0], [0,-2], [2,-2], [2,2], [-2,2], [-2,0] }
|
27
72
|
subject { closed_concave_polyline }
|
data/test/geometry/size.rb
CHANGED
@@ -2,6 +2,8 @@ require 'minitest/autorun'
|
|
2
2
|
require 'geometry/size'
|
3
3
|
|
4
4
|
describe Geometry::Size do
|
5
|
+
subject { Geometry::Size[10,10] }
|
6
|
+
|
5
7
|
describe "when constructed" do
|
6
8
|
it "create a Size object using list syntax" do
|
7
9
|
size = Geometry::Size[2,1]
|
@@ -94,4 +96,28 @@ describe Geometry::Size do
|
|
94
96
|
size = Geometry::Size[10,11]
|
95
97
|
assert_equal('Size[10, 11]', size.to_s)
|
96
98
|
end
|
99
|
+
|
100
|
+
it 'must inset with horizontal and vertical insets' do
|
101
|
+
subject.inset(4).must_equal Geometry::Size[6, 6]
|
102
|
+
subject.inset(2,3).must_equal Geometry::Size[8, 7]
|
103
|
+
subject.inset(x:2, y:3).must_equal Geometry::Size[8, 7]
|
104
|
+
subject.inset(left:2, top:3).must_equal Geometry::Size[8, 7]
|
105
|
+
subject.inset(right:2, bottom:3).must_equal Geometry::Size[8, 7]
|
106
|
+
end
|
107
|
+
|
108
|
+
it 'must inset with insets for top, left, bottom, right' do
|
109
|
+
subject.inset(top:1, left:2, bottom:3, right:4).must_equal Geometry::Size[4, 6]
|
110
|
+
end
|
111
|
+
|
112
|
+
it 'must outset' do
|
113
|
+
subject.outset(4).must_equal Geometry::Size[14, 14]
|
114
|
+
subject.outset(2,3).must_equal Geometry::Size[12, 13]
|
115
|
+
subject.outset(x:2, y:3).must_equal Geometry::Size[12, 13]
|
116
|
+
subject.outset(left:2, top:3).must_equal Geometry::Size[12, 13]
|
117
|
+
subject.outset(right:2, bottom:3).must_equal Geometry::Size[12, 13]
|
118
|
+
end
|
119
|
+
|
120
|
+
it 'must inset with insets for top, left, bottom, right' do
|
121
|
+
subject.outset(top:1, left:2, bottom:3, right:4).must_equal Geometry::Size[16, 14]
|
122
|
+
end
|
97
123
|
end
|
data/test/geometry/square.rb
CHANGED
@@ -19,11 +19,43 @@ describe Geometry::Square do
|
|
19
19
|
|
20
20
|
it "must accept an origin Point and a size" do
|
21
21
|
square = Square.new origin:[1,2], size:5
|
22
|
-
square.must_be_kind_of Geometry::
|
22
|
+
square.must_be_kind_of Geometry::SizedSquare
|
23
23
|
square.origin.must_equal Point[1,2]
|
24
24
|
square.height.must_equal 5
|
25
25
|
square.width.must_equal 5
|
26
26
|
end
|
27
|
+
|
28
|
+
it 'must accept a center and a size' do
|
29
|
+
square = Square.new center:[1,2], size:5
|
30
|
+
square.must_be_kind_of Geometry::CenteredSquare
|
31
|
+
square.center.must_equal Point[1,2]
|
32
|
+
square.size.must_equal 5
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'must work with only a size parameter' do
|
36
|
+
square = Square.new size:5
|
37
|
+
square.must_be_kind_of Geometry::CenteredSquare
|
38
|
+
square.center.must_equal Point[0,0]
|
39
|
+
square.size.must_equal 5
|
40
|
+
end
|
41
|
+
|
42
|
+
it 'must deal with a non-numeric size' do
|
43
|
+
square = Square.new size:[5,5]
|
44
|
+
square.must_be_kind_of Geometry::CenteredSquare
|
45
|
+
square.center.must_equal Point[0,0]
|
46
|
+
square.size.must_equal 5
|
47
|
+
end
|
48
|
+
|
49
|
+
it 'must accept a Size size' do
|
50
|
+
square = Square.new size:Size[5,5]
|
51
|
+
square.must_be_kind_of Geometry::CenteredSquare
|
52
|
+
square.center.must_equal Point[0,0]
|
53
|
+
square.size.must_equal 5
|
54
|
+
end
|
55
|
+
|
56
|
+
it 'must reject a size parameter that is not square' do
|
57
|
+
-> { Square.new size:[1,2] }.must_raise Geometry::NotSquareError
|
58
|
+
end
|
27
59
|
end
|
28
60
|
|
29
61
|
describe "properties" do
|
@@ -32,6 +64,18 @@ describe Geometry::Square do
|
|
32
64
|
it "must have an origin accessor" do
|
33
65
|
subject.origin.must_equal Point[2,3]
|
34
66
|
end
|
67
|
+
|
68
|
+
it "must have a minmax property that returns the corners of the bounding rectangle" do
|
69
|
+
subject.minmax.must_equal [Point[2, 3], Point[3, 4]]
|
70
|
+
end
|
71
|
+
|
72
|
+
it "must have a max property that returns the upper right corner of the bounding rectangle" do
|
73
|
+
subject.max.must_equal Point[3, 4]
|
74
|
+
end
|
75
|
+
|
76
|
+
it "must have a min property that returns the lower left corner of the bounding rectangle" do
|
77
|
+
subject.min.must_equal Point[2, 3]
|
78
|
+
end
|
35
79
|
end
|
36
80
|
end
|
37
81
|
|
@@ -62,5 +106,59 @@ describe Geometry::CenteredSquare do
|
|
62
106
|
it "must have a width property" do
|
63
107
|
square.width.must_equal 4
|
64
108
|
end
|
109
|
+
|
110
|
+
it "must have a minmax property that returns the corners of the bounding rectangle" do
|
111
|
+
square.minmax.must_equal [Point[0, 1], Point[4, 5]]
|
112
|
+
end
|
113
|
+
|
114
|
+
it "must have a max property that returns the upper right corner of the bounding rectangle" do
|
115
|
+
square.max.must_equal Point[4, 5]
|
116
|
+
end
|
117
|
+
|
118
|
+
it "must have a min property that returns the lower left corner of the bounding rectangle" do
|
119
|
+
square.min.must_equal Point[0, 1]
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
describe Geometry::SizedSquare do
|
125
|
+
describe "when constructed" do
|
126
|
+
it "must create a SizedSquare from a point and a size" do
|
127
|
+
square = Geometry::SizedSquare.new [2,3], 5
|
128
|
+
square.must_be_instance_of Geometry::SizedSquare
|
129
|
+
square.must_be_kind_of Geometry::Square
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
describe "properties" do
|
134
|
+
let(:square) { Geometry::SizedSquare.new [2,3], 4 }
|
135
|
+
|
136
|
+
it "must have a center property" do
|
137
|
+
square.center.must_equal Point[4,5]
|
138
|
+
end
|
139
|
+
|
140
|
+
it "must have a points property" do
|
141
|
+
square.points.must_equal [Point[2,3], Point[6,3], Point[6,7], Point[2,7]]
|
142
|
+
end
|
143
|
+
|
144
|
+
it "must have a height property" do
|
145
|
+
square.height.must_equal 4
|
146
|
+
end
|
147
|
+
|
148
|
+
it "must have a width property" do
|
149
|
+
square.width.must_equal 4
|
150
|
+
end
|
151
|
+
|
152
|
+
it "must have a minmax property that returns the corners of the bounding rectangle" do
|
153
|
+
square.minmax.must_equal [Point[2, 3], Point[6, 7]]
|
154
|
+
end
|
155
|
+
|
156
|
+
it "must have a max property that returns the upper right corner of the bounding rectangle" do
|
157
|
+
square.max.must_equal Point[6, 7]
|
158
|
+
end
|
159
|
+
|
160
|
+
it "must have a min property that returns the lower left corner of the bounding rectangle" do
|
161
|
+
square.min.must_equal Point[2, 3]
|
162
|
+
end
|
65
163
|
end
|
66
164
|
end
|
@@ -84,6 +84,14 @@ describe Geometry::Transformation do
|
|
84
84
|
end
|
85
85
|
end
|
86
86
|
|
87
|
+
it 'must translate with a Point' do
|
88
|
+
Transformation.new(translate:[1,2]).translate(Point[3,4]).translation.must_equal Point[4,6]
|
89
|
+
end
|
90
|
+
|
91
|
+
it 'must translate with an Array' do
|
92
|
+
Transformation.new(translate:[1,2]).translate([3,4]).translation.must_equal Point[4,6]
|
93
|
+
end
|
94
|
+
|
87
95
|
describe "comparison" do
|
88
96
|
subject { Transformation.new(origin:[1,2]) }
|
89
97
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: geometry
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: '6.
|
4
|
+
version: '6.2'
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Brandon Fosdick
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-
|
11
|
+
date: 2014-04-21 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: Geometric primitives and algorithms for Ruby
|
14
14
|
email:
|
@@ -85,7 +85,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
85
85
|
version: '0'
|
86
86
|
requirements: []
|
87
87
|
rubyforge_project: geometry
|
88
|
-
rubygems_version: 2.
|
88
|
+
rubygems_version: 2.2.2
|
89
89
|
signing_key:
|
90
90
|
specification_version: 4
|
91
91
|
summary: Geometric primitives and algoritms
|