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,457 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "spec_helper"
|
|
4
|
+
|
|
5
|
+
describe Vector2d::Lengths do
|
|
6
|
+
subject(:vector) { Vector2d.new(2, 3) }
|
|
7
|
+
|
|
8
|
+
describe "#length" do
|
|
9
|
+
it "calculates the length" do
|
|
10
|
+
expect(vector.length).to be_within(0.0001).of(3.6055)
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
describe "#magnitude" do
|
|
15
|
+
it "is an alias of #length" do
|
|
16
|
+
expect(vector.magnitude).to be_within(0.0001).of(3.6055)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
describe "#norm" do
|
|
21
|
+
it "is an alias of #length" do
|
|
22
|
+
expect(vector.norm).to be_within(0.0001).of(3.6055)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
describe "#length_squared" do
|
|
27
|
+
it "calculates the squared length" do
|
|
28
|
+
expect(vector.length_squared).to eq(13)
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
describe "#squared_length" do
|
|
33
|
+
subject(:vector) { Vector2d.new(2, 3).squared_length }
|
|
34
|
+
|
|
35
|
+
it_behaves_like "a deprecated method", "squared_length", "#length_squared"
|
|
36
|
+
|
|
37
|
+
it "returns the squared length, as #length_squared does" do
|
|
38
|
+
expect(vector).to eq(13)
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
describe "#zero?" do
|
|
43
|
+
subject { vector.zero? }
|
|
44
|
+
|
|
45
|
+
context "when vector is the zero vector" do
|
|
46
|
+
let(:vector) { Vector2d.new(0, 0) }
|
|
47
|
+
|
|
48
|
+
it { is_expected.to be(true) }
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
context "when vector isn't the zero vector" do
|
|
52
|
+
let(:vector) { Vector2d.new(2, 3) }
|
|
53
|
+
|
|
54
|
+
it { is_expected.to be(false) }
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
describe "#approx_zero?" do
|
|
59
|
+
subject { vector.approx_zero? }
|
|
60
|
+
|
|
61
|
+
context "when vector is the zero vector" do
|
|
62
|
+
let(:vector) { Vector2d.new(0, 0) }
|
|
63
|
+
|
|
64
|
+
it { is_expected.to be(true) }
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
context "when vector is a rounding error away from zero" do
|
|
68
|
+
let(:vector) { Vector2d.new(1e-17, 0) }
|
|
69
|
+
|
|
70
|
+
it { is_expected.to be(true) }
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
context "when vector is small but not that small" do
|
|
74
|
+
let(:vector) { Vector2d.new(1e-15, 0) }
|
|
75
|
+
|
|
76
|
+
it { is_expected.to be(false) }
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
context "when vector isn't the zero vector" do
|
|
80
|
+
it { is_expected.to be(false) }
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
context "with a tolerance" do
|
|
84
|
+
it "takes it as an absolute length" do
|
|
85
|
+
expect(Vector2d.new(0.2, 0)).to be_approx_zero(0.5)
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
it "is false outside the tolerance" do
|
|
89
|
+
expect(Vector2d.new(0.6, 0)).not_to be_approx_zero(0.5)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
it "raises an error on a complex tolerance" do
|
|
93
|
+
expect { vector.approx_zero?(Complex(1, 2)) }.to(
|
|
94
|
+
raise_error(ArgumentError, "not a valid coordinate: (1+2i)")
|
|
95
|
+
)
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
describe "#normalized?" do
|
|
101
|
+
subject { vector.normalized? }
|
|
102
|
+
|
|
103
|
+
context "when vector is normalized" do
|
|
104
|
+
let(:vector) { Vector2d.new(2, 3).normalize }
|
|
105
|
+
|
|
106
|
+
it { is_expected.to be(true) }
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
context "when vector isn't normalized" do
|
|
110
|
+
let(:vector) { Vector2d.new(2, 3) }
|
|
111
|
+
|
|
112
|
+
it { is_expected.to be(false) }
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
context "when vector is barely longer than a unit vector" do
|
|
116
|
+
let(:vector) { Vector2d.new(1 + 1e-9, 0) }
|
|
117
|
+
|
|
118
|
+
it { is_expected.to be(false) }
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
it "is true for any normalized vector" do
|
|
122
|
+
vectors = (1..50).flat_map do |x|
|
|
123
|
+
(1..50).map { |y| Vector2d.new(x, y).normalize }
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
expect(vectors).to all(be_normalized)
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
describe "#normalize" do
|
|
131
|
+
subject { vector.normalize }
|
|
132
|
+
|
|
133
|
+
its(:x) { is_expected.to be_within(0.0001).of(0.5547) }
|
|
134
|
+
its(:y) { is_expected.to be_within(0.0001).of(0.8320) }
|
|
135
|
+
|
|
136
|
+
context "with the zero vector" do
|
|
137
|
+
subject(:vector) { Vector2d.new(0, 0) }
|
|
138
|
+
|
|
139
|
+
it "returns the zero vector" do
|
|
140
|
+
expect(vector.normalize).to eq(Vector2d.new(0, 0))
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
describe "#resize" do
|
|
146
|
+
subject(:resized) { vector.resize(2.0) }
|
|
147
|
+
|
|
148
|
+
it "modifies the vector length" do
|
|
149
|
+
expect(resized.length).to be_within(0.0001).of(2.0)
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
it "modifies the x property" do
|
|
153
|
+
expect(resized.x).to be_within(0.0001).of(1.1094)
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
it "modifies the y property" do
|
|
157
|
+
expect(resized.y).to be_within(0.0001).of(1.6641)
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
context "with the zero vector" do
|
|
161
|
+
subject(:vector) { Vector2d.new(0, 0) }
|
|
162
|
+
|
|
163
|
+
it "returns the zero vector" do
|
|
164
|
+
expect(resized).to eq(Vector2d.new(0, 0))
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
context "with a float zero vector" do
|
|
169
|
+
subject(:vector) { Vector2d.new(0.0, 0.0) }
|
|
170
|
+
|
|
171
|
+
it "returns the zero vector" do
|
|
172
|
+
expect(resized).to eq(Vector2d.new(0.0, 0.0))
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
context "with a complex length" do
|
|
177
|
+
it "raises an error" do
|
|
178
|
+
expect { vector.resize(Complex(1, 2)) }.to(
|
|
179
|
+
raise_error(ArgumentError, "not a valid coordinate: (1+2i)")
|
|
180
|
+
)
|
|
181
|
+
end
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
context "with a complex length and the zero vector" do
|
|
185
|
+
subject(:vector) { Vector2d.new(0, 0) }
|
|
186
|
+
|
|
187
|
+
it "raises an error" do
|
|
188
|
+
expect { vector.resize(Complex(1, 2)) }.to(
|
|
189
|
+
raise_error(ArgumentError, "not a valid coordinate: (1+2i)")
|
|
190
|
+
)
|
|
191
|
+
end
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
context "with a negative length" do
|
|
195
|
+
subject(:resized) { vector.resize(-2.0) }
|
|
196
|
+
|
|
197
|
+
it "modifies the vector length" do
|
|
198
|
+
expect(resized.length).to be_within(0.0001).of(2.0)
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
it "reverses the x property" do
|
|
202
|
+
expect(resized.x).to be_within(0.0001).of(-1.1094)
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
it "reverses the y property" do
|
|
206
|
+
expect(resized.y).to be_within(0.0001).of(-1.6641)
|
|
207
|
+
end
|
|
208
|
+
end
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
describe "#limit_length" do
|
|
212
|
+
context "when argument is longer than length" do
|
|
213
|
+
let(:arg) { 5.0 }
|
|
214
|
+
|
|
215
|
+
it "does not change the length" do
|
|
216
|
+
expect(vector.limit_length(arg).length).to be_within(0.0001).of(3.6055)
|
|
217
|
+
end
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
context "when argument is shorter than length" do
|
|
221
|
+
let(:arg) { 2.5 }
|
|
222
|
+
|
|
223
|
+
it "changes the length" do
|
|
224
|
+
expect(vector.limit_length(arg).length).to be_within(0.0001).of(arg)
|
|
225
|
+
end
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
context "with the zero vector" do
|
|
229
|
+
subject(:vector) { Vector2d.new(0, 0) }
|
|
230
|
+
|
|
231
|
+
it "returns the zero vector" do
|
|
232
|
+
expect(vector.limit_length(5.0)).to eq(Vector2d.new(0, 0))
|
|
233
|
+
end
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
context "when argument is negative" do
|
|
237
|
+
it "raises an error" do
|
|
238
|
+
expect { vector.limit_length(-1.0) }.to(
|
|
239
|
+
raise_error(ArgumentError, "negative max length: -1.0")
|
|
240
|
+
)
|
|
241
|
+
end
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
context "when argument is negative and vector is zero" do
|
|
245
|
+
subject(:vector) { Vector2d.new(0, 0) }
|
|
246
|
+
|
|
247
|
+
it "raises an error" do
|
|
248
|
+
expect { vector.limit_length(-1.0) }.to raise_error(ArgumentError)
|
|
249
|
+
end
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
context "when argument is complex" do
|
|
253
|
+
it "raises an error" do
|
|
254
|
+
expect { vector.limit_length(Complex(1, 2)) }.to(
|
|
255
|
+
raise_error(ArgumentError, "not a valid coordinate: (1+2i)")
|
|
256
|
+
)
|
|
257
|
+
end
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
context "when argument is nil" do
|
|
261
|
+
it "raises an error" do
|
|
262
|
+
expect { vector.limit_length(nil) }.to(
|
|
263
|
+
raise_error(ArgumentError, "not a valid coordinate: nil")
|
|
264
|
+
)
|
|
265
|
+
end
|
|
266
|
+
end
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
describe "#clamp_length" do
|
|
270
|
+
it "raises an error without a maximum" do
|
|
271
|
+
expect { vector.clamp_length(2.5) }.to(
|
|
272
|
+
raise_error(ArgumentError,
|
|
273
|
+
"wrong number of arguments (given 1, expected 2)")
|
|
274
|
+
)
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
context "with a minimum and a maximum" do
|
|
278
|
+
it "scales a short vector up to the minimum" do
|
|
279
|
+
expect(vector.clamp_length(5.0, 10.0).length)
|
|
280
|
+
.to be_within(0.0001).of(5.0)
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
it "scales a long vector down to the maximum" do
|
|
284
|
+
expect(vector.clamp_length(0.5, 1.0).length)
|
|
285
|
+
.to be_within(0.0001).of(1.0)
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
it "leaves a vector between the bounds alone" do
|
|
289
|
+
expect(vector.clamp_length(1.0, 10.0).length)
|
|
290
|
+
.to be_within(0.0001).of(3.6055)
|
|
291
|
+
end
|
|
292
|
+
|
|
293
|
+
it "matches #limit_length when the minimum is zero" do
|
|
294
|
+
expect(vector.clamp_length(0, 2.5)).to eq(vector.limit_length(2.5))
|
|
295
|
+
end
|
|
296
|
+
|
|
297
|
+
it "raises an error when the minimum is negative" do
|
|
298
|
+
expect { vector.clamp_length(-1.0, 10.0) }.to(
|
|
299
|
+
raise_error(ArgumentError, "negative min length: -1.0")
|
|
300
|
+
)
|
|
301
|
+
end
|
|
302
|
+
|
|
303
|
+
it "raises an error when the minimum exceeds the maximum" do
|
|
304
|
+
expect { vector.clamp_length(10.0, 1.0) }.to raise_error(ArgumentError)
|
|
305
|
+
end
|
|
306
|
+
|
|
307
|
+
it "leaves the zero vector alone" do
|
|
308
|
+
expect(Vector2d.new(0, 0).clamp_length(5.0, 10.0))
|
|
309
|
+
.to eq(Vector2d.new(0, 0))
|
|
310
|
+
end
|
|
311
|
+
end
|
|
312
|
+
|
|
313
|
+
context "with a range" do
|
|
314
|
+
it "clamps the length between the bounds" do
|
|
315
|
+
expect(vector.clamp_length(5.0..10.0).length)
|
|
316
|
+
.to be_within(0.0001).of(5.0)
|
|
317
|
+
end
|
|
318
|
+
|
|
319
|
+
it "matches the two argument form" do
|
|
320
|
+
expect(vector.clamp_length(5.0..10.0))
|
|
321
|
+
.to eq(vector.clamp_length(5.0, 10.0))
|
|
322
|
+
end
|
|
323
|
+
|
|
324
|
+
it "clamps only the upper bound of a beginless range" do
|
|
325
|
+
expect(vector.clamp_length(..1.0).length).to be_within(0.0001).of(1.0)
|
|
326
|
+
end
|
|
327
|
+
|
|
328
|
+
it "clamps only the lower bound of an endless range" do
|
|
329
|
+
expect(vector.clamp_length(5.0..).length).to be_within(0.0001).of(5.0)
|
|
330
|
+
end
|
|
331
|
+
|
|
332
|
+
it "matches #limit_length for a beginless range" do
|
|
333
|
+
expect(vector.clamp_length(..1.0)).to eq(vector.limit_length(1.0))
|
|
334
|
+
end
|
|
335
|
+
|
|
336
|
+
it "raises an error with an exclusive range" do
|
|
337
|
+
expect { vector.clamp_length(5.0...10.0) }.to(
|
|
338
|
+
raise_error(ArgumentError, "cannot clamp with an exclusive range")
|
|
339
|
+
)
|
|
340
|
+
end
|
|
341
|
+
|
|
342
|
+
it "raises an error with a negative bound" do
|
|
343
|
+
expect { vector.clamp_length(-1.0..10.0) }.to(
|
|
344
|
+
raise_error(ArgumentError, "negative min length: -1.0")
|
|
345
|
+
)
|
|
346
|
+
end
|
|
347
|
+
|
|
348
|
+
it "raises an error with a second argument" do
|
|
349
|
+
expect { vector.clamp_length(5.0..10.0, 20.0) }.to(
|
|
350
|
+
raise_error(ArgumentError,
|
|
351
|
+
"wrong number of arguments (given 2, expected 1)")
|
|
352
|
+
)
|
|
353
|
+
end
|
|
354
|
+
end
|
|
355
|
+
end
|
|
356
|
+
|
|
357
|
+
describe "#distance" do
|
|
358
|
+
let(:comp) { Vector2d.new(3, 4) }
|
|
359
|
+
|
|
360
|
+
it "returns the distance between two vectors" do
|
|
361
|
+
expect(vector.distance(comp)).to be_within(0.0001).of(1.4142)
|
|
362
|
+
end
|
|
363
|
+
end
|
|
364
|
+
|
|
365
|
+
describe "#distance_squared" do
|
|
366
|
+
let(:comp) { Vector2d.new(5, 6) }
|
|
367
|
+
|
|
368
|
+
it "returns the squared distance between two vectors" do
|
|
369
|
+
expect(vector.distance_squared(comp)).to eq(18)
|
|
370
|
+
end
|
|
371
|
+
end
|
|
372
|
+
|
|
373
|
+
describe "#squared_distance" do
|
|
374
|
+
subject(:vector) { Vector2d.new(2, 3).squared_distance(Vector2d.new(5, 6)) }
|
|
375
|
+
|
|
376
|
+
it_behaves_like "a deprecated method", "squared_distance", "#distance_squared"
|
|
377
|
+
|
|
378
|
+
it "returns the squared distance, as #distance_squared does" do
|
|
379
|
+
expect(vector).to eq(18)
|
|
380
|
+
end
|
|
381
|
+
end
|
|
382
|
+
|
|
383
|
+
describe "#manhattan_distance" do
|
|
384
|
+
let(:comp) { Vector2d.new(5, 7) }
|
|
385
|
+
|
|
386
|
+
it "returns the sum of the absolute differences" do
|
|
387
|
+
expect(vector.manhattan_distance(comp)).to eq(7)
|
|
388
|
+
end
|
|
389
|
+
|
|
390
|
+
it "is symmetric" do
|
|
391
|
+
expect(comp.manhattan_distance(vector))
|
|
392
|
+
.to eq(vector.manhattan_distance(comp))
|
|
393
|
+
end
|
|
394
|
+
|
|
395
|
+
it "returns zero for the same vector" do
|
|
396
|
+
expect(vector.manhattan_distance(vector)).to eq(0)
|
|
397
|
+
end
|
|
398
|
+
|
|
399
|
+
it "coerces the argument" do
|
|
400
|
+
expect(vector.manhattan_distance([5, 7])).to eq(7)
|
|
401
|
+
end
|
|
402
|
+
end
|
|
403
|
+
|
|
404
|
+
describe "#chebyshev_distance" do
|
|
405
|
+
let(:comp) { Vector2d.new(5, 7) }
|
|
406
|
+
|
|
407
|
+
it "returns the largest absolute difference" do
|
|
408
|
+
expect(vector.chebyshev_distance(comp)).to eq(4)
|
|
409
|
+
end
|
|
410
|
+
|
|
411
|
+
it "is symmetric" do
|
|
412
|
+
expect(comp.chebyshev_distance(vector))
|
|
413
|
+
.to eq(vector.chebyshev_distance(comp))
|
|
414
|
+
end
|
|
415
|
+
|
|
416
|
+
it "returns zero for the same vector" do
|
|
417
|
+
expect(vector.chebyshev_distance(vector)).to eq(0)
|
|
418
|
+
end
|
|
419
|
+
|
|
420
|
+
it "coerces the argument" do
|
|
421
|
+
expect(vector.chebyshev_distance([5, 7])).to eq(4)
|
|
422
|
+
end
|
|
423
|
+
end
|
|
424
|
+
|
|
425
|
+
describe "#direction_to" do
|
|
426
|
+
subject(:vector) { Vector2d.new(2, 3) }
|
|
427
|
+
|
|
428
|
+
let(:comp) { Vector2d.new(5, 7) }
|
|
429
|
+
|
|
430
|
+
it "points at the other vector" do
|
|
431
|
+
expect(vector.direction_to(comp).round(3)).to eq(Vector2d.new(0.6, 0.8))
|
|
432
|
+
end
|
|
433
|
+
|
|
434
|
+
it "returns a unit vector" do
|
|
435
|
+
expect(vector.direction_to(comp).length).to be_within(0.0001).of(1.0)
|
|
436
|
+
end
|
|
437
|
+
|
|
438
|
+
it "matches the normalized difference" do
|
|
439
|
+
expect(vector.direction_to(comp)).to eq((comp - vector).normalize)
|
|
440
|
+
end
|
|
441
|
+
|
|
442
|
+
it "points the other way around" do
|
|
443
|
+
expect(vector.direction_to(comp))
|
|
444
|
+
.to eq(comp.direction_to(vector).reverse)
|
|
445
|
+
end
|
|
446
|
+
|
|
447
|
+
it "coerces the argument" do
|
|
448
|
+
expect(vector.direction_to([5, 7])).to eq(vector.direction_to(comp))
|
|
449
|
+
end
|
|
450
|
+
|
|
451
|
+
context "when the other vector is this one" do
|
|
452
|
+
it "returns the zero vector" do
|
|
453
|
+
expect(vector.direction_to(vector)).to eq(Vector2d.new(0, 0))
|
|
454
|
+
end
|
|
455
|
+
end
|
|
456
|
+
end
|
|
457
|
+
end
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "spec_helper"
|
|
4
|
+
require "matrix"
|
|
5
|
+
|
|
6
|
+
describe Vector2d::MatrixInterop do
|
|
7
|
+
subject(:vector) { Vector2d.new(2, 3) }
|
|
8
|
+
|
|
9
|
+
let(:rotation) { Matrix[[0, -1], [1, 0]] }
|
|
10
|
+
|
|
11
|
+
describe ".matrix?" do
|
|
12
|
+
it "recognizes a matrix" do
|
|
13
|
+
expect(described_class).to be_matrix(rotation)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
it "doesn't recognize anything else" do
|
|
17
|
+
expect(described_class).not_to be_matrix([[0, -1], [1, 0]])
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
describe ".vector?" do
|
|
22
|
+
it "recognizes a vector" do
|
|
23
|
+
expect(described_class).to be_vector(Vector[2, 3])
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
it "doesn't recognize anything else" do
|
|
27
|
+
expect(described_class).not_to be_vector(vector)
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
describe "#coerce" do
|
|
32
|
+
it "returns the matrix first, followed by itself as a vector" do
|
|
33
|
+
expect(vector.coerce(rotation)).to eq([rotation, Vector[2, 3]])
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
it "makes a vector the right hand operand of a matrix" do
|
|
37
|
+
expect(rotation * vector).to eq(Vector[-3, 2])
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
it "makes a vector the left hand operand of a Vector" do
|
|
41
|
+
expect(Vector[1, 2] + vector).to eq(Vector2d.new(3, 5))
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
context "when the other operand isn't a matrix" do
|
|
45
|
+
it "falls back to parsing the operand" do
|
|
46
|
+
expect(vector.coerce(4)).to eq([Vector2d.new(4, 4), vector])
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
describe "#to_matrix" do
|
|
52
|
+
it "returns a column matrix" do
|
|
53
|
+
expect(vector.to_matrix).to eq(Matrix[[2], [3]])
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
describe "#to_vector" do
|
|
58
|
+
it "returns a vector" do
|
|
59
|
+
expect(vector.to_vector).to eq(Vector[2, 3])
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
describe "#transform" do
|
|
64
|
+
it "multiplies the matrix by itself as a column" do
|
|
65
|
+
expect(vector.transform(rotation)).to eq(Vector2d.new(-3, 2))
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
context "when the argument isn't a matrix" do
|
|
69
|
+
it "raises a TypeError" do
|
|
70
|
+
expect { vector.transform([[0, -1], [1, 0]]) }.to(
|
|
71
|
+
raise_error(TypeError, "Array is not a Matrix")
|
|
72
|
+
)
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
context "when the matrix doesn't have two columns" do
|
|
77
|
+
it "raises a dimension mismatch" do
|
|
78
|
+
expect { vector.transform(Matrix[[1, 2, 3], [4, 5, 6]]) }.to(
|
|
79
|
+
raise_error(ExceptionForMatrix::ErrDimensionMismatch)
|
|
80
|
+
)
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
context "when the matrix holds a complex" do
|
|
85
|
+
it "raises an error" do
|
|
86
|
+
expect { vector.transform(Matrix[[Complex(0, 1), 0], [0, 1]]) }.to(
|
|
87
|
+
raise_error(ArgumentError, "not a valid coordinate: (0+2i)")
|
|
88
|
+
)
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|