vector2d 2.3.0 → 3.0.0

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 (65) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/build.yml +1 -1
  3. data/.gitignore +3 -1
  4. data/.release-please-manifest.json +1 -1
  5. data/.rubocop.yml +1 -1
  6. data/.yardopts +11 -0
  7. data/CHANGELOG.md +216 -0
  8. data/Gemfile +5 -1
  9. data/Gemfile.lock +35 -5
  10. data/README.md +325 -12
  11. data/Rakefile +18 -0
  12. data/benchmark/vector_comparison.rb +129 -0
  13. data/lib/vector2d/angles.rb +315 -0
  14. data/lib/vector2d/arithmetic.rb +97 -0
  15. data/lib/vector2d/comparison.rb +188 -0
  16. data/lib/vector2d/componentwise.rb +239 -0
  17. data/lib/vector2d/constructors.rb +137 -0
  18. data/lib/vector2d/conversions.rb +139 -0
  19. data/lib/vector2d/coordinates.rb +22 -0
  20. data/lib/vector2d/deprecation.rb +16 -0
  21. data/lib/vector2d/dimensions.rb +297 -0
  22. data/lib/vector2d/interpolation.rb +191 -0
  23. data/lib/vector2d/lengths.rb +284 -0
  24. data/lib/vector2d/matrix_interop.rb +93 -0
  25. data/lib/vector2d/parsing.rb +142 -0
  26. data/lib/vector2d/projection.rb +208 -0
  27. data/lib/vector2d/version.rb +2 -1
  28. data/lib/vector2d.rb +172 -60
  29. data/spec/lib/vector2d/angles_spec.rb +441 -0
  30. data/spec/lib/vector2d/{calculations_spec.rb → arithmetic_spec.rb} +48 -58
  31. data/spec/lib/vector2d/comparison_spec.rb +358 -0
  32. data/spec/lib/vector2d/componentwise_spec.rb +300 -0
  33. data/spec/lib/vector2d/constructors_spec.rb +299 -0
  34. data/spec/lib/vector2d/conversions_spec.rb +234 -0
  35. data/spec/lib/vector2d/dimensions_spec.rb +820 -0
  36. data/spec/lib/vector2d/interpolation_spec.rb +322 -0
  37. data/spec/lib/vector2d/lengths_spec.rb +457 -0
  38. data/spec/lib/vector2d/matrix_interop_spec.rb +92 -0
  39. data/spec/lib/vector2d/parsing_spec.rb +329 -0
  40. data/spec/lib/vector2d/projection_spec.rb +306 -0
  41. data/spec/lib/vector2d_documentation_spec.rb +38 -0
  42. data/spec/lib/vector2d_immutability_spec.rb +263 -0
  43. data/spec/lib/vector2d_spec.rb +90 -50
  44. data/spec/lib/vector2d_subclassing_spec.rb +210 -0
  45. data/spec/lib/vector2d_tags_spec.rb +43 -0
  46. data/spec/spec_helper.rb +2 -0
  47. data/spec/support/doc_examples/comments.rb +65 -0
  48. data/spec/support/doc_examples/markdown.rb +50 -0
  49. data/spec/support/doc_examples.rb +94 -0
  50. data/spec/support/doc_tags.rb +151 -0
  51. data/spec/support/shared_examples/class_preserving_method.rb +10 -0
  52. data/spec/support/shared_examples/deprecated_method.rb +25 -0
  53. data/spec/support/shared_examples/parsed_vector.rb +11 -0
  54. data/vector2d.gemspec +2 -1
  55. metadata +42 -13
  56. data/.travis.yml +0 -10
  57. data/lib/vector2d/calculations.rb +0 -141
  58. data/lib/vector2d/coercions.rb +0 -64
  59. data/lib/vector2d/fitting.rb +0 -60
  60. data/lib/vector2d/properties.rb +0 -46
  61. data/lib/vector2d/transformations.rb +0 -98
  62. data/spec/lib/vector2d/coercions_spec.rb +0 -59
  63. data/spec/lib/vector2d/fitting_spec.rb +0 -70
  64. data/spec/lib/vector2d/properties_spec.rb +0 -47
  65. data/spec/lib/vector2d/transformations_spec.rb +0 -139
@@ -0,0 +1,239 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Vector2d
4
+ # Operations applied to each coordinate on its own, the way Numeric
5
+ # applies them to a number.
6
+ module Componentwise
7
+ # Returns the absolute value of each axis. This is component-wise,
8
+ # not the magnitude of the vector, which is #length.
9
+ #
10
+ # Vector2d(-2, 3).abs # => Vector2d(2,3)
11
+ # Vector2d(-2, -3).abs # => Vector2d(2,3)
12
+ #
13
+ # @return [self]
14
+ def abs
15
+ build(x.abs, y.abs)
16
+ end
17
+
18
+ # Rounds vector up to nearest integer.
19
+ #
20
+ # Vector2d(2.4, 3.6).ceil # => Vector2d(3,4)
21
+ # Vector2d(2.441, 3.666).ceil(2) # => Vector2d(2.45,3.67)
22
+ #
23
+ # @param digits [Integer] the number of decimal places to keep
24
+ # @return [self]
25
+ def ceil(digits = 0)
26
+ build(x.ceil(digits), y.ceil(digits))
27
+ end
28
+
29
+ # Rounds vector down to nearest integer.
30
+ #
31
+ # Vector2d(2.4, 3.6).floor # => Vector2d(2,3)
32
+ # Vector2d(2.444, 3.669).floor(2) # => Vector2d(2.44,3.66)
33
+ #
34
+ # @param digits [Integer] the number of decimal places to keep
35
+ # @return [self]
36
+ def floor(digits = 0)
37
+ build(x.floor(digits), y.floor(digits))
38
+ end
39
+
40
+ # Rounds vector to nearest integer.
41
+ #
42
+ # Vector2d(2.4, 3.6).round # => Vector2d(2,4)
43
+ # Vector2d(2.4444, 3.666).round(2) # => Vector2d(2.44,3.67)
44
+ #
45
+ # @param digits [Integer] the number of decimal places to keep
46
+ # @return [self]
47
+ def round(digits = 0)
48
+ build(x.round(digits), y.round(digits))
49
+ end
50
+
51
+ # @deprecated Use #limit_length instead. The name belongs to the
52
+ # #ceil/#floor/#round family, which maps Numeric over both
53
+ # coordinates.
54
+ #
55
+ # @param max [Integer, Float, Rational, BigDecimal] the maximum length
56
+ # @return [self]
57
+ def truncate(max)
58
+ warn_deprecated("Vector2d#truncate is deprecated. Use #limit_length instead.")
59
+ limit_length(max)
60
+ end
61
+
62
+ # Returns the sign of each axis, -1, 0 or 1.
63
+ #
64
+ # Vector2d(-2, 3).sign # => Vector2d(-1,1)
65
+ # Vector2d(0, -3).sign # => Vector2d(0,-1)
66
+ #
67
+ # The signs are integers, whatever the coordinates were. There are
68
+ # only three of them, and they are exact.
69
+ #
70
+ # Vector2d(-2.5, 0.0).sign # => Vector2d(-1,0)
71
+ #
72
+ # NaN has no sign, so ArgumentError is raised.
73
+ #
74
+ # Vector2d(Float::NAN, 3).sign # => ArgumentError
75
+ #
76
+ # @return [self] a vector of -1, 0 and 1
77
+ def sign
78
+ build(coordinate_sign(x), coordinate_sign(y))
79
+ end
80
+
81
+ # Snaps each axis to the nearest multiple of a step. The step is
82
+ # coerced, so scalars work too, and a vector gives each axis its
83
+ # own step.
84
+ #
85
+ # vector = Vector2d(23, 47)
86
+ # vector.snap(10) # => Vector2d(20,50)
87
+ # vector.snap(Vector2d(10, 5)) # => Vector2d(20,45)
88
+ #
89
+ # Coordinates take the type of the step, so an integer step snaps
90
+ # to integers.
91
+ #
92
+ # Vector2d(2.3, 3.7).snap(1) # => Vector2d(2,4)
93
+ # Vector2d(2.3, 3.7).snap(0.5) # => Vector2d(2.5,3.5)
94
+ #
95
+ # A step of zero has no multiples to snap to, and leaves the axis
96
+ # unchanged.
97
+ #
98
+ # vector.snap(0) # => Vector2d(23,47)
99
+ # vector.snap(Vector2d(10, 0)) # => Vector2d(20,47)
100
+ #
101
+ # @!macro coercible
102
+ # @return [self]
103
+ def snap(step)
104
+ v = coerce_vector(step)
105
+ build(snap_coordinate(x, v.x), snap_coordinate(y, v.y))
106
+ end
107
+
108
+ # Returns the larger value of each axis. The other vector is
109
+ # coerced, so scalars work too.
110
+ #
111
+ # vector = Vector2d(2, 8)
112
+ # vector.max(Vector2d(5, 5)) # => Vector2d(5,8)
113
+ # vector.max(5) # => Vector2d(5,8)
114
+ #
115
+ # @!macro coercible
116
+ # @return [self]
117
+ def max(other)
118
+ v = coerce_vector(other)
119
+ build([x, v.x].max, [y, v.y].max)
120
+ end
121
+
122
+ # Returns the smaller value of each axis. The other vector is
123
+ # coerced, so scalars work too.
124
+ #
125
+ # vector = Vector2d(2, 8)
126
+ # vector.min(Vector2d(5, 5)) # => Vector2d(2,5)
127
+ # vector.min(5) # => Vector2d(2,5)
128
+ #
129
+ # @!macro coercible
130
+ # @return [self]
131
+ def min(other)
132
+ v = coerce_vector(other)
133
+ build([x, v.x].min, [y, v.y].min)
134
+ end
135
+
136
+ # Clamps the vector between two others, one axis at a time. The
137
+ # bounds are coerced, so scalars work too.
138
+ #
139
+ # vector = Vector2d(2, 8)
140
+ # vector.clamp(Vector2d(3, 3), Vector2d(6, 6)) # => Vector2d(3,6)
141
+ # vector.clamp(3, 6) # => Vector2d(3,6)
142
+ #
143
+ # The bounds can also be given as a single range, which may be
144
+ # beginless or endless to clamp only one side.
145
+ #
146
+ # vector.clamp(3..6) # => Vector2d(3,6)
147
+ # vector.clamp(..6) # => Vector2d(2,6)
148
+ # vector.clamp(3..) # => Vector2d(3,8)
149
+ #
150
+ # The range must not exclude its end, as with Comparable#clamp.
151
+ #
152
+ # vector.clamp(3...6) # => ArgumentError
153
+ #
154
+ # @overload clamp(min, max)
155
+ # @param min [Vector2d, Array, String, Hash, Integer, Float, Rational,
156
+ # BigDecimal, ::Vector, ::Matrix] the lower bound, anything
157
+ # Vector2d.parse accepts
158
+ # @param max [Vector2d, Array, String, Hash, Integer, Float, Rational,
159
+ # BigDecimal, ::Vector, ::Matrix] the upper bound
160
+ # @overload clamp(range)
161
+ # @param range [Range] both bounds, and may be beginless or endless
162
+ # @return [self]
163
+ def clamp(min, max = nil)
164
+ min_v, max_v = clamp_bounds(min, max)
165
+ build(
166
+ x.clamp(Range.new(min_v&.x, max_v&.x)),
167
+ y.clamp(Range.new(min_v&.y, max_v&.y))
168
+ )
169
+ end
170
+
171
+ # Returns the vector with x replaced.
172
+ #
173
+ # Vector2d(2, 3).with_x(5) # => Vector2d(5,3)
174
+ #
175
+ # Vectors are immutable, so this is how a single axis is changed.
176
+ # The value is a coordinate, not a vector, and is not coerced.
177
+ #
178
+ # Vector2d(2, 3).with_x("5") # => ArgumentError
179
+ #
180
+ # @param value [Integer, Float, Rational, BigDecimal] the new x coordinate
181
+ # @return [self]
182
+ def with_x(value)
183
+ build(value, y)
184
+ end
185
+
186
+ # Returns the vector with y replaced.
187
+ #
188
+ # Vector2d(2, 3).with_y(5) # => Vector2d(2,5)
189
+ #
190
+ # The value is a coordinate, not a vector, and is not coerced.
191
+ #
192
+ # Vector2d(2, 3).with_y(nil) # => ArgumentError
193
+ #
194
+ # @param value [Integer, Float, Rational, BigDecimal] the new y coordinate
195
+ # @return [self]
196
+ def with_y(value)
197
+ build(x, value)
198
+ end
199
+
200
+ private
201
+
202
+ def clamp_bounds(min, max)
203
+ if min.is_a?(Range)
204
+ return range_bounds(min, max)
205
+ .map { |bound| bound && coerce_vector(bound) }
206
+ end
207
+
208
+ raise ArgumentError, "wrong number of arguments (given 1, expected 2)" if max.nil?
209
+
210
+ [coerce_vector(min), coerce_vector(max)]
211
+ end
212
+
213
+ # Splits the range form of #clamp and #clamp_length into its two
214
+ # ends, either of which can be nil.
215
+ def range_bounds(range, max)
216
+ raise ArgumentError, "wrong number of arguments (given 2, expected 1)" unless max.nil?
217
+ raise ArgumentError, "cannot clamp with an exclusive range" if range.exclude_end?
218
+
219
+ [range.begin, range.end]
220
+ end
221
+
222
+ # The sign of a coordinate. Comparing to zero gives one of -1, 0
223
+ # and 1, or nothing at all for NaN.
224
+ def coordinate_sign(value)
225
+ sign = value <=> 0
226
+ raise ArgumentError, "NaN has no sign" if sign.nil?
227
+
228
+ sign
229
+ end
230
+
231
+ # Rounds a coordinate to the nearest multiple of a step. A step of
232
+ # zero has no multiples, and the coordinate is left alone.
233
+ def snap_coordinate(value, step)
234
+ return value if step.zero?
235
+
236
+ (value / step.to_f).round * step
237
+ end
238
+ end
239
+ end
@@ -0,0 +1,137 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Vector2d
4
+ # The constructors that build a vector from something other than
5
+ # coordinates: an angle, a random draw, or a direction. They are
6
+ # class methods rather than constants, so a subclass gets its own.
7
+ module Constructors
8
+ # Creates a vector from an angle in radians, with an optional
9
+ # length. Angles are measured counterclockwise from the positive x
10
+ # axis, the same convention #angle follows.
11
+ #
12
+ # Vector2d.from_angle(0) # => Vector2d(1.0,0.0)
13
+ # Vector2d.from_angle(Math::PI / 4) # => Vector2d(0.7071..,0.7071..)
14
+ # Vector2d.from_angle(Math::PI / 4, 2.0) # => Vector2d(1.4142..,1.4142..)
15
+ #
16
+ # Coordinates are always floats. This is the inverse of #to_polar.
17
+ #
18
+ # length, angle = Vector2d(2, 3).to_polar
19
+ # Vector2d.from_angle(angle, length) # => Vector2d(2.0,3.0)
20
+ #
21
+ # Raises ArgumentError unless both arguments are real numbers.
22
+ # Complex numbers are not coordinates, and are rejected.
23
+ #
24
+ # Vector2d.from_angle(Complex(1, 2)) # => ArgumentError
25
+ #
26
+ # @param angle [Integer, Float, Rational, BigDecimal] the angle in radians
27
+ # @param length [Integer, Float, Rational, BigDecimal]
28
+ # the length of the vector
29
+ # @return [Vector2d] a vector of the receiver's class
30
+ def from_angle(angle, length = 1.0)
31
+ angle = coordinate(angle).to_f
32
+ length = coordinate(length).to_f
33
+ build(Math.cos(angle) * length, Math.sin(angle) * length)
34
+ end
35
+
36
+ # Creates a random vector, uniformly distributed by angle, with an
37
+ # optional length. Coordinates are always floats, as with
38
+ # .from_angle.
39
+ #
40
+ # Vector2d.random.length.round(6) # => 1.0
41
+ # Vector2d.random(2.0).length.round(6) # => 2.0
42
+ #
43
+ # Pass a Random to draw from a seeded sequence.
44
+ #
45
+ # a = Vector2d.random(random: Random.new(42))
46
+ # b = Vector2d.random(random: Random.new(42))
47
+ # a == b # => true
48
+ #
49
+ # Raises ArgumentError unless the length is a real number.
50
+ #
51
+ # Vector2d.random(Complex(1, 2)) # => ArgumentError
52
+ #
53
+ # @param length [Integer, Float, Rational, BigDecimal]
54
+ # the length of the vector
55
+ # @param random [#rand] the source of randomness
56
+ # @return [Vector2d] a vector of the receiver's class
57
+ def random(length = 1.0, random: Random)
58
+ from_angle(random.rand * 2 * Math::PI, length)
59
+ end
60
+
61
+ # The zero vector.
62
+ #
63
+ # Vector2d.zero # => Vector2d(0,0)
64
+ #
65
+ # This and the five constants below have integer coordinates. They
66
+ # are exact, and integers keep them exact through arithmetic with
67
+ # integer vectors, widening to floats only when a float is
68
+ # involved. .from_angle returns floats instead, because a general
69
+ # angle has no exact coordinates.
70
+ #
71
+ # Vector2d.zero.x # => 0
72
+ # (Vector2d.up * 2).y # => 2
73
+ # (Vector2d.up * 0.5).y # => 0.5
74
+ #
75
+ # @return [Vector2d] a vector of the receiver's class
76
+ def zero
77
+ build(0, 0)
78
+ end
79
+
80
+ # The vector with both coordinates set to one.
81
+ #
82
+ # Vector2d.one # => Vector2d(1,1)
83
+ #
84
+ # @return [Vector2d] a vector of the receiver's class
85
+ def one
86
+ build(1, 1)
87
+ end
88
+
89
+ # The unit vector pointing up.
90
+ #
91
+ # Vector2d.up # => Vector2d(0,1)
92
+ # Vector2d.up.angle # => 1.5707..
93
+ #
94
+ # The y axis grows upwards here, and angles turn counterclockwise
95
+ # from the positive x axis. That is the convention .from_angle,
96
+ # #angle, #rotate and #perpendicular all follow. Libraries drawing
97
+ # in screen coordinates grow the y axis downwards and call (0, -1)
98
+ # up, so flip the y axis at that boundary.
99
+ #
100
+ # @return [Vector2d] a vector of the receiver's class
101
+ def up
102
+ build(0, 1)
103
+ end
104
+
105
+ # The unit vector pointing down. See .up for the direction the y
106
+ # axis grows in.
107
+ #
108
+ # Vector2d.down # => Vector2d(0,-1)
109
+ # Vector2d.down.angle # => -1.5707..
110
+ #
111
+ # @return [Vector2d] a vector of the receiver's class
112
+ def down
113
+ build(0, -1)
114
+ end
115
+
116
+ # The unit vector pointing left.
117
+ #
118
+ # Vector2d.left # => Vector2d(-1,0)
119
+ # Vector2d.left.angle # => 3.1415..
120
+ #
121
+ # @return [Vector2d] a vector of the receiver's class
122
+ def left
123
+ build(-1, 0)
124
+ end
125
+
126
+ # The unit vector pointing right, along the positive x axis. This
127
+ # is the direction angles are measured from.
128
+ #
129
+ # Vector2d.right # => Vector2d(1,0)
130
+ # Vector2d.right.angle # => 0.0
131
+ #
132
+ # @return [Vector2d] a vector of the receiver's class
133
+ def right
134
+ build(1, 0)
135
+ end
136
+ end
137
+ end
@@ -0,0 +1,139 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Vector2d
4
+ # Conversions to and from other Ruby objects, including the coercion
5
+ # protocol and pattern matching. Conversions to the standard library
6
+ # Matrix and Vector are in Vector2d::MatrixInterop.
7
+ module Conversions
8
+ # Implements Ruby's coercion protocol, so a vector can be the right
9
+ # hand operand of a scalar.
10
+ #
11
+ # 2 * Vector2d(3, 4) # => Vector2d(6,8)
12
+ #
13
+ # The operand is built through #build, so a subclass is the result
14
+ # on either side of the operator.
15
+ #
16
+ # Matrices are coerced the other way around, see
17
+ # Vector2d::MatrixInterop#coerce.
18
+ #
19
+ # @!macro coercible
20
+ # @return [Array(self, self)] the coerced operand and this vector
21
+ def coerce(other)
22
+ v = coerce_vector(other)
23
+ [build(v.x, v.y), self]
24
+ end
25
+
26
+ # Returns the components as an array, so a vector can be matched
27
+ # against an array pattern.
28
+ #
29
+ # Vector2d(3, 4).deconstruct # => [3,4]
30
+ #
31
+ # case Vector2d(3, 4)
32
+ # in [0, 0] then :origin
33
+ # in [Integer => a, Integer => b] then a + b
34
+ # end # => 7
35
+ #
36
+ # @return [Array<Integer, Float, Rational, BigDecimal>] the [x, y] pair
37
+ def deconstruct
38
+ to_a
39
+ end
40
+
41
+ # Returns the components as a hash, so a vector can be matched
42
+ # against a hash pattern. Both components are always returned,
43
+ # whichever keys the pattern asks for.
44
+ #
45
+ # Vector2d(3, 4).deconstruct_keys([:x]) # => {x: 3, y: 4}
46
+ #
47
+ # case Vector2d(0, 4)
48
+ # in {x: 0} then :on_y_axis
49
+ # in {y: 0} then :on_x_axis
50
+ # end # => :on_y_axis
51
+ #
52
+ # @param _keys [Array<Symbol>, nil] ignored, both components are
53
+ # always returned
54
+ # @return [Hash{Symbol => Integer, Float, Rational, BigDecimal}]
55
+ # the x and y coordinates
56
+ def deconstruct_keys(_keys)
57
+ to_hash
58
+ end
59
+
60
+ # Renders vector as a pretty string.
61
+ #
62
+ # Vector2d(2, 3).inspect # => "Vector2d(2,3)"
63
+ #
64
+ # @return [String]
65
+ def inspect
66
+ "#{self.class}(#{x},#{y})"
67
+ end
68
+
69
+ # Converts vector to array.
70
+ #
71
+ # Vector2d(2, 3).to_a # => [2,3]
72
+ #
73
+ # @return [Array<Integer, Float, Rational, BigDecimal>] the [x, y] pair
74
+ def to_a
75
+ [x, y]
76
+ end
77
+
78
+ # Converts vector to hash.
79
+ #
80
+ # Vector2d(2, 3).to_hash # => {x: 2, y: 3}
81
+ #
82
+ # @return [Hash{Symbol => Integer, Float, Rational, BigDecimal}]
83
+ # the x and y coordinates
84
+ def to_hash
85
+ { x: x, y: y }
86
+ end
87
+
88
+ # Converts the coordinates to integers. The result is still a
89
+ # vector of this class, unlike #to_vector, which converts to the
90
+ # standard library Vector.
91
+ #
92
+ # Vector2d(2.0, 3.0).to_i_vector # => Vector2d(2,3)
93
+ #
94
+ # @return [self]
95
+ def to_i_vector
96
+ build(x.to_i, y.to_i)
97
+ end
98
+
99
+ # Converts the coordinates to floats. As with #to_i_vector, the
100
+ # result is a vector of this class.
101
+ #
102
+ # Vector2d(2, 3).to_f_vector # => Vector2d(2.0,3.0)
103
+ #
104
+ # @return [self]
105
+ def to_f_vector
106
+ build(x.to_f, y.to_f)
107
+ end
108
+
109
+ # Converts vector to string.
110
+ #
111
+ # Vector2d.new(150, 100).to_s # => "150x100"
112
+ #
113
+ # @return [String]
114
+ def to_s
115
+ "#{x}x#{y}"
116
+ end
117
+
118
+ private
119
+
120
+ # Parses anything Vector2d.parse accepts into a vector. A vector is
121
+ # returned as it is.
122
+ def coerce_vector(other)
123
+ return other if other.is_a?(Vector2d)
124
+ return Vector2d.parse(other) if parseable?(other)
125
+
126
+ raise TypeError, "#{other.class} can't be coerced into #{self.class}"
127
+ end
128
+
129
+ # Can the object be parsed into a vector? Complex numbers are
130
+ # Numeric, but they are not coordinates.
131
+ def parseable?(other)
132
+ case other
133
+ when Vector2d, Array, String, Hash then true
134
+ when Numeric then other.real?
135
+ else MatrixInterop.vector?(other) || MatrixInterop.matrix?(other)
136
+ end
137
+ end
138
+ end
139
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Vector2d
4
+ # Coordinate validation, shared by the class methods that parse input
5
+ # and by the constructor.
6
+ module Coordinates
7
+ module_function
8
+
9
+ # Returns the value if it is a valid coordinate, raises
10
+ # ArgumentError otherwise. Coordinates are real numbers, so Complex
11
+ # is rejected along with everything outside Numeric.
12
+ #
13
+ # @param value [Object] the value to validate
14
+ # @return [Integer, Float, Rational, BigDecimal] the value
15
+ def coordinate(value)
16
+ return value if value.is_a?(Float) || value.is_a?(Integer)
17
+ raise ArgumentError, "not a valid coordinate: #{value.inspect}" unless value.is_a?(Numeric) && value.real?
18
+
19
+ value
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Vector2d
4
+ # The warning issued by the methods deprecated for 3.0. Each of them
5
+ # names its replacement, and forwards to it.
6
+ module Deprecation
7
+ private
8
+
9
+ # Warns that a method is deprecated. Silent unless deprecation
10
+ # warnings are enabled, either with <tt>Warning[:deprecated] = true</tt>
11
+ # or by running Ruby with <tt>-w</tt>.
12
+ def warn_deprecated(message)
13
+ warn(message, uplevel: 2, category: :deprecated)
14
+ end
15
+ end
16
+ end