geometry 6.1 → 6.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: ed229b8577bfe815ff25060d851e6f27515cf7a7
4
- data.tar.gz: 9ed183df010e991b72c216639acda3f2f68884d5
3
+ metadata.gz: c1d1683621a91df0a25df4e3f2cc4fe6ecdf3dfb
4
+ data.tar.gz: 597979326e97270c5fd7f037615f391e7f50fc18
5
5
  SHA512:
6
- metadata.gz: ea42b04b4aa1bc9f89f5d64cc2e96ff61a066bff34108d6b534e492da37dd69a7aa8163f5388bb46ae350c726926074d9a201480d642c376518bf3efc63c631b
7
- data.tar.gz: bafb8c4996b9017d28f43d7b56a0b1e253d738f20ade966aa196efca041513f2ebd502be560926a3404b9314c04ce3f13e4c8c382e2a1dd06d797062ada2c7ac
6
+ metadata.gz: 2fbe85bbe712dff07a728326e3aafc40325849f03cb995221b8a6f65e6f4b3a6ba6b10de21f13e0ed4b18b92664f92ecb1b99a67b013430173494a35449354b2
7
+ data.tar.gz: 5f1e1ddc4670af310745a74951eb6280bd4d515122ade09df4bc9cbbc40c6247fc5131c3ee7ef46f9d9ea4e707850e8462c03e458e6443e1793f374f950344d4
@@ -3,7 +3,7 @@ $:.push File.expand_path("../lib", __FILE__)
3
3
 
4
4
  Gem::Specification.new do |s|
5
5
  s.name = "geometry"
6
- s.version = '6.1'
6
+ s.version = '6.2'
7
7
  s.authors = ["Brandon Fosdick"]
8
8
  s.email = ["bfoz@bfoz.net"]
9
9
  s.homepage = "http://github.com/bfoz/geometry"
@@ -130,7 +130,7 @@ An edge. It's a line segment between 2 points. Generally part of a {Polygon}.
130
130
 
131
131
  # @return [Vector] A {Vector} pointing from first to last
132
132
  def vector
133
- Vector[*((last-first).to_a)]
133
+ last - first
134
134
  end
135
135
 
136
136
  def to_a
@@ -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
@@ -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 = @edges.pop # Remove the previous 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
- push_edge Edge.new(@edges.last.last, @edges.first.first) unless @edges.empty? || closed?
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
@@ -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 = array[0].to_a unless array[0].is_a?(Numeric)
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
@@ -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
- square = Square.new from:[1,2], to:[2,3] # Using two corners
12
- square = Square.new origin:[3,4], size:5 # Using an origin point and a size
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
@@ -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 }
@@ -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
@@ -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::Square
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.1'
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-01-13 00:00:00.000000000 Z
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.1.11
88
+ rubygems_version: 2.2.2
89
89
  signing_key:
90
90
  specification_version: 4
91
91
  summary: Geometric primitives and algoritms