geometry 6.4 → 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 +5 -5
- data/.github/workflows/ruby.yml +21 -0
- data/Gemfile +3 -0
- data/README.markdown +1 -2
- data/geometry.gemspec +5 -1
- data/lib/geometry/annulus.rb +20 -2
- data/lib/geometry/arc.rb +72 -1
- data/lib/geometry/edge.rb +21 -4
- data/lib/geometry/line.rb +84 -18
- data/lib/geometry/obround.rb +1 -2
- data/lib/geometry/path.rb +27 -0
- data/lib/geometry/point.rb +59 -8
- data/lib/geometry/point_iso.rb +12 -2
- data/lib/geometry/point_one.rb +44 -34
- data/lib/geometry/point_zero.rb +12 -1
- data/lib/geometry/polygon.rb +13 -12
- data/lib/geometry/polyline.rb +6 -6
- data/lib/geometry/rectangle.rb +12 -12
- data/lib/geometry/rotation.rb +5 -3
- data/lib/geometry/size.rb +1 -3
- data/lib/geometry/square.rb +13 -11
- data/lib/geometry/transformation/composition.rb +1 -1
- data/lib/geometry/transformation.rb +15 -0
- data/lib/geometry/triangle.rb +1 -1
- data/test/geometry/annulus.rb +12 -0
- data/test/geometry/arc.rb +98 -0
- data/test/geometry/circle.rb +3 -3
- data/test/geometry/edge.rb +16 -2
- data/test/geometry/line.rb +73 -0
- data/test/geometry/path.rb +16 -0
- data/test/geometry/point.rb +78 -2
- data/test/geometry/point_iso.rb +31 -21
- data/test/geometry/point_one.rb +11 -1
- data/test/geometry/point_zero.rb +46 -36
- data/test/geometry/polygon.rb +1 -1
- data/test/geometry/rectangle.rb +6 -1
- data/test/geometry/rotation.rb +1 -1
- data/test/geometry/size_one.rb +1 -1
- data/test/geometry/size_zero.rb +1 -1
- data/test/geometry/square.rb +15 -10
- data/test/geometry/transformation.rb +15 -1
- data/test/geometry/vector.rb +1 -1
- metadata +36 -9
- data/.travis.yml +0 -12
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 18aa25c45e739b77377ac376b9575f9828e2eb0566b4349a073be5b4deeea20e
|
4
|
+
data.tar.gz: 1ddbf89f0ad3854c222ffd344c0eaedd129d315ec9b1488f503f519dc8b5ca5b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
data/README.markdown
CHANGED
@@ -1,7 +1,6 @@
|
|
1
1
|
Geometry for Ruby
|
2
2
|
=================
|
3
3
|
|
4
|
-
[](https://travis-ci.org/bfoz/geometry)
|
5
4
|
[](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-
|
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
|
-
|
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
|
data/lib/geometry/annulus.rb
CHANGED
@@ -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
|
@@ -66,6 +66,24 @@ known as a Ring, is a circle that ate another circle.
|
|
66
66
|
@outer_diameter = options[:outer_diameter] || options[:diameter]
|
67
67
|
@outer_radius = options[:outer_radius] || options[:radius]
|
68
68
|
end
|
69
|
+
|
70
|
+
# @!attribute max
|
71
|
+
# @return [Point] The upper right corner of the bounding {Rectangle}
|
72
|
+
def max
|
73
|
+
@center+radius
|
74
|
+
end
|
75
|
+
|
76
|
+
# @!attribute min
|
77
|
+
# @return [Point] The lower left corner of the bounding {Rectangle}
|
78
|
+
def min
|
79
|
+
@center-radius
|
80
|
+
end
|
81
|
+
|
82
|
+
# @!attribute minmax
|
83
|
+
# @return [Array<Point>] The lower left and upper right corners of the bounding {Rectangle}
|
84
|
+
def minmax
|
85
|
+
[self.min, self.max]
|
86
|
+
end
|
69
87
|
end
|
70
88
|
|
71
89
|
# Ring is an alias of Annulus because that's the word that most people use,
|
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
|
-
|
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
@@ -31,6 +31,15 @@ Supports two-point, slope-intercept, and point-slope initializer forms
|
|
31
31
|
class Line
|
32
32
|
include ClusterFactory
|
33
33
|
|
34
|
+
# @!attribute [r] horizontal?
|
35
|
+
# @return [Boolean] true if the slope is zero
|
36
|
+
|
37
|
+
# @!attribute [r] slope
|
38
|
+
# @return [Number] the slope of the {Line}
|
39
|
+
|
40
|
+
# @!attribute [r] vertical?
|
41
|
+
# @return [Boolean] true if the slope is infinite
|
42
|
+
|
34
43
|
# @overload [](Array, Array)
|
35
44
|
# @return [TwoPointLine]
|
36
45
|
# @overload [](Point, Point)
|
@@ -85,15 +94,34 @@ Supports two-point, slope-intercept, and point-slope initializer forms
|
|
85
94
|
end
|
86
95
|
end
|
87
96
|
|
97
|
+
module SlopedLine
|
98
|
+
# @!attribute slope
|
99
|
+
# @return [Number] the slope of the {Line}
|
100
|
+
attr_reader :slope
|
101
|
+
|
102
|
+
# @!attribute horizontal?
|
103
|
+
# @return [Boolean] true if the slope is zero
|
104
|
+
def horizontal?
|
105
|
+
slope.zero?
|
106
|
+
end
|
107
|
+
|
108
|
+
# @!attribute vertical?
|
109
|
+
# @return [Boolean] true if the slope is infinite
|
110
|
+
def vertical?
|
111
|
+
slope.infinite? != nil
|
112
|
+
rescue # Non-Float's don't have an infinite? method
|
113
|
+
false
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
88
117
|
# @private
|
89
118
|
class PointSlopeLine < Line
|
119
|
+
include SlopedLine
|
120
|
+
|
90
121
|
# @!attribute point
|
91
122
|
# @return [Point] the stating point
|
92
123
|
attr_reader :point
|
93
124
|
|
94
|
-
# @return [Number] the slope of the {Line}
|
95
|
-
attr_reader :slope
|
96
|
-
|
97
125
|
# @param point [Point] a {Point} that lies on the {Line}
|
98
126
|
# @param slope [Number] the slope of the {Line}
|
99
127
|
def initialize(point, slope)
|
@@ -126,12 +154,23 @@ Supports two-point, slope-intercept, and point-slope initializer forms
|
|
126
154
|
def to_s
|
127
155
|
'Line(' + @slope.to_s + ',' + @point.to_s + ')'
|
128
156
|
end
|
157
|
+
|
158
|
+
# Find the requested axis intercept
|
159
|
+
# @param axis [Symbol] the axis to intercept (either :x or :y)
|
160
|
+
# @return [Number] the location of the intercept
|
161
|
+
def intercept(axis=:y)
|
162
|
+
case axis
|
163
|
+
when :x
|
164
|
+
vertical? ? point.x : (horizontal? ? nil : (slope * point.x - point.y))
|
165
|
+
when :y
|
166
|
+
vertical? ? nil : (horizontal? ? point.y : (point.y - slope * point.x))
|
167
|
+
end
|
168
|
+
end
|
129
169
|
end
|
130
170
|
|
131
171
|
# @private
|
132
172
|
class SlopeInterceptLine < Line
|
133
|
-
|
134
|
-
attr_reader :slope
|
173
|
+
include SlopedLine
|
135
174
|
|
136
175
|
# @param slope [Number] the slope
|
137
176
|
# @param intercept [Number] the location of the y-axis intercept
|
@@ -151,7 +190,7 @@ Supports two-point, slope-intercept, and point-slope initializer forms
|
|
151
190
|
((other.first.y == slope * other.first.x + intercept)) && (other.last.y == (slope * other.last.x + intercept))
|
152
191
|
else
|
153
192
|
self.eql? other
|
154
|
-
|
193
|
+
end
|
155
194
|
end
|
156
195
|
|
157
196
|
# Two {SlopeInterceptLine}s are equal if both have equal slopes and intercepts
|
@@ -160,13 +199,9 @@ Supports two-point, slope-intercept, and point-slope initializer forms
|
|
160
199
|
(intercept == other.intercept) && (slope == other.slope)
|
161
200
|
end
|
162
201
|
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
def vertical?
|
167
|
-
(1/0.0) == @slope
|
168
|
-
end
|
169
|
-
|
202
|
+
# Find the requested axis intercept
|
203
|
+
# @param axis [Symbol] the axis to intercept (either :x or :y)
|
204
|
+
# @return [Number] the location of the intercept
|
170
205
|
def intercept(axis=:y)
|
171
206
|
case axis
|
172
207
|
when :x
|
@@ -183,11 +218,21 @@ Supports two-point, slope-intercept, and point-slope initializer forms
|
|
183
218
|
|
184
219
|
# @private
|
185
220
|
class TwoPointLine < Line
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
221
|
+
# @!attribute first
|
222
|
+
# @return [Point] the {Line}'s starting point
|
223
|
+
attr_reader :first
|
224
|
+
|
225
|
+
# @!attribute last
|
226
|
+
# @return [Point] the {Line}'s end point
|
227
|
+
attr_reader :last
|
228
|
+
|
229
|
+
# @param first [Point] the starting point
|
230
|
+
# @param last [Point] the end point
|
231
|
+
def initialize(first, last)
|
232
|
+
@first = Point[first]
|
233
|
+
@last = Point[last]
|
190
234
|
end
|
235
|
+
|
191
236
|
def inspect
|
192
237
|
'Line(' + @first.inspect + ', ' + @last.inspect + ')'
|
193
238
|
end
|
@@ -206,7 +251,7 @@ Supports two-point, slope-intercept, and point-slope initializer forms
|
|
206
251
|
((first.y == other.slope * first.x + other.intercept)) && (last.y == (other.slope * last.x + other.intercept))
|
207
252
|
else
|
208
253
|
self.eql?(other) || ((first == other.last) && (last == other.first))
|
209
|
-
|
254
|
+
end
|
210
255
|
end
|
211
256
|
|
212
257
|
# Two {TwoPointLine}s are equal if both have equal endpoints
|
@@ -221,6 +266,27 @@ Supports two-point, slope-intercept, and point-slope initializer forms
|
|
221
266
|
def slope
|
222
267
|
(last.y - first.y)/(last.x - first.x)
|
223
268
|
end
|
269
|
+
|
270
|
+
def horizontal?
|
271
|
+
first.y == last.y
|
272
|
+
end
|
273
|
+
|
274
|
+
def vertical?
|
275
|
+
first.x == last.x
|
276
|
+
end
|
277
|
+
|
278
|
+
# Find the requested axis intercept
|
279
|
+
# @param axis [Symbol] the axis to intercept (either :x or :y)
|
280
|
+
# @return [Number] the location of the intercept
|
281
|
+
def intercept(axis=:y)
|
282
|
+
case axis
|
283
|
+
when :x
|
284
|
+
vertical? ? first.x : (horizontal? ? nil : (first.x - first.y/slope))
|
285
|
+
when :y
|
286
|
+
vertical? ? nil : (horizontal? ? first.y : (first.y - slope * first.x))
|
287
|
+
end
|
288
|
+
end
|
289
|
+
|
224
290
|
# @endgroup
|
225
291
|
end
|
226
292
|
end
|
data/lib/geometry/obround.rb
CHANGED
@@ -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)
|
data/lib/geometry/point.rb
CHANGED
@@ -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
|
35
|
+
super(*array)
|
38
36
|
end
|
39
37
|
|
40
|
-
# Creates and returns a new {PointIso} instance. Or, a {Point} full of
|
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,
|
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)
|
@@ -128,7 +128,13 @@ geometry class (x, y, z).
|
|
128
128
|
@elements.max
|
129
129
|
else
|
130
130
|
args = args.first if 1 == args.size
|
131
|
-
|
131
|
+
case args
|
132
|
+
when PointIso then self.class[@elements.map {|e| [e, args.value].max }]
|
133
|
+
when PointOne then self.class[@elements.map {|e| [e, 1].max }]
|
134
|
+
when PointZero then self.class[@elements.map {|e| [e, 0].max }]
|
135
|
+
else
|
136
|
+
self.class[@elements.zip(args).map(&:max)]
|
137
|
+
end
|
132
138
|
end
|
133
139
|
end
|
134
140
|
|
@@ -141,7 +147,13 @@ geometry class (x, y, z).
|
|
141
147
|
@elements.min
|
142
148
|
else
|
143
149
|
args = args.first if 1 == args.size
|
144
|
-
|
150
|
+
case args
|
151
|
+
when PointIso then self.class[@elements.map {|e| [e, args.value].min }]
|
152
|
+
when PointOne then self.class[@elements.map {|e| [e, 1].min }]
|
153
|
+
when PointZero then self.class[@elements.map {|e| [e, 0].min }]
|
154
|
+
else
|
155
|
+
self.class[@elements.zip(args).map(&:min)]
|
156
|
+
end
|
145
157
|
end
|
146
158
|
end
|
147
159
|
|
@@ -157,6 +169,46 @@ geometry class (x, y, z).
|
|
157
169
|
end
|
158
170
|
end
|
159
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
|
+
|
188
|
+
# Returns a new {Point} with the given number of elements removed from the end
|
189
|
+
# @return [Point] the popped elements
|
190
|
+
def pop(count=1)
|
191
|
+
self.class[to_a.pop(count)]
|
192
|
+
end
|
193
|
+
|
194
|
+
# Returns a new {Point} with the given elements appended
|
195
|
+
# @return [Point]
|
196
|
+
def push(*args)
|
197
|
+
self.class[to_a.push(*args)]
|
198
|
+
end
|
199
|
+
|
200
|
+
# Removes the first element and returns it
|
201
|
+
# @return [Point] the shifted elements
|
202
|
+
def shift(count=1)
|
203
|
+
self.class[to_a.shift(count)]
|
204
|
+
end
|
205
|
+
|
206
|
+
# Prepend the given objects and return a new {Point}
|
207
|
+
# @return [Point]
|
208
|
+
def unshift(*args)
|
209
|
+
self.class[to_a.unshift(*args)]
|
210
|
+
end
|
211
|
+
|
160
212
|
# @group Accessors
|
161
213
|
# @param [Integer] i Index into the {Point}'s elements
|
162
214
|
# @return [Numeric] Element i (starting at 0)
|
@@ -262,4 +314,3 @@ geometry class (x, y, z).
|
|
262
314
|
|
263
315
|
end
|
264
316
|
end
|
265
|
-
|
data/lib/geometry/point_iso.rb
CHANGED
@@ -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.
|
@@ -91,6 +89,18 @@ An object repesenting a N-dimensional {Point} with identical elements.
|
|
91
89
|
end
|
92
90
|
end
|
93
91
|
|
92
|
+
# Returns a new {Point} with the given number of elements removed from the end
|
93
|
+
# @return [Point] the popped elements
|
94
|
+
def pop(count=1)
|
95
|
+
Point[Array.new(count, @value)]
|
96
|
+
end
|
97
|
+
|
98
|
+
# Removes the first element and returns it
|
99
|
+
# @return [Point] the shifted elements
|
100
|
+
def shift(count=1)
|
101
|
+
Point[Array.new(count, @value)]
|
102
|
+
end
|
103
|
+
|
94
104
|
# @group Accessors
|
95
105
|
# @param i [Integer] Index into the {Point}'s elements
|
96
106
|
# @return [Numeric] Element i (starting at 0)
|