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.
- checksums.yaml +4 -4
- data/.github/workflows/build.yml +1 -1
- data/.gitignore +3 -1
- data/.release-please-manifest.json +1 -1
- data/.rubocop.yml +1 -1
- data/.yardopts +11 -0
- data/CHANGELOG.md +216 -0
- data/Gemfile +5 -1
- data/Gemfile.lock +35 -5
- data/README.md +325 -12
- data/Rakefile +18 -0
- data/benchmark/vector_comparison.rb +129 -0
- data/lib/vector2d/angles.rb +315 -0
- data/lib/vector2d/arithmetic.rb +97 -0
- data/lib/vector2d/comparison.rb +188 -0
- data/lib/vector2d/componentwise.rb +239 -0
- data/lib/vector2d/constructors.rb +137 -0
- data/lib/vector2d/conversions.rb +139 -0
- data/lib/vector2d/coordinates.rb +22 -0
- data/lib/vector2d/deprecation.rb +16 -0
- data/lib/vector2d/dimensions.rb +297 -0
- data/lib/vector2d/interpolation.rb +191 -0
- data/lib/vector2d/lengths.rb +284 -0
- data/lib/vector2d/matrix_interop.rb +93 -0
- data/lib/vector2d/parsing.rb +142 -0
- data/lib/vector2d/projection.rb +208 -0
- data/lib/vector2d/version.rb +2 -1
- data/lib/vector2d.rb +172 -60
- data/spec/lib/vector2d/angles_spec.rb +441 -0
- data/spec/lib/vector2d/{calculations_spec.rb → arithmetic_spec.rb} +48 -58
- data/spec/lib/vector2d/comparison_spec.rb +358 -0
- data/spec/lib/vector2d/componentwise_spec.rb +300 -0
- data/spec/lib/vector2d/constructors_spec.rb +299 -0
- data/spec/lib/vector2d/conversions_spec.rb +234 -0
- data/spec/lib/vector2d/dimensions_spec.rb +820 -0
- data/spec/lib/vector2d/interpolation_spec.rb +322 -0
- data/spec/lib/vector2d/lengths_spec.rb +457 -0
- data/spec/lib/vector2d/matrix_interop_spec.rb +92 -0
- data/spec/lib/vector2d/parsing_spec.rb +329 -0
- data/spec/lib/vector2d/projection_spec.rb +306 -0
- data/spec/lib/vector2d_documentation_spec.rb +38 -0
- data/spec/lib/vector2d_immutability_spec.rb +263 -0
- data/spec/lib/vector2d_spec.rb +90 -50
- data/spec/lib/vector2d_subclassing_spec.rb +210 -0
- data/spec/lib/vector2d_tags_spec.rb +43 -0
- data/spec/spec_helper.rb +2 -0
- data/spec/support/doc_examples/comments.rb +65 -0
- data/spec/support/doc_examples/markdown.rb +50 -0
- data/spec/support/doc_examples.rb +94 -0
- data/spec/support/doc_tags.rb +151 -0
- data/spec/support/shared_examples/class_preserving_method.rb +10 -0
- data/spec/support/shared_examples/deprecated_method.rb +25 -0
- data/spec/support/shared_examples/parsed_vector.rb +11 -0
- data/vector2d.gemspec +2 -1
- metadata +42 -13
- data/.travis.yml +0 -10
- data/lib/vector2d/calculations.rb +0 -141
- data/lib/vector2d/coercions.rb +0 -64
- data/lib/vector2d/fitting.rb +0 -60
- data/lib/vector2d/properties.rb +0 -46
- data/lib/vector2d/transformations.rb +0 -98
- data/spec/lib/vector2d/coercions_spec.rb +0 -59
- data/spec/lib/vector2d/fitting_spec.rb +0 -70
- data/spec/lib/vector2d/properties_spec.rb +0 -47
- data/spec/lib/vector2d/transformations_spec.rb +0 -139
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class Vector2d
|
|
4
|
+
# Products of two vectors, and the projection, reflection and
|
|
5
|
+
# refraction built on them.
|
|
6
|
+
module Projection
|
|
7
|
+
# The products taking both vectors as arguments. Extended into
|
|
8
|
+
# Vector2d, so they are called on the class.
|
|
9
|
+
#
|
|
10
|
+
# Vector2d.dot_product(Vector2d(2, 1), Vector2d(2, 3)) # => 7
|
|
11
|
+
#
|
|
12
|
+
module ClassMethods
|
|
13
|
+
# Calculates dot product of two vectors.
|
|
14
|
+
#
|
|
15
|
+
# v1 = Vector2d(2, 1)
|
|
16
|
+
# v2 = Vector2d(2, 3)
|
|
17
|
+
# Vector2d.dot_product(v1, v2) # => 7
|
|
18
|
+
#
|
|
19
|
+
# @param vector1 [Vector2d] one of the vectors
|
|
20
|
+
# @param vector2 [Vector2d] the other vector
|
|
21
|
+
# @return [Integer, Float, Rational, BigDecimal] a scalar
|
|
22
|
+
def dot_product(vector1, vector2)
|
|
23
|
+
(vector1.x * vector2.x) + (vector1.y * vector2.y)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Calculates cross product of two vectors.
|
|
27
|
+
#
|
|
28
|
+
# v1 = Vector2d(2, 1)
|
|
29
|
+
# v2 = Vector2d(2, 3)
|
|
30
|
+
# Vector2d.cross_product(v1, v2) # => 4
|
|
31
|
+
#
|
|
32
|
+
# @param vector1 [Vector2d] the vector the product is measured from
|
|
33
|
+
# @param vector2 [Vector2d] the vector the product is measured to
|
|
34
|
+
# @return [Integer, Float, Rational, BigDecimal] a scalar
|
|
35
|
+
def cross_product(vector1, vector2)
|
|
36
|
+
(vector1.x * vector2.y) - (vector1.y * vector2.x)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Dot product of this vector and another vector.
|
|
41
|
+
#
|
|
42
|
+
# v1 = Vector2d(2, 1)
|
|
43
|
+
# v2 = Vector2d(2, 3)
|
|
44
|
+
# v1.dot_product(v2) # => 7
|
|
45
|
+
#
|
|
46
|
+
# @!macro coercible
|
|
47
|
+
# @return [Integer, Float, Rational, BigDecimal] a scalar
|
|
48
|
+
def dot_product(other)
|
|
49
|
+
v = coerce_vector(other)
|
|
50
|
+
self.class.dot_product(self, v)
|
|
51
|
+
end
|
|
52
|
+
alias inner_product dot_product
|
|
53
|
+
alias dot dot_product
|
|
54
|
+
|
|
55
|
+
# Cross product of this vector and another vector. In two
|
|
56
|
+
# dimensions this is a scalar, the z component of the equivalent
|
|
57
|
+
# three dimensional cross product. Vector#cross_product returns a
|
|
58
|
+
# perpendicular vector instead, which is #perpendicular here.
|
|
59
|
+
#
|
|
60
|
+
# v1 = Vector2d(2, 1)
|
|
61
|
+
# v2 = Vector2d(2, 3)
|
|
62
|
+
# v1.cross_product(v2) # => 4
|
|
63
|
+
#
|
|
64
|
+
# @!macro coercible
|
|
65
|
+
# @return [Integer, Float, Rational, BigDecimal] a scalar
|
|
66
|
+
def cross_product(other)
|
|
67
|
+
v = coerce_vector(other)
|
|
68
|
+
self.class.cross_product(self, v)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Vector projection of this vector onto another vector. The
|
|
72
|
+
# argument is coerced, so scalars work too.
|
|
73
|
+
#
|
|
74
|
+
# v1 = Vector2d(2, 3)
|
|
75
|
+
# v2 = Vector2d(4, 0)
|
|
76
|
+
# v1.project(v2) # => Vector2d(2.0,0.0)
|
|
77
|
+
#
|
|
78
|
+
# The zero vector has no direction, and there is nothing to project
|
|
79
|
+
# onto. The zero vector is returned.
|
|
80
|
+
#
|
|
81
|
+
# v1.project(Vector2d(0, 0)) # => Vector2d(0.0,0.0)
|
|
82
|
+
#
|
|
83
|
+
# @!macro coercible
|
|
84
|
+
# @return [self]
|
|
85
|
+
def project(other)
|
|
86
|
+
v = coerce_vector(other)
|
|
87
|
+
return build(0.0, 0.0) if v.zero?
|
|
88
|
+
|
|
89
|
+
scale = dot_product(v).to_f / v.length_squared
|
|
90
|
+
build(v.x * scale, v.y * scale)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# Vector rejection of this vector from another vector, the component
|
|
94
|
+
# left over when the projection is subtracted.
|
|
95
|
+
#
|
|
96
|
+
# v1 = Vector2d(2, 3)
|
|
97
|
+
# v2 = Vector2d(4, 0)
|
|
98
|
+
# v1.reject(v2) # => Vector2d(0.0,3.0)
|
|
99
|
+
#
|
|
100
|
+
# The zero vector has no direction, and nothing is projected away.
|
|
101
|
+
#
|
|
102
|
+
# v1.reject(Vector2d(0, 0)) # => Vector2d(2.0,3.0)
|
|
103
|
+
#
|
|
104
|
+
# @!macro coercible
|
|
105
|
+
# @return [self]
|
|
106
|
+
def reject(other)
|
|
107
|
+
self - project(other)
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# Scalar projection of this vector onto another vector, the signed
|
|
111
|
+
# length of the projection. It is negative when the vectors point in
|
|
112
|
+
# opposite directions.
|
|
113
|
+
#
|
|
114
|
+
# v1 = Vector2d(2, 3)
|
|
115
|
+
# v2 = Vector2d(4, 0)
|
|
116
|
+
# v1.scalar_projection(v2) # => 2.0
|
|
117
|
+
# v1.scalar_projection(Vector2d(-4, 0)) # => -2.0
|
|
118
|
+
#
|
|
119
|
+
# The zero vector has no direction, and there is nothing to project
|
|
120
|
+
# onto. The scalar projection is zero.
|
|
121
|
+
#
|
|
122
|
+
# v1.scalar_projection(Vector2d(0, 0)) # => 0.0
|
|
123
|
+
#
|
|
124
|
+
# @!macro coercible
|
|
125
|
+
# @return [Float] a scalar, the signed length of the projection
|
|
126
|
+
def scalar_projection(other)
|
|
127
|
+
v = coerce_vector(other)
|
|
128
|
+
return 0.0 if v.zero?
|
|
129
|
+
|
|
130
|
+
dot_product(v) / v.length
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
# Reflects this vector about the line perpendicular to the normal,
|
|
134
|
+
# the way a ray bounces off a surface. The normal is normalized
|
|
135
|
+
# internally, so it can be of any length.
|
|
136
|
+
#
|
|
137
|
+
# vector = Vector2d(2, 3)
|
|
138
|
+
# vector.reflect(Vector2d(0, 1)) # => Vector2d(2.0,-3.0)
|
|
139
|
+
# vector.reflect(Vector2d(0, 5)) # => Vector2d(2.0,-3.0)
|
|
140
|
+
#
|
|
141
|
+
# The zero vector has no direction, and defines no surface to
|
|
142
|
+
# reflect off. Nothing is reflected, and the vector is returned.
|
|
143
|
+
#
|
|
144
|
+
# vector.reflect(Vector2d(0, 0)) # => Vector2d(2.0,3.0)
|
|
145
|
+
#
|
|
146
|
+
# @!macro coercible
|
|
147
|
+
# @return [self]
|
|
148
|
+
def reflect(normal)
|
|
149
|
+
v = coerce_vector(normal)
|
|
150
|
+
return to_f_vector if v.zero?
|
|
151
|
+
|
|
152
|
+
n = v.normalize
|
|
153
|
+
self - (n * (2 * dot_product(n)))
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
# Refracts this vector through a surface with the given normal and
|
|
157
|
+
# ratio of refractive indices, the way a ray bends entering a
|
|
158
|
+
# different medium. The normal is normalized internally, so it can
|
|
159
|
+
# be of any length.
|
|
160
|
+
#
|
|
161
|
+
# ray = Vector2d(1, -1).normalize
|
|
162
|
+
# ray.refract(Vector2d(0, 1), 0.5) # => Vector2d(0.3535..,-0.9354..)
|
|
163
|
+
# ray.refract(Vector2d(0, 5), 0.5) # => Vector2d(0.3535..,-0.9354..)
|
|
164
|
+
#
|
|
165
|
+
# A ratio of one leaves the ray on its course.
|
|
166
|
+
#
|
|
167
|
+
# ray.refract(Vector2d(0, 1), 1.0) # => Vector2d(0.7071..,-0.7071..)
|
|
168
|
+
#
|
|
169
|
+
# Past the critical angle the ray does not cross the surface at all.
|
|
170
|
+
# This is total internal reflection, and the zero vector is
|
|
171
|
+
# returned.
|
|
172
|
+
#
|
|
173
|
+
# ray.refract(Vector2d(0, 1), 2.0) # => Vector2d(0.0,0.0)
|
|
174
|
+
#
|
|
175
|
+
# The zero vector has no direction, and defines no surface to
|
|
176
|
+
# refract through. Nothing is refracted, and the vector is returned.
|
|
177
|
+
#
|
|
178
|
+
# ray.refract(Vector2d(0, 0), 0.5) # => Vector2d(0.7071..,-0.7071..)
|
|
179
|
+
#
|
|
180
|
+
# Raises ArgumentError unless the ratio is a real number.
|
|
181
|
+
#
|
|
182
|
+
# ray.refract(Vector2d(0, 1), Complex(1, 2)) # => ArgumentError
|
|
183
|
+
#
|
|
184
|
+
# @!macro coercible
|
|
185
|
+
# @param refractive_index [Integer, Float, Rational, BigDecimal]
|
|
186
|
+
# the ratio of refractive indices
|
|
187
|
+
# @return [self]
|
|
188
|
+
def refract(normal, refractive_index)
|
|
189
|
+
v = coerce_vector(normal)
|
|
190
|
+
eta = coordinate(refractive_index)
|
|
191
|
+
return to_f_vector if v.zero?
|
|
192
|
+
|
|
193
|
+
refract_through(v.normalize, eta)
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
private
|
|
197
|
+
|
|
198
|
+
# Refracts through a normalized normal. A negative discriminant is
|
|
199
|
+
# total internal reflection, where no refracted ray exists.
|
|
200
|
+
def refract_through(normal, eta)
|
|
201
|
+
cosine = dot_product(normal)
|
|
202
|
+
k = 1 - ((eta * eta) * (1 - (cosine * cosine)))
|
|
203
|
+
return build(0.0, 0.0) if k.negative?
|
|
204
|
+
|
|
205
|
+
(self * eta) - (normal * ((eta * cosine) + Math.sqrt(k)))
|
|
206
|
+
end
|
|
207
|
+
end
|
|
208
|
+
end
|
data/lib/vector2d/version.rb
CHANGED
data/lib/vector2d.rb
CHANGED
|
@@ -1,86 +1,198 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
class Vector2d
|
|
11
|
-
extend Vector2d::Calculations::ClassMethods
|
|
12
|
-
include Vector2d::Calculations
|
|
13
|
-
include Vector2d::Coercions
|
|
14
|
-
include Vector2d::Fitting
|
|
15
|
-
include Vector2d::Properties
|
|
16
|
-
include Vector2d::Transformations
|
|
17
|
-
|
|
18
|
-
class << self
|
|
19
|
-
# Creates a new vector.
|
|
20
|
-
# The following examples are all valid:
|
|
21
|
-
#
|
|
22
|
-
# Vector2d.parse(150, 100)
|
|
23
|
-
# Vector2d.parse(150.0, 100.0)
|
|
24
|
-
# Vector2d.parse("150x100")
|
|
25
|
-
# Vector2d.parse("150.0x100.0")
|
|
26
|
-
# Vector2d.parse([150,100})
|
|
27
|
-
# Vector2d.parse({x: 150, y: 100})
|
|
28
|
-
# Vector2d.parse({"x" => 150.0, "y" => 100.0})
|
|
29
|
-
# Vector2d.parse(Vector2d(150, 100))
|
|
30
|
-
def parse(arg, second_arg = nil)
|
|
31
|
-
if second_arg.nil?
|
|
32
|
-
parse_single_arg(arg)
|
|
33
|
-
else
|
|
34
|
-
new(arg, second_arg)
|
|
35
|
-
end
|
|
36
|
-
end
|
|
37
|
-
|
|
38
|
-
private
|
|
39
|
-
|
|
40
|
-
def parse_single_arg(arg)
|
|
41
|
-
return arg if arg.is_a?(Vector2d)
|
|
42
|
-
return parse(*arg) if arg.is_a?(Array)
|
|
43
|
-
return parse_str(arg) if arg.is_a?(String)
|
|
44
|
-
return parse_hash(arg.dup) if arg.is_a?(Hash)
|
|
45
|
-
|
|
46
|
-
new(arg, arg)
|
|
47
|
-
end
|
|
3
|
+
# The one place the types Vector2d.parse accepts are written out. Every
|
|
4
|
+
# parameter that is coerced into a vector takes this union.
|
|
5
|
+
#
|
|
6
|
+
# @!macro [new] coercible
|
|
7
|
+
# @param $1 [Vector2d, Array, String, Hash, Integer, Float, Rational,
|
|
8
|
+
# BigDecimal, ::Vector, ::Matrix] anything Vector2d.parse accepts
|
|
48
9
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
10
|
+
require_relative "vector2d/angles"
|
|
11
|
+
require_relative "vector2d/arithmetic"
|
|
12
|
+
require_relative "vector2d/comparison"
|
|
13
|
+
require_relative "vector2d/componentwise"
|
|
14
|
+
require_relative "vector2d/constructors"
|
|
15
|
+
require_relative "vector2d/conversions"
|
|
16
|
+
require_relative "vector2d/coordinates"
|
|
17
|
+
require_relative "vector2d/deprecation"
|
|
18
|
+
require_relative "vector2d/dimensions"
|
|
19
|
+
require_relative "vector2d/interpolation"
|
|
20
|
+
require_relative "vector2d/lengths"
|
|
21
|
+
require_relative "vector2d/matrix_interop"
|
|
22
|
+
require_relative "vector2d/parsing"
|
|
23
|
+
require_relative "vector2d/projection"
|
|
24
|
+
require_relative "vector2d/version"
|
|
54
25
|
|
|
55
|
-
|
|
56
|
-
|
|
26
|
+
# An immutable two dimensional vector.
|
|
27
|
+
#
|
|
28
|
+
# Coordinates are real numbers, and keep the type they were given.
|
|
29
|
+
# Integer coordinates stay exact through arithmetic with integers, and
|
|
30
|
+
# widen to floats when a float is involved.
|
|
31
|
+
#
|
|
32
|
+
# Vector2d(2, 3) * 2 # => Vector2d(4,6)
|
|
33
|
+
# Vector2d(2, 3) * 0.5 # => Vector2d(1.0,1.5)
|
|
34
|
+
#
|
|
35
|
+
# Instances are frozen, so every operation returns a new vector instead
|
|
36
|
+
# of changing the receiver. New vectors are built through #build, which
|
|
37
|
+
# a subclass overrides to carry its own state across operations.
|
|
38
|
+
#
|
|
39
|
+
# .parse is the permissive constructor, and Vector2d() is shorthand for
|
|
40
|
+
# it. .new takes exactly two coordinates.
|
|
41
|
+
#
|
|
42
|
+
# Vector2d("2x3") # => Vector2d(2,3)
|
|
43
|
+
# Vector2d.new(2, 3) # => Vector2d(2,3)
|
|
44
|
+
#
|
|
45
|
+
class Vector2d
|
|
46
|
+
extend Vector2d::Angles::ClassMethods
|
|
47
|
+
extend Vector2d::Constructors
|
|
48
|
+
extend Vector2d::Coordinates
|
|
49
|
+
extend Vector2d::Parsing
|
|
50
|
+
extend Vector2d::Projection::ClassMethods
|
|
51
|
+
include Vector2d::Angles
|
|
52
|
+
include Vector2d::Arithmetic
|
|
53
|
+
include Vector2d::Comparison
|
|
54
|
+
include Vector2d::Componentwise
|
|
55
|
+
include Vector2d::Conversions
|
|
56
|
+
include Vector2d::Coordinates
|
|
57
|
+
include Vector2d::Deprecation
|
|
58
|
+
include Vector2d::Dimensions
|
|
59
|
+
include Vector2d::Interpolation
|
|
60
|
+
include Vector2d::Lengths
|
|
61
|
+
include Vector2d::MatrixInterop
|
|
62
|
+
include Vector2d::Projection
|
|
57
63
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
64
|
+
# Builds a new vector of this class from two coordinates, the way
|
|
65
|
+
# .parse and .from_angle do. Override it in a subclass whose
|
|
66
|
+
# constructor requires more than the coordinates, and see #build for
|
|
67
|
+
# the instance side.
|
|
68
|
+
#
|
|
69
|
+
# class Labeled < Vector2d
|
|
70
|
+
# attr_reader :label
|
|
71
|
+
#
|
|
72
|
+
# def initialize(x, y, label = nil)
|
|
73
|
+
# @label = label
|
|
74
|
+
# super(x, y)
|
|
75
|
+
# end
|
|
76
|
+
#
|
|
77
|
+
# def self.build(x, y) = new(x, y, "unlabeled")
|
|
78
|
+
# end
|
|
79
|
+
#
|
|
80
|
+
# Labeled.parse("2x3").label # => "unlabeled"
|
|
81
|
+
#
|
|
82
|
+
# @param x [Integer, Float, Rational, BigDecimal] the x coordinate
|
|
83
|
+
# @param y [Integer, Float, Rational, BigDecimal] the y coordinate
|
|
84
|
+
# @return [Vector2d] a vector of this class
|
|
85
|
+
def self.build(x, y)
|
|
86
|
+
new(x, y)
|
|
61
87
|
end
|
|
62
88
|
|
|
89
|
+
# The coordinates.
|
|
90
|
+
#
|
|
91
|
+
# Vector2d(2, 3).x # => 2
|
|
92
|
+
# Vector2d(2, 3).y # => 3
|
|
93
|
+
#
|
|
94
|
+
# @return [Integer, Float, Rational, BigDecimal]
|
|
63
95
|
attr_reader :x, :y
|
|
64
96
|
|
|
97
|
+
# Creates a vector from two coordinates, which must be real numbers.
|
|
98
|
+
# Every vector is constructed through here, so this is what keeps a
|
|
99
|
+
# coordinate from being anything else. Instances are frozen.
|
|
100
|
+
#
|
|
101
|
+
# Vector2d.new(2, 3) # => Vector2d(2,3)
|
|
102
|
+
# Vector2d.new(Complex(1, 2), 3) # => ArgumentError
|
|
103
|
+
# Vector2d.new(2, 3).frozen? # => true
|
|
104
|
+
# Ractor.shareable?(Vector2d.new(2, 3)) # => true
|
|
105
|
+
#
|
|
106
|
+
# @param x [Integer, Float, Rational, BigDecimal] the x coordinate
|
|
107
|
+
# @param y [Integer, Float, Rational, BigDecimal] the y coordinate
|
|
65
108
|
def initialize(x, y)
|
|
66
|
-
@x = x
|
|
67
|
-
@y = y
|
|
109
|
+
@x = coordinate(x)
|
|
110
|
+
@y = coordinate(y)
|
|
111
|
+
freeze
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# Copies are frozen too.
|
|
115
|
+
#
|
|
116
|
+
# Vector2d(2, 3).dup.frozen? # => true
|
|
117
|
+
#
|
|
118
|
+
# @param other [Vector2d] the vector being copied
|
|
119
|
+
# @return [void]
|
|
120
|
+
def initialize_copy(other)
|
|
121
|
+
super
|
|
122
|
+
freeze
|
|
68
123
|
end
|
|
69
124
|
|
|
70
125
|
# Compares two vectors
|
|
71
126
|
#
|
|
72
127
|
# Vector2d(2, 3) == Vector2d(2, 3) # => true
|
|
73
128
|
# Vector2d(2, 3) == Vector2d(1, 0) # => false
|
|
129
|
+
# Vector2d(2, 3) == [2, 3] # => false
|
|
74
130
|
#
|
|
131
|
+
# @param other [Object] any object
|
|
132
|
+
# @return [Boolean]
|
|
75
133
|
def ==(other)
|
|
76
|
-
other.x == x && other.y == y
|
|
134
|
+
other.is_a?(Vector2d) && other.x == x && other.y == y
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
# Compares two vectors for hash equality. Unlike #==, the other object
|
|
138
|
+
# must be a vector of the same class, and the coordinates must be of
|
|
139
|
+
# the same type.
|
|
140
|
+
#
|
|
141
|
+
# Vector2d(2, 3).eql?(Vector2d(2, 3)) # => true
|
|
142
|
+
# Vector2d(2, 3).eql?(Vector2d(2.0, 3.0)) # => false
|
|
143
|
+
#
|
|
144
|
+
# @param other [Object] any object
|
|
145
|
+
# @return [Boolean]
|
|
146
|
+
def eql?(other)
|
|
147
|
+
other.instance_of?(self.class) && x.eql?(other.x) && y.eql?(other.y)
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
# Hash value of the vector, consistent with #eql?.
|
|
151
|
+
#
|
|
152
|
+
# Vector2d(2, 3).hash == Vector2d(2, 3).hash # => true
|
|
153
|
+
#
|
|
154
|
+
# @return [Integer]
|
|
155
|
+
def hash
|
|
156
|
+
[self.class, x, y].hash
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
# Builds a new vector of this class from two coordinates. Every method
|
|
160
|
+
# that returns a new vector goes through here, so a subclass whose
|
|
161
|
+
# constructor takes more than the coordinates only has to override
|
|
162
|
+
# this to have its own state carried across operations.
|
|
163
|
+
#
|
|
164
|
+
# class Labeled < Vector2d
|
|
165
|
+
# attr_reader :label
|
|
166
|
+
#
|
|
167
|
+
# def initialize(x, y, label = nil)
|
|
168
|
+
# @label = label
|
|
169
|
+
# super(x, y)
|
|
170
|
+
# end
|
|
171
|
+
#
|
|
172
|
+
# def build(x, y) = self.class.new(x, y, label)
|
|
173
|
+
# end
|
|
174
|
+
#
|
|
175
|
+
# Labeled.new(2, 3, "point").abs.label # => "point"
|
|
176
|
+
#
|
|
177
|
+
# Class level constructors have no instance to carry state from, and
|
|
178
|
+
# use .build instead.
|
|
179
|
+
#
|
|
180
|
+
# @param x [Integer, Float, Rational, BigDecimal] the x coordinate
|
|
181
|
+
# @param y [Integer, Float, Rational, BigDecimal] the y coordinate
|
|
182
|
+
# @return [self]
|
|
183
|
+
def build(x, y)
|
|
184
|
+
self.class.build(x, y)
|
|
77
185
|
end
|
|
78
186
|
end
|
|
79
187
|
|
|
80
|
-
#
|
|
188
|
+
# Shorthand for Vector2d.parse, and takes the same arguments.
|
|
81
189
|
#
|
|
82
|
-
# Vector2d(2, 3)
|
|
190
|
+
# Vector2d(2, 3) # => Vector2d(2,3)
|
|
191
|
+
# Vector2d("2x3") # => Vector2d(2,3)
|
|
192
|
+
# Vector2d([2, 3]) # => Vector2d(2,3)
|
|
83
193
|
#
|
|
194
|
+
# @return [Vector2d]
|
|
195
|
+
# @see Vector2d.parse for the arguments
|
|
84
196
|
def Vector2d(*)
|
|
85
197
|
Vector2d.parse(*)
|
|
86
198
|
end
|