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