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.
Files changed (44) hide show
  1. checksums.yaml +5 -5
  2. data/.github/workflows/ruby.yml +21 -0
  3. data/Gemfile +3 -0
  4. data/README.markdown +1 -2
  5. data/geometry.gemspec +5 -1
  6. data/lib/geometry/annulus.rb +20 -2
  7. data/lib/geometry/arc.rb +72 -1
  8. data/lib/geometry/edge.rb +21 -4
  9. data/lib/geometry/line.rb +84 -18
  10. data/lib/geometry/obround.rb +1 -2
  11. data/lib/geometry/path.rb +27 -0
  12. data/lib/geometry/point.rb +59 -8
  13. data/lib/geometry/point_iso.rb +12 -2
  14. data/lib/geometry/point_one.rb +44 -34
  15. data/lib/geometry/point_zero.rb +12 -1
  16. data/lib/geometry/polygon.rb +13 -12
  17. data/lib/geometry/polyline.rb +6 -6
  18. data/lib/geometry/rectangle.rb +12 -12
  19. data/lib/geometry/rotation.rb +5 -3
  20. data/lib/geometry/size.rb +1 -3
  21. data/lib/geometry/square.rb +13 -11
  22. data/lib/geometry/transformation/composition.rb +1 -1
  23. data/lib/geometry/transformation.rb +15 -0
  24. data/lib/geometry/triangle.rb +1 -1
  25. data/test/geometry/annulus.rb +12 -0
  26. data/test/geometry/arc.rb +98 -0
  27. data/test/geometry/circle.rb +3 -3
  28. data/test/geometry/edge.rb +16 -2
  29. data/test/geometry/line.rb +73 -0
  30. data/test/geometry/path.rb +16 -0
  31. data/test/geometry/point.rb +78 -2
  32. data/test/geometry/point_iso.rb +31 -21
  33. data/test/geometry/point_one.rb +11 -1
  34. data/test/geometry/point_zero.rb +46 -36
  35. data/test/geometry/polygon.rb +1 -1
  36. data/test/geometry/rectangle.rb +6 -1
  37. data/test/geometry/rotation.rb +1 -1
  38. data/test/geometry/size_one.rb +1 -1
  39. data/test/geometry/size_zero.rb +1 -1
  40. data/test/geometry/square.rb +15 -10
  41. data/test/geometry/transformation.rb +15 -1
  42. data/test/geometry/vector.rb +1 -1
  43. metadata +36 -9
  44. data/.travis.yml +0 -12
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 2071223ccd3ab40dc84675595b6b204aa0d7df10
4
- data.tar.gz: ed6b83fe034a5734ca9938c7ab54f4a4fcdda736
2
+ SHA256:
3
+ metadata.gz: 18aa25c45e739b77377ac376b9575f9828e2eb0566b4349a073be5b4deeea20e
4
+ data.tar.gz: 1ddbf89f0ad3854c222ffd344c0eaedd129d315ec9b1488f503f519dc8b5ca5b
5
5
  SHA512:
6
- metadata.gz: d78e31275dd418edf77a9067157213b63690bb7139ccf8c8561b228bb51e7be9e8b72e3a9187f02beb8d77ddc95427f882454f49c4827196f832acadec131e3c
7
- data.tar.gz: ba61dfc2b8d83090d59fe4d5348d3b184edeca188bf59c604d6888644a7392b943f8979ac4ea82392b1d8a026b53873128bab58c64702af149c3be68c1852057
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.4'
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
@@ -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
- 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
@@ -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
- # @return [Number] the slope of the {Line}
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
- end
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
- def horizontal?
164
- 0 == @slope
165
- end
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
- attr_reader :first, :last
187
-
188
- def initialize(point0, point1)
189
- @first, @last = [Point[point0], Point[point1]]
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
- end
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
@@ -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)
@@ -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
- self.class[@elements.zip(args).map(&:max)]
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
- self.class[@elements.zip(args).map(&:min)]
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
-
@@ -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)