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.
Files changed (70) hide show
  1. checksums.yaml +4 -4
  2. data/.github/dependabot.yml +39 -0
  3. data/.github/workflows/build.yml +48 -10
  4. data/.gitignore +3 -1
  5. data/.release-please-manifest.json +3 -0
  6. data/.rubocop.yml +5 -2
  7. data/.tool-versions +1 -0
  8. data/.yardopts +11 -0
  9. data/CHANGELOG.md +224 -0
  10. data/CODE_OF_CONDUCT.md +92 -0
  11. data/CONTRIBUTING.md +30 -0
  12. data/Gemfile +6 -2
  13. data/Gemfile.lock +85 -51
  14. data/README.md +330 -13
  15. data/Rakefile +21 -0
  16. data/benchmark/vector_comparison.rb +129 -0
  17. data/lib/vector2d/angles.rb +315 -0
  18. data/lib/vector2d/arithmetic.rb +97 -0
  19. data/lib/vector2d/comparison.rb +188 -0
  20. data/lib/vector2d/componentwise.rb +239 -0
  21. data/lib/vector2d/constructors.rb +137 -0
  22. data/lib/vector2d/conversions.rb +139 -0
  23. data/lib/vector2d/coordinates.rb +22 -0
  24. data/lib/vector2d/deprecation.rb +16 -0
  25. data/lib/vector2d/dimensions.rb +297 -0
  26. data/lib/vector2d/interpolation.rb +191 -0
  27. data/lib/vector2d/lengths.rb +284 -0
  28. data/lib/vector2d/matrix_interop.rb +93 -0
  29. data/lib/vector2d/parsing.rb +142 -0
  30. data/lib/vector2d/projection.rb +208 -0
  31. data/lib/vector2d/version.rb +2 -1
  32. data/lib/vector2d.rb +174 -62
  33. data/release-please-config.json +10 -0
  34. data/spec/lib/vector2d/angles_spec.rb +441 -0
  35. data/spec/lib/vector2d/{calculations_spec.rb → arithmetic_spec.rb} +48 -58
  36. data/spec/lib/vector2d/comparison_spec.rb +358 -0
  37. data/spec/lib/vector2d/componentwise_spec.rb +300 -0
  38. data/spec/lib/vector2d/constructors_spec.rb +299 -0
  39. data/spec/lib/vector2d/conversions_spec.rb +234 -0
  40. data/spec/lib/vector2d/dimensions_spec.rb +820 -0
  41. data/spec/lib/vector2d/interpolation_spec.rb +322 -0
  42. data/spec/lib/vector2d/lengths_spec.rb +457 -0
  43. data/spec/lib/vector2d/matrix_interop_spec.rb +92 -0
  44. data/spec/lib/vector2d/parsing_spec.rb +329 -0
  45. data/spec/lib/vector2d/projection_spec.rb +306 -0
  46. data/spec/lib/vector2d_documentation_spec.rb +38 -0
  47. data/spec/lib/vector2d_immutability_spec.rb +263 -0
  48. data/spec/lib/vector2d_spec.rb +90 -50
  49. data/spec/lib/vector2d_subclassing_spec.rb +210 -0
  50. data/spec/lib/vector2d_tags_spec.rb +43 -0
  51. data/spec/spec_helper.rb +2 -0
  52. data/spec/support/doc_examples/comments.rb +65 -0
  53. data/spec/support/doc_examples/markdown.rb +50 -0
  54. data/spec/support/doc_examples.rb +94 -0
  55. data/spec/support/doc_tags.rb +151 -0
  56. data/spec/support/shared_examples/class_preserving_method.rb +10 -0
  57. data/spec/support/shared_examples/deprecated_method.rb +25 -0
  58. data/spec/support/shared_examples/parsed_vector.rb +11 -0
  59. data/vector2d.gemspec +9 -3
  60. metadata +55 -19
  61. data/.travis.yml +0 -10
  62. data/lib/vector2d/calculations.rb +0 -141
  63. data/lib/vector2d/coercions.rb +0 -64
  64. data/lib/vector2d/fitting.rb +0 -60
  65. data/lib/vector2d/properties.rb +0 -46
  66. data/lib/vector2d/transformations.rb +0 -85
  67. data/spec/lib/vector2d/coercions_spec.rb +0 -59
  68. data/spec/lib/vector2d/fitting_spec.rb +0 -70
  69. data/spec/lib/vector2d/properties_spec.rb +0 -47
  70. 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