geometry 6.5 → 6.6

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,15 +1,7 @@
1
1
  ---
2
- !binary "U0hBMQ==":
3
- metadata.gz: !binary |-
4
- YTE5MmE5YjU5ZDBkOTZiNGY0OGI5NzU5NDU4Yzk1OTk3Y2VjNDBhYw==
5
- data.tar.gz: !binary |-
6
- N2QyODkzMTkwYzhkZjhmYjg2MjliOTViYTM4ZjkxN2JkMDA4NDAyOQ==
2
+ SHA256:
3
+ metadata.gz: 18aa25c45e739b77377ac376b9575f9828e2eb0566b4349a073be5b4deeea20e
4
+ data.tar.gz: 1ddbf89f0ad3854c222ffd344c0eaedd129d315ec9b1488f503f519dc8b5ca5b
7
5
  SHA512:
8
- metadata.gz: !binary |-
9
- ODc3NmE4ZDEzMDg0YjVhNGI0YmU0YjY4NTNkMTM3Mjk4NDFkYjRlMDZiZjkw
10
- Y2M2ZWNmYTc1NzU2ODFkN2Y2NGI3NTYzYzE2NTcwY2IwMWVlODYzM2U1NmIx
11
- NzNmYjM3OTEyMDY3NGVhZjU3NTZiNzY1YmQ1ZDVmODEzODE3N2I=
12
- data.tar.gz: !binary |-
13
- ZDU2NTJmODQ1ZTAyMzc2NGJiNjFhM2E4NTBhNWIxNjY5M2JjNThhNWJjN2Rm
14
- MjAwNTYwMjlkMWE1N2UyNTMwZTMxNWZkNDc5NTU3NzhiNDlhOTdjMmY0NzBk
15
- ZWRiNmVlMDgwZjVjNzY5YjM0ZTMyM2U4M2UzOGU0MzIzYmVkMjM=
6
+ metadata.gz: 69645a43932bb7b4c36a6401daabf49d2bbcbf3450f750f85d1c2f60f8d1e7944cb0f1c5110569912bc10d656f3ef8a1f5ffaa80ee9eb228a8a8f7855aaed0c8
7
+ data.tar.gz: 8e68aa085655950d9f499a887cf9ec0aeaac2c183e53a242675105f093652fdcb141aae4cc91226bedadf97e0dd94651b25956026c78c4a98e60b57d7b232dfa
@@ -0,0 +1,21 @@
1
+ name: Ruby
2
+
3
+ on: [push]
4
+
5
+ jobs:
6
+ test:
7
+ strategy:
8
+ fail-fast: false
9
+ matrix:
10
+ ruby-version: ['3.0', '3.1', '3.2', '3.3']
11
+ os: [ubuntu-latest]
12
+
13
+ runs-on: ${{ matrix.os }}
14
+
15
+ steps:
16
+ - uses: actions/checkout@v4
17
+ - uses: ruby/setup-ruby@v1
18
+ with:
19
+ ruby-version: ${{ matrix.ruby-version }}
20
+ bundler-cache: true
21
+ - run: bundle exec rake
data/Gemfile CHANGED
@@ -3,5 +3,8 @@ source "http://rubygems.org"
3
3
  gemspec
4
4
 
5
5
  group :test do
6
+ gem 'minitest'
6
7
  gem 'rake'
7
8
  end
9
+
10
+ gem "matrix", "~> 0.4.2"
data/README.markdown CHANGED
@@ -1,7 +1,6 @@
1
1
  Geometry for Ruby
2
2
  =================
3
3
 
4
- [![Build Status](https://travis-ci.org/bfoz/geometry.png)](https://travis-ci.org/bfoz/geometry)
5
4
  [![Gem Version](https://badge.fury.io/rb/geometry.svg)](http://badge.fury.io/rb/geometry)
6
5
 
7
6
  Classes and methods for the handling of all of the basic geometry that you
@@ -15,7 +14,7 @@ that don't work in higher dimensions and I'll do my best to fix them.
15
14
  License
16
15
  -------
17
16
 
18
- Copyright 2012-2014 Brandon Fosdick <bfoz@bfoz.net> and released under the BSD license.
17
+ Copyright 2012-2024 Brandon Fosdick <bfoz@bfoz.net> and released under the BSD license.
19
18
 
20
19
  Primitives
21
20
  ----------
data/geometry.gemspec CHANGED
@@ -2,8 +2,9 @@
2
2
  $:.push File.expand_path("../lib", __FILE__)
3
3
 
4
4
  Gem::Specification.new do |s|
5
+ spec = s
5
6
  s.name = "geometry"
6
- s.version = '6.5'
7
+ spec.version = '6.6'
7
8
  s.authors = ["Brandon Fosdick"]
8
9
  s.email = ["bfoz@bfoz.net"]
9
10
  s.homepage = "http://github.com/bfoz/geometry"
@@ -18,4 +19,7 @@ Gem::Specification.new do |s|
18
19
  s.require_paths = ["lib"]
19
20
 
20
21
  s.required_ruby_version = '>= 2.0'
22
+
23
+ spec.add_development_dependency "bundler", "~> 2"
24
+ spec.add_development_dependency "rake", "~> 13"
21
25
  end
@@ -22,7 +22,7 @@ known as a Ring, is a circle that ate another circle.
22
22
  # @!attribute inner_radius
23
23
  # @return [Number] the radius of the inside of the {Annulus}
24
24
  def inner_radius
25
- @inner_radius || (@inner_diameter && @inner_diameter/2)
25
+ @inner_radius || (@inner_diameter && @inner_diameter.to_r/2)
26
26
  end
27
27
 
28
28
  # @!attribute outer_diameter
@@ -34,7 +34,7 @@ known as a Ring, is a circle that ate another circle.
34
34
  # @!attribute outer_radius
35
35
  # @return [Number] the outer radius
36
36
  def outer_radius
37
- @outer_radius || (@outer_diameter && @outer_diameter/2)
37
+ @outer_radius || ((@outer_diameter && @outer_diameter).to_r/2)
38
38
  end
39
39
 
40
40
  # @!attribute diameter
data/lib/geometry/arc.rb CHANGED
@@ -17,8 +17,15 @@ An {Arc} with its center at [1,1] and a radius of 2 that starts at the X-axis an
17
17
  include ClusterFactory
18
18
 
19
19
  attr_reader :center
20
+
21
+ # @return [Number] the radius of the {Arc}
20
22
  attr_reader :radius
21
- attr_reader :start_angle, :end_angle
23
+
24
+ # @return [Number] the starting angle of the {Arc} as radians from the x-axis
25
+ attr_reader :start_angle
26
+
27
+ # @return [Number] the ending angle of the {Arc} as radians from the x-axis
28
+ attr_reader :end_angle
22
29
 
23
30
  # @overload new(center, start, end)
24
31
  # Create a new {Arc} given center, start and end {Point}s
@@ -90,5 +97,69 @@ An {Arc} with its center at [1,1] and a radius of 2 that starts at the X-axis an
90
97
  # The end point of the {Arc}
91
98
  # @return [Point]
92
99
  alias :last :end
100
+
101
+ def ==(other)
102
+ if other.is_a?(ThreePointArc)
103
+ (self.center == other.center) && (self.end == other.end) && (self.start == other.start)
104
+ else
105
+ super other
106
+ end
107
+ end
108
+
109
+ # @group Attributes
110
+
111
+ # @return [Point] The upper-right corner of the bounding rectangle that encloses the {Path}
112
+ def max
113
+ minmax.last
114
+ end
115
+
116
+ # @return [Point] The lower-left corner of the bounding rectangle that encloses the {Path}
117
+ def min
118
+ minmax.first
119
+ end
120
+
121
+ # @return [Array<Point>] The lower-left and upper-right corners of the enclosing bounding rectangle
122
+ def minmax
123
+ a = [self.start, self.end]
124
+ quadrants = a.map(&:quadrant)
125
+
126
+ # If the Arc spans more than one quadrant, then it must cross at
127
+ # least one axis. Each axis-crossing is a potential extrema.
128
+ if quadrants.first != quadrants.last
129
+ range = (quadrants.first...quadrants.last)
130
+ # If the Arc crosses the X axis...
131
+ if quadrants.first > quadrants.last
132
+ range = (quadrants.first..4).to_a + (1...quadrants.last).to_a
133
+ end
134
+
135
+ a = range.map do |q|
136
+ case q
137
+ when 1 then self.center + Point[0,radius]
138
+ when 2 then self.center + Point[-radius, 0]
139
+ when 3 then self.center + Point[0,-radius]
140
+ when 4 then self.center + Point[radius,0]
141
+ end
142
+ end.push(*a)
143
+ a.reduce([a.first, a.first]) {|memo, e| [memo.first.min(e), memo.last.max(e)] }
144
+ else
145
+ [a.first.min(a.last), a.first.max(a.last)]
146
+ end
147
+ end
148
+
149
+ def end_angle
150
+ a = (self.end - self.center)
151
+ Math.atan2(a.y, a.x)
152
+ end
153
+
154
+ def radius
155
+ (self.start - self.center).magnitude
156
+ end
157
+
158
+ def start_angle
159
+ a = (self.start - self.center)
160
+ Math.atan2(a.y, a.x)
161
+ end
162
+
163
+ # @endgroup
93
164
  end
94
165
  end
data/lib/geometry/edge.rb CHANGED
@@ -1,5 +1,3 @@
1
- require 'mathn'
2
-
3
1
  require_relative 'point'
4
2
 
5
3
  module Geometry
@@ -42,6 +40,25 @@ An edge. It's a line segment between 2 points. Generally part of a {Polygon}.
42
40
  end
43
41
  end
44
42
 
43
+ # @group Attributes
44
+
45
+ # @return [Point] The upper-right corner of the bounding rectangle that encloses the {Edge}
46
+ def max
47
+ first.max(last)
48
+ end
49
+
50
+ # @return [Point] The lower-left corner of the bounding rectangle that encloses the {Edge}
51
+ def min
52
+ first.min(last)
53
+ end
54
+
55
+ # @return [Array<Point>] The lower-left and upper-right corners of the enclosing bounding rectangle
56
+ def minmax
57
+ first.minmax(last)
58
+ end
59
+
60
+ # @endgroup
61
+
45
62
  # Return a new {Edge} with swapped endpoints
46
63
  def reverse
47
64
  self.class.new(@last, @first)
@@ -128,8 +145,8 @@ An edge. It's a line segment between 2 points. Generally part of a {Polygon}.
128
145
  nil
129
146
  end
130
147
  else
131
- s = (-v1[1] * p.x + v1[0] * p.y) / denominator # v1 x (p0 - p2) / denominator
132
- t = ( v2[0] * p.y - v2[1] * p.x) / denominator # v2 x (p0 - p2) / denominator
148
+ s = (-v1[1] * p.x + v1[0] * p.y).to_r / denominator # v1 x (p0 - p2) / denominator
149
+ t = ( v2[0] * p.y - v2[1] * p.x).to_r / denominator # v2 x (p0 - p2) / denominator
133
150
 
134
151
  p0 + v1 * t if ((0..1) === s) && ((0..1) === t)
135
152
  end
data/lib/geometry/line.rb CHANGED
@@ -190,7 +190,7 @@ Supports two-point, slope-intercept, and point-slope initializer forms
190
190
  ((other.first.y == slope * other.first.x + intercept)) && (other.last.y == (slope * other.last.x + intercept))
191
191
  else
192
192
  self.eql? other
193
- end
193
+ end
194
194
  end
195
195
 
196
196
  # Two {SlopeInterceptLine}s are equal if both have equal slopes and intercepts
@@ -251,7 +251,7 @@ Supports two-point, slope-intercept, and point-slope initializer forms
251
251
  ((first.y == other.slope * first.x + other.intercept)) && (last.y == (other.slope * last.x + other.intercept))
252
252
  else
253
253
  self.eql?(other) || ((first == other.last) && (last == other.first))
254
- end
254
+ end
255
255
  end
256
256
 
257
257
  # Two {TwoPointLine}s are equal if both have equal endpoints
@@ -180,8 +180,6 @@ The {Obround} class cluster represents a rectangle with semicircular end caps
180
180
  end
181
181
 
182
182
  class SizedObround < Obround
183
- # @return [Point] The {Obround}'s center
184
- attr_reader :center
185
183
  # @return [Point] The {Obround}'s origin
186
184
  attr_accessor :origin
187
185
  # @return [Size] The {Size} of the {Obround}
@@ -219,6 +217,7 @@ The {Obround} class cluster represents a rectangle with semicircular end caps
219
217
  alias :== :eql?
220
218
 
221
219
  # @group Accessors
220
+ # @return [Point] The {Obround}'s center
222
221
  def center
223
222
  @origin + @size/2
224
223
  end
data/lib/geometry/path.rb CHANGED
@@ -52,11 +52,38 @@ An object representing a set of connected elements, each of which could be an
52
52
  end
53
53
  end
54
54
 
55
+ def ==(other)
56
+ if other.is_a?(Path)
57
+ @elements == other.elements
58
+ else
59
+ super other
60
+ end
61
+ end
62
+
63
+ # @group Attributes
64
+
65
+ # @return [Point] The upper-right corner of the bounding rectangle that encloses the {Path}
66
+ def max
67
+ elements.reduce(elements.first.max) {|memo, e| memo.max(e.max) }
68
+ end
69
+
70
+ # @return [Point] The lower-left corner of the bounding rectangle that encloses the {Path}
71
+ def min
72
+ elements.reduce(elements.first.min) {|memo, e| memo.min(e.max) }
73
+ end
74
+
75
+ # @return [Array<Point>] The lower-left and upper-right corners of the enclosing bounding rectangle
76
+ def minmax
77
+ elements.reduce(elements.first.minmax) {|memo, e| [memo.first.min(e.min), memo.last.max(e.max)] }
78
+ end
79
+
55
80
  # @return [Geometry] The last element in the {Path}
56
81
  def last
57
82
  @elements.last
58
83
  end
59
84
 
85
+ # @endgroup
86
+
60
87
  # Append a new geometry element to the {Path}
61
88
  # @return [Path]
62
89
  def push(arg)
@@ -21,8 +21,6 @@ geometry class (x, y, z).
21
21
  point = Geometry::Point[x,y]
22
22
  =end
23
23
  class Point < Vector
24
- attr_reader :x, :y, :z
25
-
26
24
  # Allow vector-style initialization, but override to support copy-init
27
25
  # from Vector or another Point
28
26
  #
@@ -34,15 +32,15 @@ geometry class (x, y, z).
34
32
  return array[0] if array[0].is_a?(Point)
35
33
  array = array[0] if array[0].is_a?(Array)
36
34
  array = array[0].to_a if array[0].is_a?(Vector)
37
- super *array
35
+ super(*array)
38
36
  end
39
37
 
40
- # Creates and returns a new {PointIso} instance. Or, a {Point} full of ones if the size argument is given.
38
+ # Creates and returns a new {PointIso} instance. Or, a {Point} full of the given value if the size argument is given.
41
39
  # @param value [Number] the value of the elements
42
40
  # @param size [Number] the size of the new {Point} full of ones
43
41
  # @return [PointIso] A new {PointIso} instance
44
42
  def self.iso(value, size=nil)
45
- size ? Point[Array.new(size, 1)] : PointIso.new(value)
43
+ size ? Point[Array.new(size, value)] : PointIso.new(value)
46
44
  end
47
45
 
48
46
  # Creates and returns a new {PointOne} instance. Or, a {Point} full of ones if the size argument is given.
@@ -119,6 +117,8 @@ geometry class (x, y, z).
119
117
  'Point' + @elements.to_s
120
118
  end
121
119
 
120
+ # @group Attributes
121
+
122
122
  # @override max()
123
123
  # @return [Number] The maximum value of the {Point}'s elements
124
124
  # @override max(point)
@@ -169,6 +169,22 @@ geometry class (x, y, z).
169
169
  end
170
170
  end
171
171
 
172
+ # Return the {Point}'s quadrant in the 2D Cartesian Euclidean Plane
173
+ # https://en.wikipedia.org/wiki/Quadrant_(plane_geometry)
174
+ # @note Undefined for all points on the axes, and for dimensionalities other than 2
175
+ # @todo Define the results for points on the axes
176
+ # @return [Bool] The {Point}'s quadrant in the 2D Cartesian Euclidean Plane
177
+ def quadrant
178
+ return nil unless elements[1]
179
+ if elements.first > 0
180
+ (elements[1] > 0) ? 1 : 4
181
+ else
182
+ (elements[1] > 0) ? 2 : 3
183
+ end
184
+ end
185
+
186
+ # @endgroup
187
+
172
188
  # Returns a new {Point} with the given number of elements removed from the end
173
189
  # @return [Point] the popped elements
174
190
  def pop(count=1)
@@ -298,4 +314,3 @@ geometry class (x, y, z).
298
314
 
299
315
  end
300
316
  end
301
-
@@ -1,5 +1,3 @@
1
- require_relative 'point'
2
-
3
1
  module Geometry
4
2
  =begin rdoc
5
3
  An object repesenting a N-dimensional {Point} with identical elements.
@@ -1,5 +1,3 @@
1
- require_relative 'point'
2
-
3
1
  module Geometry
4
2
  =begin rdoc
5
3
  An object repesenting a {Point} that is one unit away from the origin, along each
@@ -1,4 +1,3 @@
1
- require_relative 'point'
2
1
  require_relative 'point_iso'
3
2
 
4
3
  module Geometry
@@ -46,7 +46,7 @@ but there's currently nothing that enforces simplicity.
46
46
 
47
47
  # @return [Polygon] A new {Polygon} with orientation that's the opposite of the receiver
48
48
  def reverse
49
- self.class.new *(self.vertices.reverse)
49
+ self.class.new(*(self.vertices.reverse))
50
50
  end
51
51
 
52
52
  # Reverse the receiver and return it
@@ -68,7 +68,7 @@ but there's currently nothing that enforces simplicity.
68
68
  # @param [Point] point The {Point} to test
69
69
  # @return [Number] 1 if the {Point} is inside the {Polygon}, -1 if it's outside, and 0 if it's on an {Edge}
70
70
  def <=>(point)
71
- sum = edges.reduce(0) do |sum, e|
71
+ winding = edges.reduce(0) do |sum, e|
72
72
  direction = e.last.y <=> e.first.y
73
73
  # Ignore edges that don't cross the point's x coordinate
74
74
  next sum unless ((point.y <=> e.last.y) + (point.y <=> e.first.y)).abs <= 1
@@ -83,7 +83,7 @@ but there's currently nothing that enforces simplicity.
83
83
  sum += 0 <=> (direction + is_left)
84
84
  end
85
85
  end
86
- (0 == sum) ? -1 : 1
86
+ (0 == winding) ? -1 : 1
87
87
  end
88
88
 
89
89
  # Create a new {Polygon} that's the union of the receiver and a passed {Polygon}
@@ -152,11 +152,11 @@ but there's currently nothing that enforces simplicity.
152
152
  edgeFragments = edgeFragments.reject {|f| edgeFragments.find {|f2| (f[:first] == f2[:last]) and (f[:last] == f2[:first])} }
153
153
 
154
154
  # Construct the output polygons
155
- output = edgeFragments.reduce([Array.new]) do |output, fragment|
155
+ output_polygons = edgeFragments.reduce([Array.new]) do |output, fragment|
156
156
  next output if fragment.empty?
157
157
  polygon = output.last
158
158
  polygon.push fragment[:first], fragment[:last] if polygon.empty?
159
- while 1 do
159
+ loop do
160
160
  adjacent_fragment = edgeFragments.find {|f| fragment[:last] == f[:first]}
161
161
  break unless adjacent_fragment
162
162
 
@@ -170,13 +170,13 @@ but there's currently nothing that enforces simplicity.
170
170
  end
171
171
 
172
172
  # If everything worked properly there should be only one output Polygon
173
- output.reject! {|a| a.empty?}
174
- output = Polygon.new *(output[0])
173
+ output_polygons.reject! {|a| a.empty?}
174
+ output_polygons = Polygon.new(*(output_polygons[0]))
175
175
 
176
176
  # Table 4: Both input polygons are "island" type and the operation
177
177
  # is union, so the output polygon's orientation should be the same
178
178
  # as the input polygon's orientation
179
- (self.clockwise? != output.clockwise?) ? output.reverse : output
179
+ (self.clockwise? != output_polygons.clockwise?) ? output_polygons.reverse : output_polygons
180
180
  end
181
181
  alias :+ :union
182
182
 
@@ -213,7 +213,7 @@ but there's currently nothing that enforces simplicity.
213
213
  break if current_point == hull_points.first
214
214
  hull_points << min_point
215
215
  end
216
- Polygon.new *hull_points
216
+ Polygon.new(*hull_points)
217
217
  end
218
218
 
219
219
  # @endgroup
@@ -278,7 +278,7 @@ but there's currently nothing that enforces simplicity.
278
278
  redo # Recheck the modified edges
279
279
  end
280
280
  end
281
- Polygon.new *(active_edges.map {|e| e[:edge]}.compact.map {|e| [e.first, e.last]}.flatten)
281
+ Polygon.new(*(active_edges.map {|e| e[:edge]}.compact.map {|e| [e.first, e.last]}.flatten))
282
282
  end
283
283
 
284
284
  # Vertex bisectors suitable for outsetting
@@ -332,9 +332,10 @@ but there's currently nothing that enforces simplicity.
332
332
  # @param [Integer] index The index to insert the new {Point} before
333
333
  # @param [Point] point The {Point} to insert
334
334
  # @param [Integer] type The vertex type: 1 is inside, 0 is boundary, -1 is outside
335
+ # @return [Bool] true if the {Point} was inserted, false otherwise
335
336
  def insert(index, point, type)
336
- if v = @vertices.find {|v| v[:vertex] == point }
337
- v[:type] = type
337
+ if vertex = @vertices.find {|v| v[:vertex] == point }
338
+ vertex[:type] = type
338
339
  false
339
340
  else
340
341
  @vertices.insert(index, {:vertex => point, :type => type})
@@ -41,7 +41,7 @@ also like a {Path} in that it isn't necessarily closed.
41
41
  @vertices.push first
42
42
  elsif first.is_a?(Edge)
43
43
  @edges.push first
44
- @vertices.push *(first.to_a)
44
+ @vertices.push(*(first.to_a))
45
45
  end
46
46
 
47
47
  args.reduce(@vertices.last) do |previous,n|
@@ -82,7 +82,7 @@ also like a {Path} in that it isn't necessarily closed.
82
82
  else
83
83
  e = Edge.new(previous, n.first)
84
84
  push_edge e, n
85
- push_vertex *(e.to_a), *(n.to_a)
85
+ push_vertex(*(e.to_a), *(n.to_a))
86
86
  end
87
87
  n.last
88
88
  end
@@ -167,7 +167,7 @@ also like a {Path} in that it isn't necessarily closed.
167
167
  # Clone the receiver, reverse it, then return it
168
168
  # @return [Polyline] the reversed clone
169
169
  def reverse
170
- self.class.new *(edges.reverse.map! {|edge| edge.reverse! })
170
+ self.class.new(*(edges.reverse.map! {|edge| edge.reverse! }))
171
171
  end
172
172
 
173
173
  # Reverse the receiver and return it
@@ -261,7 +261,7 @@ also like a {Path} in that it isn't necessarily closed.
261
261
  redo # Recheck the modified edges
262
262
  end
263
263
  end
264
- Polyline.new *(active_edges.map {|e| e[:edge]}.compact.map {|e| [e.first, e.last]}.flatten)
264
+ Polyline.new(*(active_edges.map {|e| e[:edge]}.compact.map {|e| [e.first, e.last]}.flatten))
265
265
  end
266
266
  alias :leftset :offset
267
267
 
@@ -369,12 +369,12 @@ also like a {Path} in that it isn't necessarily closed.
369
369
  end
370
370
 
371
371
  def push_edge(*e)
372
- @edges.push *e
372
+ @edges.push(*e)
373
373
  @edges.uniq!
374
374
  end
375
375
 
376
376
  def push_vertex(*v)
377
- @vertices.push *v
377
+ @vertices.push(*v)
378
378
  @vertices.uniq!
379
379
  end
380
380
 
@@ -25,16 +25,8 @@ The {Rectangle} class cluster represents your typical arrangement of 4 corners a
25
25
  class Rectangle
26
26
  include ClusterFactory
27
27
 
28
- # @return [Point] The {Rectangle}'s center
29
- attr_reader :center
30
- # @return [Number] Height of the {Rectangle}
31
- attr_reader :height
32
- # @return [Point] The {Rectangle}'s origin
33
- attr_reader :origin
34
28
  # @return [Size] The {Size} of the {Rectangle}
35
29
  attr_reader :size
36
- # @return [Number] Width of the {Rectangle}
37
- attr_reader :width
38
30
 
39
31
  # @overload new(width, height)
40
32
  # Creates a {Rectangle} of the given width and height, centered on the origin
@@ -120,7 +112,7 @@ The {Rectangle} class cluster represents your typical arrangement of 4 corners a
120
112
  # @return [Point] The {Rectangle}'s center
121
113
  def center
122
114
  min, max = @points.minmax {|a,b| a.y <=> b.y}
123
- Point[(max.x+min.x)/2, (max.y+min.y)/2]
115
+ Point[(max.x+min.x).to_r/2, (max.y+min.y).to_r/2]
124
116
  end
125
117
 
126
118
  # @!attribute closed?
@@ -155,25 +147,28 @@ The {Rectangle} class cluster represents your typical arrangement of 4 corners a
155
147
  [self.min, self.max]
156
148
  end
157
149
 
158
- # @return [Array<Point>] The {Rectangle}'s four points (counterclockwise)
150
+ # @return [Array<Point>] The {Rectangle}'s four points (clockwise)
159
151
  def points
160
152
  point0, point2 = *@points
161
- point1 = Point[point2.x, point0.y]
162
- point3 = Point[point0.x, point2.y]
153
+ point1 = Point[point0.x, point2.y]
154
+ point3 = Point[point2.x, point0.y]
163
155
  [point0, point1, point2, point3]
164
156
  end
165
157
 
158
+ # @return [Point] The {Rectangle}'s origin
166
159
  def origin
167
160
  minx = @points.min {|a,b| a.x <=> b.x}
168
161
  miny = @points.min {|a,b| a.y <=> b.y}
169
162
  Point[minx.x, miny.y]
170
163
  end
171
164
 
165
+ # @return [Number] Height of the {Rectangle}
172
166
  def height
173
167
  min, max = @points.minmax {|a,b| a.y <=> b.y}
174
168
  max.y - min.y
175
169
  end
176
170
 
171
+ # @return [Number] Width of the {Rectangle}
177
172
  def width
178
173
  min, max = @points.minmax {|a,b| a.x <=> b.x}
179
174
  max.x - min.x
@@ -212,6 +207,11 @@ The {Rectangle} class cluster represents your typical arrangement of 4 corners a
212
207
  Rectangle.new from:(min + Point[options[:left], options[:bottom]]), to:(max - Point[options[:right], options[:top]])
213
208
  end
214
209
  end
210
+
211
+ # @return [Path] A closed {Path} that traces the boundary of the {Rectangle} clockwise, starting from the lower-left
212
+ def path
213
+ Path.new(*self.points, self.points.first)
214
+ end
215
215
  end
216
216
 
217
217
  class CenteredRectangle < Rectangle
@@ -72,16 +72,18 @@ A generalized representation of a rotation transformation.
72
72
  alias :== :eql?
73
73
 
74
74
  def identity?
75
- (!@x && !@y && !@z) || ([@x, @y, @z].select {|a| a}.all? {|a| a.respond_to?(:magnitude) ? (1 == a.magnitude) : (1 == a.size)})
75
+ x, y, z = self.x, self.y, self.z
76
+ (!x && !y && !z) || ([x, y, z].select {|a| a}.all? {|a| a.respond_to?(:magnitude) ? (1 == a.magnitude) : (1 == a.size)})
76
77
  end
77
78
 
78
79
  # @attribute [r] matrix
79
80
  # @return [Matrix] the transformation {Matrix} representing the {Rotation}
80
81
  def matrix
81
- return nil unless [@x, @y, @z].compact.size >= 2
82
+ x, y, z = self.x, self.y, self.z
83
+ return nil unless [x, y, z].compact.size >= 2
82
84
 
83
85
  # Force all axes to be Vectors
84
- x,y,z = [@x, @y, @z].map {|a| a.is_a?(Array) ? Vector[*a] : a}
86
+ x,y,z = [x, y, z].map {|a| a.is_a?(Array) ? Vector[*a] : a}
85
87
 
86
88
  # Force all axes to exist
87
89
  if x and y
data/lib/geometry/size.rb CHANGED
@@ -17,8 +17,6 @@ methods (width, height and depth).
17
17
  =end
18
18
 
19
19
  class Size < Vector
20
- attr_reader :x, :y, :z
21
-
22
20
  # Allow vector-style initialization, but override to support copy-init
23
21
  # from Vector, Point or another Size
24
22
  #
@@ -30,7 +28,7 @@ methods (width, height and depth).
30
28
  def self.[](*array)
31
29
  array.map! {|a| a.respond_to?(:to_a) ? a.to_a : a }
32
30
  array.flatten!
33
- super *array
31
+ super(*array)
34
32
  end
35
33
 
36
34
  # Creates and returns a new {SizeOne} instance. Or, a {Size} full of ones if the size argument is given.