geometry 6.5 → 6.6

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