@inglorious/utils 2.2.0 → 2.3.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inglorious/utils",
3
- "version": "2.2.0",
3
+ "version": "2.3.0",
4
4
  "description": "A set of general-purpose utility functions designed with functional programming principles in mind.",
5
5
  "author": "IceOnFire <antony.mistretta@gmail.com> (https://ingloriouscoderz.it)",
6
6
  "license": "MIT",
@@ -1,3 +1,4 @@
1
+ export type Vector = number[]
1
2
  export type Vector2 = [number, number]
2
3
  export type Vector3 = [number, number, number]
3
4
  export type Quaternion = [number, number, number, number]
@@ -1,4 +1,5 @@
1
1
  /**
2
+ * @typedef {import('./types').Vector} Vector
2
3
  * @typedef {import('./types').Vector2} Vector2
3
4
  * @typedef {import('./types').Vector3} Vector3
4
5
  */
@@ -12,7 +13,6 @@ import {
12
13
  snap as nSnap,
13
14
  } from "../numbers.js"
14
15
  import { atan2, cos, sin } from "../trigonometry.js"
15
- import { from2D, to2D } from "./2d.js"
16
16
  import { quaternion } from "./quaternion.js"
17
17
  import { cross, sum } from "./vectors.js"
18
18
 
@@ -30,8 +30,9 @@ const DEFAULT_DECIMALS = 0
30
30
 
31
31
  /**
32
32
  * Returns the absolute value of each component in the vector.
33
- * @param {Vector3} vector - The input vector.
34
- * @returns {Vector3} The vector with absolute values.
33
+ *
34
+ * @param {Vector} vector - The input vector.
35
+ * @returns {Vector} The vector with absolute values.
35
36
  */
36
37
  export function abs(vector) {
37
38
  return vector.map(nAbs)
@@ -39,8 +40,9 @@ export function abs(vector) {
39
40
 
40
41
  /**
41
42
  * Calculates the angle of the vector in radians.
42
- * @param {Vector3} vector - The input vector.
43
- * @returns {number} The angle in radians.
43
+ *
44
+ * @param {Vector} vector - The input vector.
45
+ * @returns {number} The angle of the vector in the XZ plane (for 3D) or XY plane (for 2D), in radians.
44
46
  */
45
47
  export function angle(vector) {
46
48
  return atan2(vector[vector.length - LAST_COORDINATE], vector[X])
@@ -48,10 +50,11 @@ export function angle(vector) {
48
50
 
49
51
  /**
50
52
  * Clamps the magnitude of the vector between the given min and max values.
51
- * @param {Vector3} vector - The input vector.
52
- * @param {number} min - The minimum magnitude.
53
- * @param {number} max - The maximum magnitude.
54
- * @returns {Vector3} The clamped vector.
53
+ *
54
+ * @param {Vector} vector - The input vector.
55
+ * @param {number|Vector} min - The minimum magnitude or a vector representing the lower bounds for each component.
56
+ * @param {number|Vector} max - The maximum magnitude or a vector representing the upper bounds for each component.
57
+ * @returns {Vector} The clamped vector.
55
58
  */
56
59
  export function clamp(vector, min, max) {
57
60
  const length = magnitude(vector)
@@ -75,18 +78,20 @@ export function clamp(vector, min, max) {
75
78
 
76
79
  /**
77
80
  * Returns the conjugate of the vector.
78
- * @param {Vector3} vector - The input vector.
79
- * @returns {Vector3} The conjugated vector.
81
+ *
82
+ * @param {Vector} vector - The input vector.
83
+ * @returns {Vector} The conjugated vector.
80
84
  */
81
85
  export function conjugate(vector) {
82
86
  return vector.map((coordinate, index) => (index ? -coordinate : coordinate))
83
87
  }
84
88
 
85
89
  /**
86
- * Creates a vector with the given magnitude and angle.
90
+ * Creates a 3D vector with the given magnitude and angle.
91
+ *
87
92
  * @param {number} magnitude - The magnitude of the vector.
88
93
  * @param {number} angle - The angle of the vector in radians.
89
- * @returns {Vector3} The created vector.
94
+ * @returns {Vector3} The created 3D vector.
90
95
  */
91
96
  export function createVector(magnitude, angle) {
92
97
  return multiply(fromAngle(angle), magnitude)
@@ -94,16 +99,28 @@ export function createVector(magnitude, angle) {
94
99
 
95
100
  /**
96
101
  * Divides each component of the vector by the given scalar.
97
- * @param {Vector3} vector - The input vector.
102
+ *
103
+ * @param {Vector} vector - The input vector.
98
104
  * @param {number} scalar - The scalar value.
99
- * @returns {Vector3} The resulting vector.
105
+ * @returns {Vector} The resulting vector.
100
106
  */
101
107
  export function divide(vector, scalar) {
102
108
  return vector.map((coordinate) => coordinate / scalar)
103
109
  }
104
110
 
105
111
  /**
106
- * Creates a unit vector from the given angle.
112
+ * Converts a 2D vector [x, z] into a 3D vector [x, 0, z].
113
+ *
114
+ * @param {Vector2} vector - A 2D vector represented as [x, z].
115
+ * @returns {Vector3} A 3D vector represented as [x, 0, z].
116
+ */
117
+ export function from2D(vector) {
118
+ const [x, z] = vector
119
+ return [x, NO_Y, z]
120
+ }
121
+
122
+ /**
123
+ * Creates a 3D unit vector from the given angle in the XZ plane.
107
124
  * @param {number} angle - The angle in radians.
108
125
  * @returns {Vector3} The unit vector.
109
126
  */
@@ -127,8 +144,9 @@ export function isVector(value) {
127
144
  }
128
145
 
129
146
  /**
130
- * Calculates the magnitude of the vector.
131
- * @param {Vector3} vector - The input vector.
147
+ * Calculates the magnitude (length) of the vector.
148
+ *
149
+ * @param {Vector} vector - The input vector.
132
150
  * @returns {number} The magnitude of the vector.
133
151
  */
134
152
  export function magnitude(vector) {
@@ -137,9 +155,10 @@ export function magnitude(vector) {
137
155
 
138
156
  /**
139
157
  * Calculates the modulus of each component in the vector with the given divisor.
140
- * @param {Vector3} vector - The input vector.
158
+ *
159
+ * @param {Vector} vector - The input vector.
141
160
  * @param {number} divisor - The divisor value.
142
- * @returns {Vector3} The resulting vector.
161
+ * @returns {Vector} The resulting vector.
143
162
  */
144
163
  export function mod(vector, divisor) {
145
164
  return vector.map((coordinate) => nMod(coordinate, divisor))
@@ -147,9 +166,10 @@ export function mod(vector, divisor) {
147
166
 
148
167
  /**
149
168
  * Multiplies each component of the vector by the given scalar.
150
- * @param {Vector3} vector - The input vector.
169
+ *
170
+ * @param {Vector} vector - The input vector.
151
171
  * @param {number} scalar - The scalar value.
152
- * @returns {Vector3} The resulting vector.
172
+ * @returns {Vector} The resulting vector.
153
173
  */
154
174
  export function multiply(vector, scalar) {
155
175
  return vector.map((coordinate) => coordinate * scalar)
@@ -157,8 +177,9 @@ export function multiply(vector, scalar) {
157
177
 
158
178
  /**
159
179
  * Normalizes the vector to have a magnitude of 1.
160
- * @param {Vector3} vector - The input vector.
161
- * @returns {Vector3} The normalized vector.
180
+ *
181
+ * @param {Vector} vector - The input vector.
182
+ * @returns {Vector} The normalized vector.
162
183
  */
163
184
  export function normalize(vector) {
164
185
  const length = magnitude(vector)
@@ -168,10 +189,12 @@ export function normalize(vector) {
168
189
  export const remainder = mod
169
190
 
170
191
  /**
171
- * Rotates the vector by the given angle.
172
- * @param {Vector3} vector - The input vector.
192
+ * Rotates the vector by the given angle. Handles 2D and 3D vectors.
193
+ * For 3D vectors, rotation is around the Y-axis.
194
+ *
195
+ * @param {Vector} vector - The input vector (2D or 3D).
173
196
  * @param {number} angle - The angle in radians.
174
- * @returns {Vector3} The rotated vector.
197
+ * @returns {Vector} The rotated vector.
175
198
  */
176
199
  export function rotate(vector, angle) {
177
200
  const is2D = vector.length === TWO_COORDINATES
@@ -185,15 +208,17 @@ export function rotate(vector, angle) {
185
208
 
186
209
  /**
187
210
  * Scales the vector by the given scalar. Alias of `multiply`.
188
- * @param {Vector3} vector - The input vector.
211
+ *
212
+ * @param {Vector} vector - The input vector.
189
213
  * @param {number} scalar - The scalar value.
190
- * @returns {Vector3} The resulting vector.
214
+ * @returns {Vector} The resulting vector.
191
215
  */
192
216
  export const scale = multiply
193
217
 
194
218
  /**
195
- * Sets the angle of the vector while maintaining its magnitude.
196
- * @param {Vector3} vector - The input vector.
219
+ * Sets the angle of the vector in the XZ plane while maintaining its magnitude.
220
+ *
221
+ * @param {Vector} vector - The input vector.
197
222
  * @param {number} angle - The new angle in radians.
198
223
  * @returns {Vector3} The vector with the updated angle.
199
224
  */
@@ -210,9 +235,10 @@ export const setLength = setMagnitude
210
235
 
211
236
  /**
212
237
  * Sets the magnitude of the vector while maintaining its direction.
213
- * @param {Vector3} vector - The input vector.
238
+ *
239
+ * @param {Vector} vector - The input vector.
214
240
  * @param {number} length - The new magnitude.
215
- * @returns {Vector3} The vector with the updated magnitude.
241
+ * @returns {Vector} The vector with the updated magnitude.
216
242
  */
217
243
  export function setMagnitude(vector, length) {
218
244
  const normalized = normalize(vector)
@@ -221,9 +247,10 @@ export function setMagnitude(vector, length) {
221
247
 
222
248
  /**
223
249
  * Shifts the components of the vector by the given index.
224
- * @param {Vector3} vector - The input vector.
250
+ *
251
+ * @param {Vector} vector - The input vector.
225
252
  * @param {number} index - The index to shift by.
226
- * @returns {Vector3} The shifted vector.
253
+ * @returns {Vector} The shifted vector.
227
254
  */
228
255
  export function shift(vector, index) {
229
256
  return [...vector.slice(index), ...vector.slice(X, index)]
@@ -231,9 +258,10 @@ export function shift(vector, index) {
231
258
 
232
259
  /**
233
260
  * Snaps each component of the vector to the nearest multiple of the given precision.
234
- * @param {Vector3} vector - The input vector.
261
+ *
262
+ * @param {Vector} vector - The input vector.
235
263
  * @param {number} [precision=DEFAULT_PRECISION] - The precision value.
236
- * @returns {Vector3} The snapped vector.
264
+ * @returns {Vector} The snapped vector.
237
265
  */
238
266
  export function snap(vector, precision = DEFAULT_PRECISION) {
239
267
  return vector.map((coordinate) => nSnap(coordinate, -precision, precision))
@@ -242,7 +270,19 @@ export function snap(vector, precision = DEFAULT_PRECISION) {
242
270
  export const times = multiply
243
271
 
244
272
  /**
245
- * Converts polar coordinates to Cartesian coordinates.
273
+ * Converts a 3D vector [x, y, z] into a 2D vector [x, z].
274
+ *
275
+ * @param {Vector3} vector - A 3D vector represented as [x, y, z].
276
+ * @returns {Vector2} A 2D vector represented as [x, z].
277
+ */
278
+ export function to2D(vector) {
279
+ const [x, , z] = vector
280
+ return [x, z]
281
+ }
282
+
283
+ /**
284
+ * Converts 2D polar coordinates to 2D Cartesian coordinates.
285
+ *
246
286
  * @param {Vector2} vector - The polar coordinates [magnitude, angle].
247
287
  * @returns {Vector2} The Cartesian coordinates [x, y].
248
288
  */
@@ -251,9 +291,10 @@ export function toCartesian([magnitude, angle]) {
251
291
  }
252
292
 
253
293
  /**
254
- * Converts a vector to cylindrical coordinates.
255
- * @param {Vector2} vector - The input vector.
256
- * @returns {Vector2} The cylindrical coordinates [radius, theta, z].
294
+ * Converts a 3D cartesian vector to cylindrical coordinates.
295
+ *
296
+ * @param {Vector3} vector - The input vector [x, y, z].
297
+ * @returns {Vector3} The cylindrical coordinates [radius, theta, z].
257
298
  */
258
299
  export function toCylindrical(vector) {
259
300
  const radius = magnitude(vector)
@@ -262,8 +303,9 @@ export function toCylindrical(vector) {
262
303
  }
263
304
 
264
305
  /**
265
- * Converts a vector to polar coordinates.
266
- * @param {Vector2} vector - The input vector.
306
+ * Converts a 2D cartesian vector to 2D polar coordinates.
307
+ *
308
+ * @param {Vector2} vector - The input vector [x, y].
267
309
  * @returns {Vector2} The polar coordinates [magnitude, angle].
268
310
  */
269
311
  export function toPolar(vector) {
@@ -272,7 +314,8 @@ export function toPolar(vector) {
272
314
 
273
315
  /**
274
316
  * Converts a vector to a string representation.
275
- * @param {Vector3} vector - The input vector.
317
+ *
318
+ * @param {Vector} vector - The input vector.
276
319
  * @param {number} [decimals=DEFAULT_DECIMALS] - The number of decimal places.
277
320
  * @returns {string} The string representation of the vector.
278
321
  */
@@ -309,8 +352,9 @@ export function toSpherical(vector) {
309
352
  }
310
353
 
311
354
  /**
312
- * Creates a unit vector with the given angle.
313
- * @param {number} angle - The angle in radians.
355
+ * Creates a 3D unit vector in the XZ plane with the given angle.
356
+ *
357
+ * @param {number} [angle=0] - The angle in radians.
314
358
  * @returns {Vector3} The unit vector.
315
359
  */
316
360
  export function unit(angle) {
@@ -322,7 +366,19 @@ export function unit(angle) {
322
366
  }
323
367
 
324
368
  /**
325
- * Creates a zero vector.
369
+ * A utility function to create a vector from a list of coordinates.
370
+ * This is a shorthand for `[x, y, z, ...]`.
371
+ *
372
+ * @param {...number} coords - The coordinates of the vector.
373
+ * @returns {Vector} The created vector.
374
+ */
375
+ export function v(...coords) {
376
+ return coords
377
+ }
378
+
379
+ /**
380
+ * Creates a zero vector of 3 dimensions.
381
+ *
326
382
  * @returns {Vector3} The zero vector.
327
383
  */
328
384
  export function zero() {
@@ -330,7 +386,8 @@ export function zero() {
330
386
  }
331
387
 
332
388
  /**
333
- * Rotates the vector using a quaternion.
389
+ * Rotates a 3D vector around the Y-axis using a quaternion.
390
+ *
334
391
  * @param {Vector3} vector - The input vector.
335
392
  * @param {number} angle - The angle in radians.
336
393
  * @returns {Vector3} The rotated vector.
@@ -9,7 +9,9 @@ import {
9
9
  conjugate,
10
10
  createVector,
11
11
  divide,
12
+ from2D,
12
13
  fromAngle,
14
+ isVector,
13
15
  magnitude,
14
16
  mod,
15
17
  multiply,
@@ -19,12 +21,15 @@ import {
19
21
  setMagnitude,
20
22
  shift,
21
23
  snap,
24
+ to2D,
22
25
  toCartesian,
23
26
  toCylindrical,
24
27
  toPolar,
25
28
  toSpherical,
26
29
  toString,
27
30
  unit,
31
+ v,
32
+ zero,
28
33
  } from "./vector.js"
29
34
 
30
35
  test("it should compute the absolute value of a vector's coordinates", () => {
@@ -98,6 +103,13 @@ test("it should divide a vector by a scalar", () => {
98
103
  expect(divide(vector, scalar)).toStrictEqual(expectedResult)
99
104
  })
100
105
 
106
+ test("it should build a 3D vector from a 2D one", () => {
107
+ const vector = [3, 4]
108
+ const expectedResult = [3, 0, 4]
109
+
110
+ expect(from2D(vector)).toStrictEqual(expectedResult)
111
+ })
112
+
101
113
  test("it should create a 3D unit vector from an angle", () => {
102
114
  const angle = pi() / 4
103
115
  const expectedResult = [0.7071067811865475, -0, 0.7071067811865476] // close to [cos(angle), 0, sin(angle)]
@@ -112,6 +124,19 @@ test("it should compute the magnitude of a vector (aka length)", () => {
112
124
  expect(magnitude(vector)).toBe(expectedResult)
113
125
  })
114
126
 
127
+ test("it should check if a value is a vector", () => {
128
+ expect(isVector([1, 2, 3])).toBe(true)
129
+ expect(isVector([])).toBe(true)
130
+ })
131
+
132
+ test("it should check if a value is not a vector", () => {
133
+ expect(isVector([1, "a", 3])).toBe(false)
134
+ expect(isVector({ x: 1, y: 2 })).toBe(false)
135
+ expect(isVector(null)).toBe(false)
136
+ expect(isVector("vector")).toBe(false)
137
+ expect(isVector(123)).toBe(false)
138
+ })
139
+
115
140
  test("it should apply the mod operator (aka remainder) on a vector", () => {
116
141
  const vector = [10, 12, -18]
117
142
  const divisor = 12
@@ -213,6 +238,13 @@ test("it should snap a floating vector to a certain precision", () => {
213
238
  expect(snap(vector, precision)).toStrictEqual(expectedResult)
214
239
  })
215
240
 
241
+ test("it should build a 2D vector from a 3D one", () => {
242
+ const vector = [3, 0, 4]
243
+ const expectedResult = [3, 4]
244
+
245
+ expect(to2D(vector)).toStrictEqual(expectedResult)
246
+ })
247
+
216
248
  test("it should convert a 2D polar vector to cartesian coordinates", () => {
217
249
  const vector = [sqrt(2), pi() / 4]
218
250
  const expectedResult = [1.0000000000000002, 1]
@@ -263,3 +295,15 @@ test("it should create a unit vector oriented on the X-axis", () => {
263
295
  test("it should create a unit vector oriented on the Z-axis", () => {
264
296
  expect(unit(pi() / 2)).toStrictEqual([6.123233995736766e-17, 0, 1]) // close to [0, 0, 1]
265
297
  })
298
+
299
+ test("it should create a vector from a list of coordinates", () => {
300
+ expect(v(1, 2, 3)).toStrictEqual([1, 2, 3])
301
+ expect(v()).toStrictEqual([])
302
+ expect(v(0, -5.5)).toStrictEqual([0, -5.5])
303
+ })
304
+
305
+ test("it should create a 3D zero vector", () => {
306
+ const expectedResult = [0, 0, 0]
307
+
308
+ expect(zero()).toStrictEqual(expectedResult)
309
+ })
@@ -1,28 +0,0 @@
1
- /**
2
- * @typedef {import('../../../types').Vector2} Vector2
3
- * @typedef {import('../../../types').Vector3} Vector3
4
- */
5
-
6
- const NO_Y = 0 // Default Y-coordinate value for 3D vectors derived from 2D vectors.
7
-
8
- /**
9
- * Converts a 2D vector [x, z] into a 3D vector [x, 0, z].
10
- *
11
- * @param {Vector2} vector - A 2D vector represented as [x, z].
12
- * @returns {Vector3} A 3D vector represented as [x, 0, z].
13
- */
14
- export function from2D(vector) {
15
- const [x, z] = vector
16
- return [x, NO_Y, z]
17
- }
18
-
19
- /**
20
- * Converts a 3D vector [x, y, z] into a 2D vector [x, z].
21
- *
22
- * @param {Vector3} vector - A 3D vector represented as [x, y, z].
23
- * @returns {Vector2} A 2D vector represented as [x, z].
24
- */
25
- export function to2D(vector) {
26
- const [x, , z] = vector
27
- return [x, z]
28
- }
@@ -1,17 +0,0 @@
1
- import { expect, test } from "vitest"
2
-
3
- import { from2D, to2D } from "./2d.js"
4
-
5
- test("build a 3D vector from a 2D one", () => {
6
- const vector = [3, 4]
7
- const expectedResult = [3, 0, 4]
8
-
9
- expect(from2D(vector)).toStrictEqual(expectedResult)
10
- })
11
-
12
- test("build a 2D vector from a 3D one", () => {
13
- const vector = [3, 0, 4]
14
- const expectedResult = [3, 4]
15
-
16
- expect(to2D(vector)).toStrictEqual(expectedResult)
17
- })