vector2d 2.2.4 → 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/dependabot.yml +39 -0
- data/.github/workflows/build.yml +48 -10
- data/.gitignore +3 -1
- data/.release-please-manifest.json +3 -0
- data/.rubocop.yml +5 -2
- data/.tool-versions +1 -0
- data/.yardopts +11 -0
- data/CHANGELOG.md +224 -0
- data/CODE_OF_CONDUCT.md +92 -0
- data/CONTRIBUTING.md +30 -0
- data/Gemfile +6 -2
- data/Gemfile.lock +85 -51
- data/README.md +330 -13
- data/Rakefile +21 -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 +174 -62
- data/release-please-config.json +10 -0
- 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 +9 -3
- metadata +55 -19
- 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 -85
- 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 -122
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "spec_helper"
|
|
4
|
+
|
|
5
|
+
describe Vector2d::Interpolation do
|
|
6
|
+
describe "#lerp" do
|
|
7
|
+
subject(:vector) { Vector2d.new(0, 0) }
|
|
8
|
+
|
|
9
|
+
let(:comp) { Vector2d.new(10, 20) }
|
|
10
|
+
|
|
11
|
+
it "returns this vector at zero" do
|
|
12
|
+
expect(vector.lerp(comp, 0)).to eq(Vector2d.new(0, 0))
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
it "returns the other vector at one" do
|
|
16
|
+
expect(vector.lerp(comp, 1)).to eq(Vector2d.new(10, 20))
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
it "interpolates between the vectors" do
|
|
20
|
+
expect(vector.lerp(comp, 0.25)).to eq(Vector2d.new(2.5, 5.0))
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
it "extrapolates past the other vector" do
|
|
24
|
+
expect(vector.lerp(comp, 2.0)).to eq(Vector2d.new(20.0, 40.0))
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
it "extrapolates behind this vector" do
|
|
28
|
+
expect(vector.lerp(comp, -0.5)).to eq(Vector2d.new(-5.0, -10.0))
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
it "interpolates from negative coordinates" do
|
|
32
|
+
expect(Vector2d.new(-4, 6).lerp(Vector2d.new(4, -2), 0.5))
|
|
33
|
+
.to eq(Vector2d.new(0.0, 2.0))
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
it "coerces the argument" do
|
|
37
|
+
expect(vector.lerp([10, 20], 0.5)).to eq(Vector2d.new(5.0, 10.0))
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
context "with a complex amount" do
|
|
41
|
+
it "raises an error" do
|
|
42
|
+
expect { vector.lerp(comp, Complex(1, 2)) }.to(
|
|
43
|
+
raise_error(ArgumentError, "not a valid coordinate: (1+2i)")
|
|
44
|
+
)
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
context "with a vector amount" do
|
|
49
|
+
it "raises an error" do
|
|
50
|
+
expect { vector.lerp(comp, Vector2d.new(0.25, 0.5)) }.to(
|
|
51
|
+
raise_error(ArgumentError, "not a valid coordinate: Vector2d(0.25,0.5)")
|
|
52
|
+
)
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
context "with a string amount" do
|
|
57
|
+
it "raises an error" do
|
|
58
|
+
expect { vector.lerp(comp, "2x3") }.to(
|
|
59
|
+
raise_error(ArgumentError, 'not a valid coordinate: "2x3"')
|
|
60
|
+
)
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
describe "#inverse_lerp" do
|
|
66
|
+
subject(:vector) { Vector2d.new(0, 0) }
|
|
67
|
+
|
|
68
|
+
let(:comp) { Vector2d.new(10, 20) }
|
|
69
|
+
|
|
70
|
+
it "returns zero at this vector" do
|
|
71
|
+
expect(vector.inverse_lerp(comp, vector)).to eq(0.0)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
it "returns one at the other vector" do
|
|
75
|
+
expect(vector.inverse_lerp(comp, comp)).to eq(1.0)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
it "returns the position of a value on the segment" do
|
|
79
|
+
expect(vector.inverse_lerp(comp, Vector2d.new(2.5, 5.0))).to eq(0.25)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
it "returns a scalar" do
|
|
83
|
+
expect(vector.inverse_lerp(comp, Vector2d.new(2.5, 5.0))).to be_a(Float)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
it "returns a float for integer coordinates" do
|
|
87
|
+
expect(Vector2d.new(0, 0).inverse_lerp(Vector2d.new(4, 0),
|
|
88
|
+
Vector2d.new(1, 0))).to eq(0.25)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
it "projects a value off the segment onto the line" do
|
|
92
|
+
expect(vector.inverse_lerp(comp, Vector2d.new(5, 0))).to eq(0.1)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
it "extrapolates past the other vector" do
|
|
96
|
+
expect(vector.inverse_lerp(comp, Vector2d.new(20, 40))).to eq(2.0)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
it "extrapolates behind this vector" do
|
|
100
|
+
expect(vector.inverse_lerp(comp, Vector2d.new(-5, -10))).to eq(-0.5)
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
it "inverts #lerp" do
|
|
104
|
+
amount = vector.inverse_lerp(comp, Vector2d.new(2.5, 5.0))
|
|
105
|
+
|
|
106
|
+
expect(vector.lerp(comp, amount)).to eq(Vector2d.new(2.5, 5.0))
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
it "inverts #lerp from a non-zero origin" do
|
|
110
|
+
v1 = Vector2d.new(-4, 6)
|
|
111
|
+
v2 = Vector2d.new(4, -2)
|
|
112
|
+
|
|
113
|
+
expect(v1.inverse_lerp(v2, v1.lerp(v2, 0.3))).to be_within(1e-12).of(0.3)
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
it "coerces the other vector" do
|
|
117
|
+
expect(vector.inverse_lerp([10, 20], Vector2d.new(2.5, 5.0))).to eq(0.25)
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
it "coerces the value" do
|
|
121
|
+
expect(vector.inverse_lerp(comp, "2.5x5.0")).to eq(0.25)
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
context "when the segment has no length along an axis" do
|
|
125
|
+
let(:comp) { Vector2d.new(10, 0) }
|
|
126
|
+
|
|
127
|
+
it "returns a finite amount" do
|
|
128
|
+
expect(vector.inverse_lerp(comp, Vector2d.new(2.5, 99))).to eq(0.25)
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
context "when the vectors are identical" do
|
|
133
|
+
it "returns zero" do
|
|
134
|
+
expect(vector.inverse_lerp(vector, Vector2d.new(2.5, 5.0))).to eq(0.0)
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
it "returns zero for a non-zero vector" do
|
|
138
|
+
expect(comp.inverse_lerp(comp, Vector2d.new(2.5, 5.0))).to eq(0.0)
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
context "with an unparseable value" do
|
|
143
|
+
it "raises an error" do
|
|
144
|
+
expect { vector.inverse_lerp(comp, nil) }.to(
|
|
145
|
+
raise_error(TypeError, "NilClass can't be coerced into Vector2d")
|
|
146
|
+
)
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
describe "#slerp" do
|
|
152
|
+
subject(:vector) { Vector2d.new(2, 0) }
|
|
153
|
+
|
|
154
|
+
let(:comp) { Vector2d.new(0, 4) }
|
|
155
|
+
|
|
156
|
+
it "returns this vector at zero" do
|
|
157
|
+
expect(vector.slerp(comp, 0).round(6)).to eq(Vector2d.new(2, 0))
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
it "returns the other vector at one" do
|
|
161
|
+
expect(vector.slerp(comp, 1).round(6)).to eq(Vector2d.new(0, 4))
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
it "turns halfway around the arc" do
|
|
165
|
+
expect(vector.slerp(comp, 0.5).round(4))
|
|
166
|
+
.to eq(Vector2d.new(2.1213, 2.1213))
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
it "interpolates the length" do
|
|
170
|
+
expect(vector.slerp(comp, 0.5).length).to be_within(0.0001).of(3.0)
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
it "keeps the length off the straight line #lerp follows" do
|
|
174
|
+
expect(vector.slerp(comp, 0.5).length)
|
|
175
|
+
.to be > vector.lerp(comp, 0.5).length
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
it "keeps the angle between the two vectors" do
|
|
179
|
+
expect(vector.angle_between(vector.slerp(comp, 0.5)))
|
|
180
|
+
.to be_within(0.0001).of(Math::PI / 4)
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
it "extrapolates past the other vector" do
|
|
184
|
+
expect(vector.slerp(comp, 2.0).angle)
|
|
185
|
+
.to be_within(0.0001).of(Math::PI)
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
it "coerces the argument" do
|
|
189
|
+
expect(vector.slerp([0, 4], 0.5)).to eq(vector.slerp(comp, 0.5))
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
context "when the vectors point in opposite directions" do
|
|
193
|
+
let(:comp) { Vector2d.new(-2, 0) }
|
|
194
|
+
|
|
195
|
+
it "turns counterclockwise" do
|
|
196
|
+
expect(vector.slerp(comp, 0.5).round(6)).to eq(Vector2d.new(0, 2))
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
it "reaches the other vector at one" do
|
|
200
|
+
expect(vector.slerp(comp, 1).round(6)).to eq(Vector2d.new(-2, 0))
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
it "turns counterclockwise from any direction" do
|
|
204
|
+
expect(Vector2d.new(0, 2).slerp(Vector2d.new(0, -2), 0.5).round(6))
|
|
205
|
+
.to eq(Vector2d.new(-2, 0))
|
|
206
|
+
end
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
context "when the vectors point the same way" do
|
|
210
|
+
let(:comp) { Vector2d.new(4, 0) }
|
|
211
|
+
|
|
212
|
+
it "only interpolates the length" do
|
|
213
|
+
expect(vector.slerp(comp, 0.5).round(6)).to eq(Vector2d.new(3, 0))
|
|
214
|
+
end
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
context "with the zero vector" do
|
|
218
|
+
let(:comp) { Vector2d.new(0, 0) }
|
|
219
|
+
|
|
220
|
+
it "interpolates linearly" do
|
|
221
|
+
expect(vector.slerp(comp, 0.25)).to eq(vector.lerp(comp, 0.25))
|
|
222
|
+
end
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
context "when this vector is zero" do
|
|
226
|
+
subject(:vector) { Vector2d.new(0, 0) }
|
|
227
|
+
|
|
228
|
+
it "interpolates linearly" do
|
|
229
|
+
expect(vector.slerp(comp, 0.25)).to eq(vector.lerp(comp, 0.25))
|
|
230
|
+
end
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
context "with a complex amount" do
|
|
234
|
+
it "raises an error" do
|
|
235
|
+
expect { vector.slerp(comp, Complex(1, 2)) }.to(
|
|
236
|
+
raise_error(ArgumentError, "not a valid coordinate: (1+2i)")
|
|
237
|
+
)
|
|
238
|
+
end
|
|
239
|
+
end
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
describe "#midpoint" do
|
|
243
|
+
subject(:vector) { Vector2d.new(0, 0) }
|
|
244
|
+
|
|
245
|
+
let(:comp) { Vector2d.new(10, 20) }
|
|
246
|
+
|
|
247
|
+
it "returns the point halfway between the vectors" do
|
|
248
|
+
expect(vector.midpoint(comp)).to eq(Vector2d.new(5.0, 10.0))
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
it "is symmetric" do
|
|
252
|
+
expect(comp.midpoint(vector)).to eq(vector.midpoint(comp))
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
it "matches #lerp at one half" do
|
|
256
|
+
expect(vector.midpoint(comp)).to eq(vector.lerp(comp, 0.5))
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
it "coerces the argument" do
|
|
260
|
+
expect(vector.midpoint([10, 20])).to eq(Vector2d.new(5.0, 10.0))
|
|
261
|
+
end
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
describe "#move_toward" do
|
|
265
|
+
subject(:vector) { Vector2d.new(0, 0) }
|
|
266
|
+
|
|
267
|
+
let(:comp) { Vector2d.new(10, 0) }
|
|
268
|
+
|
|
269
|
+
it "moves the given distance" do
|
|
270
|
+
expect(vector.move_toward(comp, 2)).to eq(Vector2d.new(2, 0))
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
it "moves along the direction to the target" do
|
|
274
|
+
expect(Vector2d.new(2, 3).move_toward(Vector2d.new(5, 7), 2.5).round(3))
|
|
275
|
+
.to eq(Vector2d.new(3.5, 5))
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
it "stops at the target" do
|
|
279
|
+
expect(vector.move_toward(comp, 20)).to eq(comp)
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
it "stops at the target when the distance is exact" do
|
|
283
|
+
expect(vector.move_toward(comp, 10)).to eq(comp)
|
|
284
|
+
end
|
|
285
|
+
|
|
286
|
+
it "moves away from the target with a negative distance" do
|
|
287
|
+
expect(vector.move_toward(comp, -2)).to eq(Vector2d.new(-2, 0))
|
|
288
|
+
end
|
|
289
|
+
|
|
290
|
+
it "does not overshoot moving away" do
|
|
291
|
+
expect(vector.move_toward(comp, -20)).to eq(Vector2d.new(-20, 0))
|
|
292
|
+
end
|
|
293
|
+
|
|
294
|
+
it "coerces the target" do
|
|
295
|
+
expect(vector.move_toward([10, 0], 2)).to eq(Vector2d.new(2, 0))
|
|
296
|
+
end
|
|
297
|
+
|
|
298
|
+
it "converges on the target in steps" do
|
|
299
|
+
stepped = 6.times.reduce(vector) { |v, _| v.move_toward(comp, 2) }
|
|
300
|
+
|
|
301
|
+
expect(stepped).to eq(comp)
|
|
302
|
+
end
|
|
303
|
+
|
|
304
|
+
context "when the vector is already at the target" do
|
|
305
|
+
it "returns the target" do
|
|
306
|
+
expect(comp.move_toward(comp, 2)).to eq(comp)
|
|
307
|
+
end
|
|
308
|
+
|
|
309
|
+
it "returns the target with a negative distance" do
|
|
310
|
+
expect(comp.move_toward(comp, -2)).to eq(comp)
|
|
311
|
+
end
|
|
312
|
+
end
|
|
313
|
+
|
|
314
|
+
context "with a complex distance" do
|
|
315
|
+
it "raises an error" do
|
|
316
|
+
expect { vector.move_toward(comp, Complex(1, 2)) }.to(
|
|
317
|
+
raise_error(ArgumentError, "not a valid coordinate: (1+2i)")
|
|
318
|
+
)
|
|
319
|
+
end
|
|
320
|
+
end
|
|
321
|
+
end
|
|
322
|
+
end
|