geometry 6.1 → 6.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|