@inglorious/utils 3.8.0 → 5.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 (73) hide show
  1. package/README.md +83 -3
  2. package/package.json +104 -12
  3. package/src/algorithms/decision-tree.js +3 -3
  4. package/src/algorithms/path-finding.js +6 -6
  5. package/src/data-structures/array.js +12 -0
  6. package/src/data-structures/board.js +97 -15
  7. package/src/data-structures/index.js +1 -2
  8. package/src/data-structures/object.js +164 -2
  9. package/src/data-structures/string.js +77 -0
  10. package/src/data-structures/tree.js +1 -1
  11. package/src/functions/function.js +190 -0
  12. package/src/functions/index.js +1 -4
  13. package/src/math/circle.js +5 -7
  14. package/src/math/hitmask.js +1 -1
  15. package/src/math/index.js +2 -6
  16. package/src/math/line.js +4 -4
  17. package/src/math/point.js +31 -9
  18. package/src/math/quaternion.js +19 -2
  19. package/src/math/rectangle.js +16 -4
  20. package/src/math/rng.js +5 -2
  21. package/src/math/segment.js +11 -5
  22. package/src/math/trigonometry.js +1 -1
  23. package/src/math/vector.js +288 -37
  24. package/src/physics/acceleration.js +2 -3
  25. package/src/physics/friction.js +1 -1
  26. package/src/physics/gravity.js +1 -1
  27. package/src/physics/jump.js +0 -4
  28. package/src/physics/velocity.js +2 -3
  29. package/src/v.js +1 -1
  30. package/types/algorithms/decision-tree.d.ts +27 -0
  31. package/types/algorithms/index.d.ts +2 -0
  32. package/types/algorithms/path-finding.d.ts +69 -0
  33. package/types/data-structures/array.d.ts +61 -0
  34. package/types/data-structures/board.d.ts +121 -0
  35. package/types/data-structures/boolean.d.ts +4 -0
  36. package/types/data-structures/heap.d.ts +69 -0
  37. package/types/data-structures/index.d.ts +8 -0
  38. package/types/data-structures/object.d.ts +237 -0
  39. package/types/data-structures/set.d.ts +4 -0
  40. package/types/data-structures/tree.d.ts +21 -0
  41. package/types/functions/function.d.ts +57 -0
  42. package/types/functions/index.d.ts +1 -0
  43. package/types/index.d.ts +6 -4
  44. package/types/math/circle.d.ts +22 -0
  45. package/types/math/hitmask.d.ts +20 -0
  46. package/types/math/index.d.ts +14 -0
  47. package/types/math/line.d.ts +11 -0
  48. package/types/math/linear-interpolation.d.ts +2 -0
  49. package/types/math/number.d.ts +26 -0
  50. package/types/math/point.d.ts +50 -0
  51. package/types/math/quaternion.d.ts +10 -0
  52. package/types/math/rectangle.d.ts +57 -0
  53. package/types/math/rng.d.ts +14 -0
  54. package/types/math/segment.d.ts +23 -0
  55. package/types/math/statistics.d.ts +11 -0
  56. package/types/math/triangle.d.ts +5 -0
  57. package/types/math/trigonometry.d.ts +26 -0
  58. package/types/math/vector.d.ts +219 -0
  59. package/types/physics/acceleration.d.ts +24 -0
  60. package/types/physics/friction.d.ts +11 -0
  61. package/types/physics/gravity.d.ts +20 -0
  62. package/types/physics/index.d.ts +5 -0
  63. package/types/physics/jump.d.ts +10 -0
  64. package/types/physics/velocity.d.ts +20 -0
  65. package/types/v.d.ts +7 -3
  66. package/src/data-structures/objects.js +0 -113
  67. package/src/functions/functions.js +0 -31
  68. package/src/math/quaternions.js +0 -23
  69. package/src/math/vectors.js +0 -212
  70. package/types/algorithms.d.ts +0 -26
  71. package/types/data-structures.d.ts +0 -53
  72. package/types/math.d.ts +0 -90
  73. /package/src/math/{numbers.js → number.js} +0 -0
@@ -1,3 +1,4 @@
1
+ import { isFunction } from "../functions/function.js"
1
2
  import { v } from "../v.js"
2
3
  import { isArray } from "./array.js"
3
4
 
@@ -27,6 +28,57 @@ export function clone(obj) {
27
28
  return deserialize(serialize(obj))
28
29
  }
29
30
 
31
+ /**
32
+ * Recursively merges properties from a source object into a target object.
33
+ * This is a helper function for `merge` and `extend`.
34
+ *
35
+ * @param {Object} target - The target object to merge into.
36
+ * @param {Object} source - The source object to merge from.
37
+ * @param {Function} [merger] - An optional function to customize merging behavior for specific keys.
38
+ * @returns {Object} - The modified target object.
39
+ */
40
+ function deepMerge(target, source, merger) {
41
+ for (const [key, value] of Object.entries(source)) {
42
+ if (isFunction(merger)) {
43
+ const mergedValue = merger(target[key], value)
44
+ if (mergedValue !== undefined) {
45
+ target[key] = mergedValue
46
+ continue
47
+ }
48
+ }
49
+
50
+ if (isArray(value)) {
51
+ target[key] = value
52
+ } else if (isObject(value)) {
53
+ if (!isObject(target[key])) {
54
+ target[key] = {}
55
+ }
56
+ target[key] = deepMerge(target[key], value, merger)
57
+ } else {
58
+ target[key] = value
59
+ }
60
+ }
61
+ return target
62
+ }
63
+
64
+ /**
65
+ * Assigns default properties to a target object from a source object.
66
+ * For each key in `defaultProps`, if `target[key]` is `null` or `undefined`,
67
+ * it is set to `defaultProps[key]`.
68
+ *
69
+ * This function modifies the target object in place.
70
+ *
71
+ * @param {Object} target The object to apply defaults to.
72
+ * @param {Object} defaultProps The object containing the default properties.
73
+ * @returns {Object} The modified target object.
74
+ */
75
+ export function defaults(target, defaultProps) {
76
+ for (const key in defaultProps) {
77
+ target[key] ??= defaultProps[key]
78
+ }
79
+ return target
80
+ }
81
+
30
82
  /**
31
83
  * Deserializes an object, converting plain object representations back into
32
84
  * their original types, such as vector-like objects. This is the inverse
@@ -66,6 +118,35 @@ export function deserialize(str) {
66
118
  }
67
119
  }
68
120
 
121
+ /**
122
+ * Creates a new object by deeply merging a target object with one or more source objects.
123
+ * This function is immutable; it does not modify the original `target` or `sources`.
124
+ * Nested objects are merged recursively.
125
+ * Arrays from a source object will overwrite arrays in the target object.
126
+ *
127
+ * @param {Object} target - The base object.
128
+ * @param {...Object} sources - The source objects to merge into the target object.
129
+ * @returns {Object} - A new object containing the merged properties.
130
+ * @see {@link merge} for a mutable version.
131
+ */
132
+ export function extend(target, ...sources) {
133
+ return extendWith(undefined, target, ...sources)
134
+ }
135
+
136
+ /**
137
+ * Creates a new object by deeply merging a target object with one or more source objects,
138
+ * using a custom merger function to handle specific properties.
139
+ * This function is immutable.
140
+ *
141
+ * @param {Function} [merger] - A function to customize merging behavior. It receives `(targetValue, sourceValue)`. If it returns `undefined`, the default merge logic is used.
142
+ * @param {Object} target - The base object.
143
+ * @param {...Object} sources - The source objects to merge.
144
+ * @returns {Object} A new object with the merged properties.
145
+ */
146
+ export function extendWith(merger, target, ...sources) {
147
+ return mergeWith(merger, {}, target, ...sources)
148
+ }
149
+
69
150
  /**
70
151
  * Filters the properties of an object based on a callback function.
71
152
  *
@@ -138,6 +219,57 @@ export function get(obj, path, defaultValue = undefined) {
138
219
  * @param {*} value - The value to check.
139
220
  * @returns {boolean} True if the value is a plain object, false otherwise.
140
221
  */
222
+ /**
223
+ * Whether two values are the same, all the way down.
224
+ *
225
+ * Written here rather than with `JSON.stringify` because stringify cannot tell a key
226
+ * that was left out from one that was set to nothing, and those are different: a key
227
+ * mapped to `undefined` is a key with nothing mapped to it, which is how a mapping is
228
+ * taken off.
229
+ *
230
+ * @param {*} left - The first value.
231
+ * @param {*} right - The second value.
232
+ * @param {WeakMap} [seen] - The pairs already compared, so a structure that contains
233
+ * itself is not walked for ever. Passed along rather than asked for.
234
+ *
235
+ * What it does not settle is whether two structures *share* the same parts. One value
236
+ * used twice against two equal ones written out twice are reported as different, because
237
+ * only the pairs already met are remembered. That errs towards calling things changed
238
+ * when they are not, which is the safe way round for the thing this is asked about.
239
+ *
240
+ * @returns {boolean} Whether the two are the same.
241
+ */
242
+ export function isDeepEqual(left, right, seen = new WeakMap()) {
243
+ if (Object.is(left, right)) return true
244
+
245
+ // `Object.is` has already said no to these two being the same object, and neither can be
246
+ // walked: a function is equal to itself and nothing else, and a key present in one and
247
+ // not the other is a difference even when both read as undefined.
248
+ if (typeof left !== "object" || typeof right !== "object") return false
249
+ if (left === null || right === null) return false
250
+
251
+ if (Array.isArray(left) !== Array.isArray(right)) return false
252
+
253
+ // A thing that contains itself would otherwise be walked for ever. Meeting the same
254
+ // thing twice means it has already been compared, and it was the same then or the walk
255
+ // would have stopped there -- so the only question left is whether it was matched to
256
+ // this one. A structure that loops back to itself differently is a difference, which is
257
+ // what stops this reporting two equal loops that are not.
258
+ if (seen.has(left)) return seen.get(left) === right
259
+
260
+ seen.set(left, right)
261
+
262
+ const leftKeys = Object.keys(left)
263
+ const rightKeys = Object.keys(right)
264
+
265
+ if (leftKeys.length !== rightKeys.length) return false
266
+
267
+ return leftKeys.every(
268
+ (key) =>
269
+ Object.hasOwn(right, key) && isDeepEqual(left[key], right[key], seen),
270
+ )
271
+ }
272
+
141
273
  export function isObject(value) {
142
274
  return value != null && value.constructor === Object
143
275
  }
@@ -157,6 +289,36 @@ export function map(obj, callback) {
157
289
  }, {})
158
290
  }
159
291
 
292
+ /**
293
+ * Merges multiple source objects into a target object.
294
+ * This function is mutable; it modifies the `target` object in place.
295
+ * Nested objects are merged recursively.
296
+ * Arrays from a source object will overwrite arrays in the target object.
297
+ *
298
+ * @param {Object} target - The target object to merge into.
299
+ * @param {...Object} sources - The source objects to merge from.
300
+ * @returns {Object} - The merged target object.
301
+ * @see {@link extend} for an immutable version.
302
+ */
303
+ export function merge(target, ...sources) {
304
+ return mergeWith(undefined, target, ...sources)
305
+ }
306
+
307
+ /**
308
+ * Merges multiple source objects into a target object, using a custom merger function.
309
+ * This function is mutable; it modifies the `target` object in place.
310
+ *
311
+ * @param {Function} [merger] - A function to customize merging behavior. It receives `(targetValue, sourceValue)`. If it returns `undefined`, the default merge logic is used.
312
+ * @param {Object} target - The target object to merge into.
313
+ * @param {...Object} sources - The source objects to merge from.
314
+ * @returns {Object} The merged target object.
315
+ */
316
+ export function mergeWith(merger, target, ...sources) {
317
+ return sources
318
+ .filter((source) => source != null)
319
+ .reduce((acc, source) => deepMerge(acc, source, merger), target)
320
+ }
321
+
160
322
  /**
161
323
  * A utility function inspired by Immer's `produce` API. It provides a convenient
162
324
  * way to work with immutable data structures by allowing "mutations" on a
@@ -208,7 +370,7 @@ export function produce(baseState, recipe, ...args) {
208
370
  */
209
371
  export function serialize(obj) {
210
372
  function replacer(key, value) {
211
- // Handle top-level vector
373
+ // Handle top-stage vector
212
374
  if (value?.__isVector__) {
213
375
  return {
214
376
  _type: "vector",
@@ -282,7 +444,7 @@ export function set(obj, path, value) {
282
444
  * Converts an object or array to a formatted string representation.
283
445
  *
284
446
  * @param {*} obj - The object or array to convert.
285
- * @param {number} [indentationLevel=INITIAL_LEVEL] - The current indentation level (used for nested structures).
447
+ * @param {number} [indentationLevel=INITIAL_LEVEL] - The current indentation stage (used for nested structures).
286
448
  * @returns {string} A string representation of the input object or array.
287
449
  */
288
450
  export function toString(obj, indentationLevel = INITIAL_LEVEL) {
@@ -11,3 +11,80 @@ export function toCamelCase(input) {
11
11
 
12
12
  return [firstChar.toLowerCase(), ...rest].join("")
13
13
  }
14
+
15
+ // How long a row or column is by one, which is the whole of what the offsets below mean.
16
+ // The grid's own edges are counted by the same one.
17
+ const ONE = 1
18
+ const NONE = 0
19
+ const FIRST = 0
20
+
21
+ /**
22
+ * How far one string is from another, in single-character edits.
23
+ *
24
+ * The grid is built in full rather than in the one row that would do, because this is
25
+ * short and the short version is where an off-by-one hides.
26
+ *
27
+ * @example
28
+ * ```js
29
+ * import { editDistance } from "@inglorious/utils/data-structures/string.js"
30
+ *
31
+ * editDistance("audio", "audi") // 1
32
+ * ```
33
+ *
34
+ * @param {string} a - The string that was asked about.
35
+ * @param {string} b - The string to compare it with.
36
+ * @returns {number} How many edits turn one into the other.
37
+ */
38
+ export function editDistance(a, b) {
39
+ const grid = Array.from({ length: a.length + ONE }, () =>
40
+ Array.from({ length: b.length + ONE }, () => NONE),
41
+ )
42
+
43
+ for (let i = FIRST; i <= a.length; i++) grid[i][FIRST] = i
44
+ for (let j = FIRST; j <= b.length; j++) grid[FIRST][j] = j
45
+
46
+ for (let i = ONE; i <= a.length; i++) {
47
+ for (let j = ONE; j <= b.length; j++) {
48
+ grid[i][j] = Math.min(
49
+ grid[i - ONE][j] + ONE,
50
+ grid[i][j - ONE] + ONE,
51
+ grid[i - ONE][j - ONE] + (a[i - ONE] === b[j - ONE] ? NONE : ONE),
52
+ )
53
+ }
54
+ }
55
+
56
+ return grid[a.length][b.length]
57
+ }
58
+
59
+ // How far a string may be from the one asked about and still be worth suggesting. A
60
+ // mistyped name is nearly always one or two edits away from the right one; anything
61
+ // further off is a different string rather than a wrong one.
62
+ const NEAR_ENOUGH = 3
63
+
64
+ /**
65
+ * The strings closest to one that was asked for, nearest first.
66
+ *
67
+ * Case is folded, because a name differing from a declared one only by a capital is the
68
+ * commonest kind of mistake and the one where saying nothing is least helpful.
69
+ *
70
+ * @example
71
+ * ```js
72
+ * import { namesNear } from "@inglorious/utils/data-structures/string.js"
73
+ *
74
+ * namesNear("audio", ["Game", "Audio"]) // ["Audio"]
75
+ * ```
76
+ *
77
+ * @param {string} wanted - The string that was asked for.
78
+ * @param {string[]} candidates - Every string that could have been meant.
79
+ * @returns {string[]} The ones worth suggesting, which may be none of them.
80
+ */
81
+ export function namesNear(wanted, candidates) {
82
+ const target = wanted.toLowerCase()
83
+ const allowed = Math.max(ONE, Math.floor(target.length / NEAR_ENOUGH))
84
+
85
+ return candidates
86
+ .map((name) => [name, editDistance(target, name.toLowerCase())])
87
+ .filter(([, d]) => d <= allowed)
88
+ .sort(([, a], [, b]) => a - b)
89
+ .map(([name]) => name)
90
+ }
@@ -1,4 +1,4 @@
1
- /** @typedef {import('../../types/data-structures').Tree} Tree */
1
+ /** @typedef {import('../../types/data-structures/tree').Tree} Tree */
2
2
 
3
3
  /**
4
4
  * Performs a breadth-first search (BFS) traversal on a tree structure.
@@ -1,3 +1,89 @@
1
+ const DEFAULT_DELAY = 0
2
+
3
+ /**
4
+ * Composes multiple functions from right to left, as if every function wraps the next one.
5
+ * The first function (rightmost) can take multiple arguments; the remaining functions must be unary.
6
+ *
7
+ * @param {...Function} fns - Functions to compose.
8
+ * @returns {Function} A function that takes the initial arguments and applies the composed functions.
9
+ */
10
+ export function compose(...fns) {
11
+ if (!fns.length) {
12
+ return (x) => x
13
+ }
14
+
15
+ const [last, ...rest] = fns.reverse()
16
+ return (...args) => rest.reduce((acc, fn) => fn(acc), last(...args))
17
+ }
18
+
19
+ /**
20
+ * Creates a debounced function that delays invoking `fn` until after `delay`
21
+ * milliseconds have elapsed since the last call.
22
+ *
23
+ * @param {Function} fn - The function to debounce.
24
+ * @param {number} [delay=0] - The number of milliseconds to delay.
25
+ * @returns {Function & { cancel: Function, flush: Function, pending: Function }} The debounced function.
26
+ */
27
+ export function debounce(fn, delay = DEFAULT_DELAY) {
28
+ let timeoutId
29
+ let lastArgs
30
+ let lastThis
31
+ let result
32
+
33
+ function clearPending() {
34
+ lastArgs = undefined
35
+ lastThis = undefined
36
+ }
37
+
38
+ function invoke() {
39
+ const args = lastArgs
40
+ const context = lastThis
41
+
42
+ clearPending()
43
+ result = fn.apply(context, args)
44
+ return result
45
+ }
46
+
47
+ function debounced(...args) {
48
+ lastArgs = args
49
+ lastThis = this
50
+
51
+ if (timeoutId !== undefined) {
52
+ clearTimeout(timeoutId)
53
+ }
54
+
55
+ timeoutId = setTimeout(() => {
56
+ timeoutId = undefined
57
+ invoke()
58
+ }, delay)
59
+
60
+ return result
61
+ }
62
+
63
+ debounced.cancel = () => {
64
+ if (timeoutId !== undefined) {
65
+ clearTimeout(timeoutId)
66
+ }
67
+
68
+ timeoutId = undefined
69
+ clearPending()
70
+ }
71
+
72
+ debounced.flush = () => {
73
+ if (timeoutId === undefined) {
74
+ return result
75
+ }
76
+
77
+ clearTimeout(timeoutId)
78
+ timeoutId = undefined
79
+ return invoke()
80
+ }
81
+
82
+ debounced.pending = () => timeoutId !== undefined
83
+
84
+ return debounced
85
+ }
86
+
1
87
  /**
2
88
  * Checks if a value is a function.
3
89
  *
@@ -7,3 +93,107 @@
7
93
  export function isFunction(func) {
8
94
  return typeof func === "function"
9
95
  }
96
+
97
+ /**
98
+ * Pipes multiple functions from left to right, as if the functions are applied one by one.
99
+ * The first function (leftmost) can take multiple arguments; the remaining functions must be unary.
100
+ *
101
+ * @param {...Function} fns - Functions to pipe.
102
+ * @returns {Function} A function that takes the initial arguments and applies the piped functions.
103
+ */
104
+ export function pipe(...fns) {
105
+ if (!fns.length) {
106
+ return (x) => x
107
+ }
108
+
109
+ const [first, ...rest] = fns
110
+ return (...args) => rest.reduce((acc, fn) => fn(acc), first(...args))
111
+ }
112
+
113
+ /**
114
+ * Creates a throttled function that invokes `fn` immediately on the leading
115
+ * edge, then suppresses further calls until `delay` milliseconds have
116
+ * elapsed.
117
+ *
118
+ * By default (`hasTrailing: false`), any calls made while suppressed are
119
+ * dropped entirely — only the leading call's arguments are ever used, and
120
+ * the very last call of a burst may be lost. Pass `{ hasTrailing: true }` to
121
+ * additionally invoke `fn` once more, after `delay` has elapsed, with the
122
+ * arguments from the most recent suppressed call — guaranteeing the final
123
+ * state of a burst is never silently dropped, at the cost of `fn` running
124
+ * again asynchronously after the burst settles.
125
+ *
126
+ * @param {Function} fn - The function to throttle.
127
+ * @param {number} [delay=0] - The number of milliseconds to suppress repeated calls.
128
+ * @param {Object} [options]
129
+ * @param {boolean} [options.hasTrailing=false] - Whether to invoke `fn` once
130
+ * more after `delay`, using the arguments of the last suppressed call.
131
+ * @returns {Function & { cancel: Function, pending: Function }} The throttled function.
132
+ */
133
+ export function throttle(
134
+ fn,
135
+ delay = DEFAULT_DELAY,
136
+ { hasTrailing = false } = {},
137
+ ) {
138
+ let timeoutId
139
+ let result
140
+ let lastArgs
141
+ let lastThis
142
+ let trailingCallPending = false
143
+
144
+ function clearPending() {
145
+ lastArgs = undefined
146
+ lastThis = undefined
147
+ trailingCallPending = false
148
+ }
149
+
150
+ function startWindow() {
151
+ timeoutId = setTimeout(() => {
152
+ timeoutId = undefined
153
+
154
+ // NOTE: this runs asynchronously, after `delay` has elapsed — so this
155
+ // is the one path where `fn` is not invoked synchronously with the
156
+ // triggering call. Callers that pass draft/proxy objects through `fn`
157
+ // (e.g. a mutative entity) must treat this like a debounced call: only
158
+ // hold onto primitives across the throttle boundary, and re-fetch any
159
+ // live object when `fn` actually runs here.
160
+ if (trailingCallPending) {
161
+ const args = lastArgs
162
+ const context = lastThis
163
+ clearPending()
164
+ result = fn.apply(context, args)
165
+ }
166
+ }, delay)
167
+ }
168
+
169
+ function throttled(...args) {
170
+ lastArgs = args
171
+ lastThis = this
172
+
173
+ if (timeoutId !== undefined) {
174
+ if (hasTrailing) {
175
+ trailingCallPending = true
176
+ }
177
+ return result
178
+ }
179
+
180
+ result = fn.apply(this, args)
181
+ lastArgs = undefined
182
+ lastThis = undefined
183
+ startWindow()
184
+ return result
185
+ }
186
+
187
+ throttled.cancel = () => {
188
+ if (timeoutId !== undefined) {
189
+ clearTimeout(timeoutId)
190
+ }
191
+
192
+ timeoutId = undefined
193
+ clearPending()
194
+ }
195
+
196
+ throttled.pending = () => timeoutId !== undefined || trailingCallPending
197
+
198
+ return throttled
199
+ }
@@ -1,4 +1 @@
1
- import * as func from "./function.js"
2
- import * as funcs from "./functions.js"
3
-
4
- export { func, funcs }
1
+ export * from "./function.js"
@@ -1,14 +1,12 @@
1
1
  /**
2
- * @typedef {import("../../types/math").Point} Point
3
- * @typedef {import("../../types/math").Circle} Circle
4
- * @typedef {import("../../types/math").Rectangle} Rectangle
5
- * @typedef {import("../../types/math").Platform} Platform
2
+ * @typedef {import("../../types/math/point").PointInput} PointInput
3
+ * @typedef {import("../../types/math/circle").Circle} Circle
4
+ * @typedef {import("../../types/math/rectangle").Rectangle} Rectangle
6
5
  */
7
6
 
8
7
  import { intersectsCircle as pointIntersectsCircle } from "./point.js"
9
8
  import { hypothenuse } from "./triangle.js"
10
- import { clamp } from "./vector.js"
11
- import { subtract } from "./vectors.js"
9
+ import { clamp, subtract } from "./vector.js"
12
10
 
13
11
  const HALF = 2
14
12
  const SQUARED = 2
@@ -17,7 +15,7 @@ const INITIAL_SUM = 0
17
15
  /**
18
16
  * Checks if a circle intersects with a point.
19
17
  * @param {Circle} circle - The circle to check.
20
- * @param {Point} point - The point to check.
18
+ * @param {PointInput} point - The point to check.
21
19
  * @returns {boolean} True if the point intersects the circle, false otherwise.
22
20
  */
23
21
  export function intersectsPoint(circle, point) {
@@ -1,6 +1,6 @@
1
1
  // @see https://jonathanwhiting.com/tutorial/collision/
2
2
 
3
- import { isBetween } from "./numbers.js"
3
+ import { isBetween } from "./number.js"
4
4
  import { intersectsRectangle } from "./rectangle.js"
5
5
 
6
6
  const FIRST = 0
package/src/math/index.js CHANGED
@@ -2,10 +2,9 @@ import * as circle from "./circle.js"
2
2
  import * as hitmask from "./hitmask.js"
3
3
  import * as line from "./line.js"
4
4
  import * as linearInterpolation from "./linear-interpolation.js"
5
- import * as numbers from "./numbers.js"
5
+ import * as numbers from "./number.js"
6
6
  import * as point from "./point.js"
7
- import * as quaternion from "./quaternion.js"
8
- import * as quaternions from "./quaternions.js"
7
+ import * as quaternions from "./quaternion.js"
9
8
  import * as rectangle from "./rectangle.js"
10
9
  import * as rng from "./rng.js"
11
10
  import * as segment from "./segment.js"
@@ -13,7 +12,6 @@ import * as statistics from "./statistics.js"
13
12
  import * as triangle from "./triangle.js"
14
13
  import * as trigonometry from "./trigonometry.js"
15
14
  import * as vector from "./vector.js"
16
- import * as vectors from "./vectors.js"
17
15
 
18
16
  export {
19
17
  circle,
@@ -22,7 +20,6 @@ export {
22
20
  linearInterpolation,
23
21
  numbers,
24
22
  point,
25
- quaternion,
26
23
  quaternions,
27
24
  rectangle,
28
25
  rng,
@@ -31,5 +28,4 @@ export {
31
28
  triangle,
32
29
  trigonometry,
33
30
  vector,
34
- vectors,
35
31
  }
package/src/math/line.js CHANGED
@@ -1,10 +1,10 @@
1
1
  /**
2
- * @typedef {import("../../types/math").Line} Line
3
- * @typedef {import("../../types/math").Point} Point
4
- * @typedef {import("../../types/math").Circle} Circle
2
+ * @typedef {import("../../types/math/line").Line} Line
3
+ * @typedef {import("../../types/math/point").Point} Point
4
+ * @typedef {import("../../types/math/circle").Circle} Circle
5
5
  */
6
6
 
7
- import { abs } from "./numbers.js"
7
+ import { abs } from "./number.js"
8
8
  import { hypothenuse } from "./triangle.js"
9
9
 
10
10
  /**
package/src/math/point.js CHANGED
@@ -1,15 +1,37 @@
1
1
  /**
2
- * @typedef {import("../../types/math").Point} Point
3
- * @typedef {import("../../types/math").Line} Line
4
- * @typedef {import("../../types/math").Circle} Circle
5
- * @typedef {import("../../types/math").Rectangle} Rectangle
2
+ * A point in the Inglorious coordinate system: `[x, y, z]`.
3
+ *
4
+ * The world is y-up: the origin sits on the floor, and a bigger `y` is always a
5
+ * higher place in the world. Gravity subtracts from it, a jump adds to it, and an
6
+ * object resting on the ground has `y = 0`.
7
+ *
8
+ * `z` is the depth axis, and it is y-up as well — it is not screen-down, so both
9
+ * vertical axes grow upwards. Renderers turn them into screen coordinates:
10
+ * `@inglorious/renderer-2d` projects a point to `canvasY = height - y - z`,
11
+ * which puts `(0, 0, 0)` at the bottom-left of the canvas and turns both axes
12
+ * upside down.
13
+ *
14
+ * @example Reading a position in a 2D game
15
+ * ```js
16
+ * v(0, 0, 0) // the floor
17
+ * v(0, 100, 0) // a hundred pixels up
18
+ *
19
+ * entity.velocity[1] -= GRAVITY * dt // falling loses height
20
+ * entity.position[1] = 0 // keeping a body on the ground
21
+ * ```
22
+ *
23
+ * @typedef {import("../../types/math/point").Point} Point
24
+ * @typedef {import("../../types/math/point").PointInput} PointInput
25
+ * @typedef {import("../../types/math/line").Line} Line
26
+ * @typedef {import("../../types/math/circle").Circle} Circle
27
+ * @typedef {import("../../types/math/rectangle").Rectangle} Rectangle
6
28
  */
7
29
 
8
30
  const SQUARED = 2
9
31
  const HALF = 2
10
32
 
11
33
  import { distanceFromPoint } from "./line.js"
12
- import { isBetween } from "./numbers.js"
34
+ import { isBetween } from "./number.js"
13
35
  import { isVector } from "./vector.js"
14
36
 
15
37
  /**
@@ -24,8 +46,8 @@ export function getDistanceFromLine(point, line) {
24
46
 
25
47
  /**
26
48
  * Checks if two points intersect.
27
- * @param {Point} point1 - The first point as a 3D coordinate [x, y, z].
28
- * @param {Point} point2 - The second point as a 3D coordinate [x, y, z].
49
+ * @param {PointInput} point1 - The first point as a 3D coordinate [x, y, z].
50
+ * @param {PointInput} point2 - The second point as a 3D coordinate [x, y, z].
29
51
  * @returns {boolean} True if the points intersect, false otherwise.
30
52
  */
31
53
  export function intersectsPoint(point1, point2) {
@@ -36,7 +58,7 @@ export function intersectsPoint(point1, point2) {
36
58
 
37
59
  /**
38
60
  * Checks if a point intersects with a circle.
39
- * @param {Point} point - The point as a 3D coordinate [x, y, z].
61
+ * @param {PointInput} point - The point as a 3D coordinate [x, y, z].
40
62
  * @param {Circle} circle - The circle with a position and radius.
41
63
  * @returns {boolean} True if the point intersects the circle, false otherwise.
42
64
  */
@@ -53,7 +75,7 @@ export function intersectsCircle(point, circle) {
53
75
 
54
76
  /**
55
77
  * Checks if a point intersects with a rectangle.
56
- * @param {Point} point - The point as a 3D coordinate [x, y, z].
78
+ * @param {PointInput} point - The point as a 3D coordinate [x, y, z].
57
79
  * @param {Rectangle} rectangle - The rectangle with a position and size.
58
80
  * @returns {boolean} True if the point intersects the rectangle, false otherwise.
59
81
  */