@inglorious/utils 5.0.0 → 5.2.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/README.md +20 -1
- package/package.json +11 -3
- package/src/algorithms/path-finding.js +22 -18
- package/src/data-structures/array.js +0 -1
- package/src/data-structures/heap.js +10 -13
- package/src/data-structures/object.js +73 -46
- package/src/data-structures/string.js +0 -5
- package/src/math/hitmask.js +0 -2
- package/src/math/line.js +0 -1
- package/src/math/number.js +3 -3
- package/src/math/quaternion.js +2 -2
- package/src/math/rectangle.js +0 -1
- package/src/math/rng.js +0 -4
- package/src/math/trigonometry.js +2 -2
- package/src/math/vector.js +0 -3
- package/src/physics/acceleration.js +3 -6
- package/src/physics/friction.js +2 -2
- package/src/physics/gravity.js +5 -5
- package/src/physics/jump.js +3 -3
- package/src/physics/velocity.js +2 -2
- package/src/storage.js +52 -0
- package/types/data-structures/object.d.ts +3 -2
- package/types/storage.d.ts +29 -0
package/README.md
CHANGED
|
@@ -16,7 +16,7 @@ This package provides a collection of pure, composable helper functions to strea
|
|
|
16
16
|
## Installation
|
|
17
17
|
|
|
18
18
|
```bash
|
|
19
|
-
|
|
19
|
+
pnpm add @inglorious/utils
|
|
20
20
|
```
|
|
21
21
|
|
|
22
22
|
---
|
|
@@ -118,6 +118,21 @@ Vectors are mutable arrays, so copy a shared vector rather than storing it where
|
|
|
118
118
|
to one, such as an entity position. Copy it with `v(...ZERO_VECTOR)`: a plain spread
|
|
119
119
|
(`[...ZERO_VECTOR]`) gives you a new array but drops the tag that `isVector` looks for.
|
|
120
120
|
|
|
121
|
+
### Saving and loading JSON
|
|
122
|
+
|
|
123
|
+
Keep a value in `localStorage` between visits, such as a game's high scores or an app's
|
|
124
|
+
saved state. Neither function throws: absent or blocked storage, and a value that cannot be
|
|
125
|
+
parsed, read as the fallback, and a write that did not happen answers `false`.
|
|
126
|
+
|
|
127
|
+
```javascript
|
|
128
|
+
import { readJSON, writeJSON } from "@inglorious/utils/storage.js"
|
|
129
|
+
|
|
130
|
+
const scores = readJSON("high-scores", [])
|
|
131
|
+
writeJSON("high-scores", [...scores, 1200]) // => true
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
Pass another `Storage` as the last argument, such as `sessionStorage` or a fake in tests.
|
|
135
|
+
|
|
121
136
|
---
|
|
122
137
|
|
|
123
138
|
## API
|
|
@@ -139,6 +154,10 @@ know whether a helper lives under `data-structures` or `math`:
|
|
|
139
154
|
- **`@inglorious/utils/quaternion`**: `quaternion` and `combine`.
|
|
140
155
|
- **`@inglorious/utils/vector`**: Single-vector helpers (`magnitude`, `normalize`, `rotate`, ...) alongside multi-vector helpers (`cross`, `dot`, `sum`, ...).
|
|
141
156
|
|
|
157
|
+
One module stands on its own:
|
|
158
|
+
|
|
159
|
+
- **`@inglorious/utils/storage`**: `readJSON` and `writeJSON`, for keeping JSON in `localStorage`.
|
|
160
|
+
|
|
142
161
|
`divide`, `mod`, `multiply` and `power` accept either a scalar or one or more vectors:
|
|
143
162
|
a scalar is applied to every component, while vectors are combined component-wise.
|
|
144
163
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@inglorious/utils",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.2.0",
|
|
4
4
|
"description": "A set of general-purpose utility functions designed with functional programming principles in mind.",
|
|
5
5
|
"author": "Matteo Antony Mistretta <antony.mistretta@gmail.com> (https://ingloriouscoderz.it)",
|
|
6
6
|
"license": "MIT",
|
|
@@ -118,6 +118,14 @@
|
|
|
118
118
|
"types": "./types/math/vector.d.ts",
|
|
119
119
|
"import": "./src/math/vector.js"
|
|
120
120
|
},
|
|
121
|
+
"./storage.js": {
|
|
122
|
+
"types": "./types/storage.d.ts",
|
|
123
|
+
"import": "./src/storage.js"
|
|
124
|
+
},
|
|
125
|
+
"./storage": {
|
|
126
|
+
"types": "./types/storage.d.ts",
|
|
127
|
+
"import": "./src/storage.js"
|
|
128
|
+
},
|
|
121
129
|
"./v.js": {
|
|
122
130
|
"types": "./types/v.d.ts",
|
|
123
131
|
"import": "./src/v.js"
|
|
@@ -137,13 +145,13 @@
|
|
|
137
145
|
},
|
|
138
146
|
"sideEffects": false,
|
|
139
147
|
"devDependencies": {
|
|
140
|
-
"@inglorious/eslint-config": "1.
|
|
148
|
+
"@inglorious/eslint-config": "1.2.0",
|
|
141
149
|
"prettier": "^3.9.6",
|
|
142
150
|
"vite": "^8.2.2",
|
|
143
151
|
"vitest": "^5.0.0"
|
|
144
152
|
},
|
|
145
153
|
"engines": {
|
|
146
|
-
"node": ">=
|
|
154
|
+
"node": ">= 26"
|
|
147
155
|
},
|
|
148
156
|
"scripts": {
|
|
149
157
|
"format": "prettier --write .",
|
|
@@ -36,24 +36,6 @@ export const eucledianDistance = (a, b) =>
|
|
|
36
36
|
*/
|
|
37
37
|
export const manhattanDistance = (a, b) => abs(subtract(a.position, b.position))
|
|
38
38
|
|
|
39
|
-
/**
|
|
40
|
-
* Compares the cost of two nodes.
|
|
41
|
-
*
|
|
42
|
-
* @param {Node} a - The first node.
|
|
43
|
-
* @param {Node} b - The second node.
|
|
44
|
-
* @returns {number} - The difference in cost between the two nodes.
|
|
45
|
-
*/
|
|
46
|
-
const compareCost = (a, b) => b.cost - a.cost
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* Compares the total cost of two nodes.
|
|
50
|
-
*
|
|
51
|
-
* @param {Node} a - The first node.
|
|
52
|
-
* @param {Node} b - The second node.
|
|
53
|
-
* @returns {number} - The difference in total cost between the two nodes.
|
|
54
|
-
*/
|
|
55
|
-
const compareTotalCost = (a, b) => b.totalCost - a.totalCost
|
|
56
|
-
|
|
57
39
|
/**
|
|
58
40
|
* Finds the shortest path in a graph from a start node to an end node using a heuristic.
|
|
59
41
|
*
|
|
@@ -148,3 +130,25 @@ function adaptGraph(graph) {
|
|
|
148
130
|
function createFindNode(nodes) {
|
|
149
131
|
return (id) => nodes.find((node) => node.id === id)
|
|
150
132
|
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Compares the cost of two nodes.
|
|
136
|
+
*
|
|
137
|
+
* @param {Node} a - The first node.
|
|
138
|
+
* @param {Node} b - The second node.
|
|
139
|
+
* @returns {number} - The difference in cost between the two nodes.
|
|
140
|
+
*/
|
|
141
|
+
function compareCost(a, b) {
|
|
142
|
+
return b.cost - a.cost
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Compares the total cost of two nodes.
|
|
147
|
+
*
|
|
148
|
+
* @param {Node} a - The first node.
|
|
149
|
+
* @param {Node} b - The second node.
|
|
150
|
+
* @returns {number} - The difference in total cost between the two nodes.
|
|
151
|
+
*/
|
|
152
|
+
function compareTotalCost(a, b) {
|
|
153
|
+
return b.totalCost - a.totalCost
|
|
154
|
+
}
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
// @see https://stackfull.dev/heaps-in-javascript
|
|
2
|
-
|
|
3
1
|
/**
|
|
4
2
|
* Default comparator for a max-heap.
|
|
5
3
|
* @param {*} a - First element.
|
|
@@ -8,17 +6,16 @@
|
|
|
8
6
|
*/
|
|
9
7
|
const DEFAULT_COMPARATOR = (a, b) => b - a
|
|
10
8
|
|
|
11
|
-
|
|
12
|
-
const
|
|
13
|
-
const
|
|
14
|
-
const
|
|
15
|
-
const
|
|
16
|
-
const
|
|
17
|
-
const
|
|
18
|
-
const
|
|
19
|
-
const
|
|
20
|
-
const
|
|
21
|
-
const COMPARATOR_THRESHOLD = 0 // Threshold for comparator results.
|
|
9
|
+
const ROOT_INDEX = 0
|
|
10
|
+
const SINGLE_ELEMENT = 1
|
|
11
|
+
const LEFT_CHILD_MULTIPLIER = 2
|
|
12
|
+
const RIGHT_CHILD_MULTIPLIER = 2
|
|
13
|
+
const PARENT_DIVISOR = 2
|
|
14
|
+
const LEFT_CHILD_OFFSET = 1
|
|
15
|
+
const RIGHT_CHILD_OFFSET = 2
|
|
16
|
+
const LAST_ELEMENT_OFFSET = 1
|
|
17
|
+
const INDEX_ADJUSTMENT = 1
|
|
18
|
+
const COMPARATOR_THRESHOLD = 0
|
|
22
19
|
|
|
23
20
|
/**
|
|
24
21
|
* Checks if a heap contains a specific item.
|
|
@@ -8,57 +8,57 @@ const NEXT_KEY_OFFSET = 1
|
|
|
8
8
|
const NEXT_LEVEL = 2
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
|
-
* Creates a deep clone of a given value
|
|
12
|
-
* This is a modern and efficient way to deep-clone objects.
|
|
11
|
+
* Creates a deep clone of a given value.
|
|
13
12
|
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
13
|
+
* Plain objects, arrays and primitives -- what state is nearly always made of -- are
|
|
14
|
+
* copied by hand, because a general algorithm pays for being one: across thousands of
|
|
15
|
+
* small objects, `structuredClone` takes about twice as long and a JSON round trip
|
|
16
|
+
* longer still. Vectors stay vectors, which neither of those keeps: the tag that marks
|
|
17
|
+
* one is not enumerable, so anything that copies only what it can see drops it.
|
|
17
18
|
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
19
|
+
* Anything else is handed to `structuredClone`, so a `Date` or a `Map` is still copied
|
|
20
|
+
* properly, and DOM nodes or class instances with non-cloneable internals still throw a
|
|
21
|
+
* `DataCloneError`. Functions are kept as they are rather than copied, since there is
|
|
22
|
+
* nothing in one to copy.
|
|
20
23
|
*
|
|
21
24
|
* This is similar to Lodash's `_.cloneDeep`.
|
|
22
25
|
*
|
|
23
26
|
* @template T
|
|
24
|
-
* @param {T}
|
|
27
|
+
* @param {T} value - The value to clone.
|
|
25
28
|
* @returns {T} A deep clone of the input value.
|
|
26
29
|
*/
|
|
27
|
-
export function clone(
|
|
28
|
-
|
|
29
|
-
|
|
30
|
+
export function clone(value) {
|
|
31
|
+
if (value?.__isVector__) {
|
|
32
|
+
return v(...value)
|
|
33
|
+
}
|
|
30
34
|
|
|
31
|
-
|
|
32
|
-
|
|
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
|
-
}
|
|
35
|
+
if (isArray(value)) {
|
|
36
|
+
const copy = new Array(value.length)
|
|
49
37
|
|
|
50
|
-
|
|
51
|
-
|
|
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
|
|
38
|
+
for (let index = 0; index < value.length; index++) {
|
|
39
|
+
copy[index] = clone(value[index])
|
|
59
40
|
}
|
|
41
|
+
|
|
42
|
+
return copy
|
|
60
43
|
}
|
|
61
|
-
|
|
44
|
+
|
|
45
|
+
if (value === null || typeof value !== "object") {
|
|
46
|
+
return value
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const prototype = Object.getPrototypeOf(value)
|
|
50
|
+
|
|
51
|
+
if (prototype !== Object.prototype && prototype !== null) {
|
|
52
|
+
return structuredClone(value)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const copy = {}
|
|
56
|
+
|
|
57
|
+
for (const key in value) {
|
|
58
|
+
copy[key] = clone(value[key])
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return copy
|
|
62
62
|
}
|
|
63
63
|
|
|
64
64
|
/**
|
|
@@ -242,9 +242,6 @@ export function get(obj, path, defaultValue = undefined) {
|
|
|
242
242
|
export function isDeepEqual(left, right, seen = new WeakMap()) {
|
|
243
243
|
if (Object.is(left, right)) return true
|
|
244
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
245
|
if (typeof left !== "object" || typeof right !== "object") return false
|
|
249
246
|
if (left === null || right === null) return false
|
|
250
247
|
|
|
@@ -326,7 +323,7 @@ export function mergeWith(merger, target, ...sources) {
|
|
|
326
323
|
*
|
|
327
324
|
* **Important:** Unlike Immer, which uses structural sharing via proxies for
|
|
328
325
|
* high performance, this implementation performs a full deep clone of the base
|
|
329
|
-
* state on every call using `
|
|
326
|
+
* state on every call using `clone`. This can be
|
|
330
327
|
* inefficient for large or complex states. It is intended for simple use cases
|
|
331
328
|
* where the convenience of the API outweighs the performance cost.
|
|
332
329
|
*
|
|
@@ -370,7 +367,6 @@ export function produce(baseState, recipe, ...args) {
|
|
|
370
367
|
*/
|
|
371
368
|
export function serialize(obj) {
|
|
372
369
|
function replacer(key, value) {
|
|
373
|
-
// Handle top-stage vector
|
|
374
370
|
if (value?.__isVector__) {
|
|
375
371
|
return {
|
|
376
372
|
_type: "vector",
|
|
@@ -427,9 +423,7 @@ export function set(obj, path, value) {
|
|
|
427
423
|
const key = keys[i]
|
|
428
424
|
const nextKey = keys[i + NEXT_KEY_OFFSET]
|
|
429
425
|
|
|
430
|
-
// Create missing intermediate objects
|
|
431
426
|
if (!(key in current) || typeof current[key] !== "object") {
|
|
432
|
-
// Create array if next key is a number, object otherwise
|
|
433
427
|
current[key] = /^\d+$/.test(nextKey) ? [] : {}
|
|
434
428
|
}
|
|
435
429
|
|
|
@@ -480,3 +474,36 @@ ${" ".repeat(indentationLevel)}}`
|
|
|
480
474
|
|
|
481
475
|
return obj
|
|
482
476
|
}
|
|
477
|
+
|
|
478
|
+
/**
|
|
479
|
+
* Recursively merges properties from a source object into a target object.
|
|
480
|
+
* This is a helper function for `merge` and `extend`.
|
|
481
|
+
*
|
|
482
|
+
* @param {Object} target - The target object to merge into.
|
|
483
|
+
* @param {Object} source - The source object to merge from.
|
|
484
|
+
* @param {Function} [merger] - An optional function to customize merging behavior for specific keys.
|
|
485
|
+
* @returns {Object} - The modified target object.
|
|
486
|
+
*/
|
|
487
|
+
function deepMerge(target, source, merger) {
|
|
488
|
+
for (const [key, value] of Object.entries(source)) {
|
|
489
|
+
if (isFunction(merger)) {
|
|
490
|
+
const mergedValue = merger(target[key], value)
|
|
491
|
+
if (mergedValue !== undefined) {
|
|
492
|
+
target[key] = mergedValue
|
|
493
|
+
continue
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
if (isArray(value)) {
|
|
498
|
+
target[key] = value
|
|
499
|
+
} else if (isObject(value)) {
|
|
500
|
+
if (!isObject(target[key])) {
|
|
501
|
+
target[key] = {}
|
|
502
|
+
}
|
|
503
|
+
target[key] = deepMerge(target[key], value, merger)
|
|
504
|
+
} else {
|
|
505
|
+
target[key] = value
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
return target
|
|
509
|
+
}
|
|
@@ -12,8 +12,6 @@ export function toCamelCase(input) {
|
|
|
12
12
|
return [firstChar.toLowerCase(), ...rest].join("")
|
|
13
13
|
}
|
|
14
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
15
|
const ONE = 1
|
|
18
16
|
const NONE = 0
|
|
19
17
|
const FIRST = 0
|
|
@@ -56,9 +54,6 @@ export function editDistance(a, b) {
|
|
|
56
54
|
return grid[a.length][b.length]
|
|
57
55
|
}
|
|
58
56
|
|
|
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
57
|
const NEAR_ENOUGH = 3
|
|
63
58
|
|
|
64
59
|
/**
|
package/src/math/hitmask.js
CHANGED
package/src/math/line.js
CHANGED
|
@@ -20,7 +20,6 @@ export function distanceFromPoint(line, point) {
|
|
|
20
20
|
return abs(a * x + b * z + c) / hypothenuse(a, b)
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
-
// @see https://math.stackexchange.com/questions/275529/check-if-line-intersects-with-circles-perimeter
|
|
24
23
|
/**
|
|
25
24
|
* Determines whether a line intersects with the perimeter of a circle.
|
|
26
25
|
* @param {Line} line - The line represented by the equation ax + bz + c = 0, where `line` is [a, b, c].
|
package/src/math/number.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
const DEFAULT_PRECISION = 1
|
|
2
|
-
const DEFAULT_TOLERANCE = 0.1
|
|
3
|
-
const SQUARE_ROOT = 0.5
|
|
1
|
+
const DEFAULT_PRECISION = 1
|
|
2
|
+
const DEFAULT_TOLERANCE = 0.1
|
|
3
|
+
const SQUARE_ROOT = 0.5
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Returns the absolute value of a number.
|
package/src/math/quaternion.js
CHANGED
|
@@ -8,9 +8,9 @@ import { cos, sin } from "./trigonometry.js"
|
|
|
8
8
|
import { cross, dot, multiply, sum } from "./vector.js"
|
|
9
9
|
|
|
10
10
|
const DEFAULT_ANGLE = 0
|
|
11
|
-
const HALF = 2
|
|
11
|
+
const HALF = 2
|
|
12
12
|
// eslint-disable-next-line no-magic-numbers
|
|
13
|
-
const Y_AXIS = v(0, 1, 0)
|
|
13
|
+
const Y_AXIS = v(0, 1, 0)
|
|
14
14
|
|
|
15
15
|
/**
|
|
16
16
|
* Combines two quaternions using the Hamilton product.
|
package/src/math/rectangle.js
CHANGED
package/src/math/rng.js
CHANGED
|
@@ -31,10 +31,6 @@ export function random(...args) {
|
|
|
31
31
|
return Math.random()
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
-
// A range with a fractional bound cannot be stepped through as integers, so it
|
|
35
|
-
// is read as a continuous range. Requiring both bounds to be floats used to
|
|
36
|
-
// send `random(0.5, 1)` down the integer path, where it could only ever answer
|
|
37
|
-
// 0 or 1.
|
|
38
34
|
if (
|
|
39
35
|
args.length > 1 &&
|
|
40
36
|
(!Number.isInteger(args[0]) || !Number.isInteger(args[1]))
|
package/src/math/trigonometry.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { mod } from "./number.js"
|
|
2
2
|
|
|
3
|
-
const HALF_CIRCLE_IN_DEGRESS = 180
|
|
4
|
-
const FULL_CIRCLE = 2
|
|
3
|
+
const HALF_CIRCLE_IN_DEGRESS = 180
|
|
4
|
+
const FULL_CIRCLE = 2
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Calculates the arctangent of the quotient of its arguments.
|
package/src/math/vector.js
CHANGED
|
@@ -499,9 +499,7 @@ export function toSpherical(vector) {
|
|
|
499
499
|
return zero()
|
|
500
500
|
}
|
|
501
501
|
|
|
502
|
-
// In a Y-up system, inclination is the angle with the Y axis.
|
|
503
502
|
const inclination = Math.acos(vector[Y] / r)
|
|
504
|
-
// Azimuth is the angle in the XZ plane, which `angle()` calculates.
|
|
505
503
|
const azimuth = angle(vector)
|
|
506
504
|
|
|
507
505
|
return v(r, inclination, azimuth)
|
|
@@ -666,7 +664,6 @@ function rotateWithQuaternion(vector, angle) {
|
|
|
666
664
|
return vector
|
|
667
665
|
}
|
|
668
666
|
|
|
669
|
-
// @see https://en.wikipedia.org/wiki/Quaternions_and_spatial_rotation#Performance_comparisons
|
|
670
667
|
const [w, ...r] = quaternion(angle)
|
|
671
668
|
const result = sum(
|
|
672
669
|
vector,
|
|
@@ -5,9 +5,9 @@
|
|
|
5
5
|
import { clamp, multiply, sum, zero } from "../math/vector.js"
|
|
6
6
|
import { applyFriction } from "./friction.js"
|
|
7
7
|
|
|
8
|
-
const DEFAULT_DT = 1
|
|
9
|
-
const NO_FRICTION = 0
|
|
10
|
-
const HALF_ACCELERATION = 0.5
|
|
8
|
+
const DEFAULT_DT = 1
|
|
9
|
+
const NO_FRICTION = 0
|
|
10
|
+
const HALF_ACCELERATION = 0.5
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
13
|
* Applies acceleration to an object using Euler's integration method.
|
|
@@ -36,15 +36,12 @@ export function applyAcceleration(params, dt = DEFAULT_DT) {
|
|
|
36
36
|
friction = NO_FRICTION,
|
|
37
37
|
} = params
|
|
38
38
|
|
|
39
|
-
// Clamp acceleration to the maximum allowed range
|
|
40
39
|
acceleration = clamp(acceleration, -maxAcceleration, maxAcceleration)
|
|
41
40
|
|
|
42
|
-
// Update velocity with acceleration and clamp to the maximum allowed speed
|
|
43
41
|
velocity = sum(velocity, multiply(acceleration, dt))
|
|
44
42
|
velocity = clamp(velocity, -maxSpeed, maxSpeed)
|
|
45
43
|
velocity = applyFriction({ velocity, friction }, dt)
|
|
46
44
|
|
|
47
|
-
// Update position with velocity and acceleration
|
|
48
45
|
position = sum(
|
|
49
46
|
position,
|
|
50
47
|
multiply(velocity, dt),
|
package/src/physics/friction.js
CHANGED
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
|
|
5
5
|
import { magnitude, setMagnitude, zero } from "../math/vector.js"
|
|
6
6
|
|
|
7
|
-
const DEFAULT_DT = 1
|
|
8
|
-
const NO_FRICTION = 0
|
|
7
|
+
const DEFAULT_DT = 1
|
|
8
|
+
const NO_FRICTION = 0
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
11
|
* Applies friction to a given velocity vector.
|
package/src/physics/gravity.js
CHANGED
|
@@ -6,11 +6,11 @@
|
|
|
6
6
|
|
|
7
7
|
import { zero } from "../math/vector.js"
|
|
8
8
|
|
|
9
|
-
const DEFAULT_DT = 1
|
|
10
|
-
const DEFAULT_JUMP = 0
|
|
11
|
-
const DEFAULT_LEAP = 0
|
|
12
|
-
const DEFAULT_SPEED = 0
|
|
13
|
-
const NO_VELOCITY = 0
|
|
9
|
+
const DEFAULT_DT = 1
|
|
10
|
+
const DEFAULT_JUMP = 0
|
|
11
|
+
const DEFAULT_LEAP = 0
|
|
12
|
+
const DEFAULT_SPEED = 0
|
|
13
|
+
const NO_VELOCITY = 0
|
|
14
14
|
|
|
15
15
|
/**
|
|
16
16
|
* Applies gravity to an object based on its current velocity and position.
|
package/src/physics/jump.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/* eslint-disable no-magic-numbers */
|
|
2
2
|
|
|
3
|
-
const DEFAULT_MAX_JUMP = 0
|
|
4
|
-
const DEFAULT_MAX_LEAP = 0
|
|
5
|
-
const DEFAULT_MAX_SPEED = 0
|
|
3
|
+
const DEFAULT_MAX_JUMP = 0
|
|
4
|
+
const DEFAULT_MAX_LEAP = 0
|
|
5
|
+
const DEFAULT_MAX_SPEED = 0
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* Calculates the vertical velocity (vy) of a jump
|
package/src/physics/velocity.js
CHANGED
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
|
|
5
5
|
import { clamp, multiply, sum, zero } from "../math/vector.js"
|
|
6
6
|
|
|
7
|
-
const DEFAULT_DT = 1
|
|
8
|
-
const DEFAULT_MAX_SPEED = 0
|
|
7
|
+
const DEFAULT_DT = 1
|
|
8
|
+
const DEFAULT_MAX_SPEED = 0
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
11
|
* Applies velocity to a position based on the given parameters and options.
|
package/src/storage.js
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
const DEFAULT_STORAGE = () => globalThis.localStorage
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Reads a value back as JSON.
|
|
5
|
+
*
|
|
6
|
+
* Storage that is absent -- a server render, a private window, a test -- reads as the
|
|
7
|
+
* fallback rather than throwing, so a caller does not have to know whether it is running
|
|
8
|
+
* somewhere that has any. A value that cannot be parsed is treated the same way: what is
|
|
9
|
+
* stored is not this function's to defend, and refusing to start over it would be worse
|
|
10
|
+
* than starting again.
|
|
11
|
+
*
|
|
12
|
+
* @param {string} key - Where the value is kept.
|
|
13
|
+
* @param {*} fallback - What to answer when there is nothing usable to read.
|
|
14
|
+
* @param {Storage} [storage] - Where to read from. Defaults to the browser's own.
|
|
15
|
+
* @returns {*} The stored value, or the fallback.
|
|
16
|
+
*/
|
|
17
|
+
export function readJSON(key, fallback, storage) {
|
|
18
|
+
try {
|
|
19
|
+
const raw = store(storage)?.getItem(key)
|
|
20
|
+
if (raw === null || raw === undefined) return fallback
|
|
21
|
+
return JSON.parse(raw)
|
|
22
|
+
} catch {
|
|
23
|
+
return fallback
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Writes a value down as JSON, and answers whether it took.
|
|
29
|
+
*
|
|
30
|
+
* Storage can be full, absent, or refusing writes for its own reasons, and none of those
|
|
31
|
+
* is worth throwing over: a game that cannot remember a score can still be played. The
|
|
32
|
+
* answer is returned so that a caller which does care -- a test, mostly -- can say so.
|
|
33
|
+
*
|
|
34
|
+
* @param {string} key - Where to keep the value.
|
|
35
|
+
* @param {*} value - The value to keep.
|
|
36
|
+
* @param {Storage} [storage] - Where to write to. Defaults to the browser's own.
|
|
37
|
+
* @returns {boolean} `true` if the value was written.
|
|
38
|
+
*/
|
|
39
|
+
export function writeJSON(key, value, storage) {
|
|
40
|
+
try {
|
|
41
|
+
const target = store(storage)
|
|
42
|
+
if (!target) return false
|
|
43
|
+
target.setItem(key, JSON.stringify(value))
|
|
44
|
+
return true
|
|
45
|
+
} catch {
|
|
46
|
+
return false
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function store(storage) {
|
|
51
|
+
return storage ?? DEFAULT_STORAGE()
|
|
52
|
+
}
|
|
@@ -71,9 +71,10 @@ export type DefaultsResult<T extends object, D extends object> = Omit<
|
|
|
71
71
|
}
|
|
72
72
|
|
|
73
73
|
/**
|
|
74
|
-
* Creates a deep clone of a value.
|
|
74
|
+
* Creates a deep clone of a value. Plain data is copied by hand and vectors stay
|
|
75
|
+
* vectors; anything else goes through `structuredClone`.
|
|
75
76
|
*/
|
|
76
|
-
export function clone<T>(
|
|
77
|
+
export function clone<T>(value: T): T
|
|
77
78
|
|
|
78
79
|
/**
|
|
79
80
|
* Assigns default properties to a target in place.
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reads a value back as JSON. Absent storage, a missing key or a value that
|
|
3
|
+
* cannot be parsed all read as the fallback, rather than throwing.
|
|
4
|
+
*
|
|
5
|
+
* @param key - Where the value is kept.
|
|
6
|
+
* @param fallback - What to answer when there is nothing usable to read.
|
|
7
|
+
* @param storage - Where to read from. Defaults to the browser's own.
|
|
8
|
+
* @returns The stored value, or the fallback.
|
|
9
|
+
*/
|
|
10
|
+
export function readJSON<T>(
|
|
11
|
+
key: string,
|
|
12
|
+
fallback: T,
|
|
13
|
+
storage?: Storage | null,
|
|
14
|
+
): T
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Writes a value down as JSON, and answers whether it took. Absent, full or
|
|
18
|
+
* refusing storage answers `false` rather than throwing.
|
|
19
|
+
*
|
|
20
|
+
* @param key - Where to keep the value.
|
|
21
|
+
* @param value - The value to keep.
|
|
22
|
+
* @param storage - Where to write to. Defaults to the browser's own.
|
|
23
|
+
* @returns `true` if the value was written.
|
|
24
|
+
*/
|
|
25
|
+
export function writeJSON(
|
|
26
|
+
key: string,
|
|
27
|
+
value: unknown,
|
|
28
|
+
storage?: Storage | null,
|
|
29
|
+
): boolean
|