@benev/math 0.0.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.
- package/license +23 -0
- package/package.json +41 -0
- package/readme.md +3 -0
- package/s/concepts/angles.ts +74 -0
- package/s/concepts/circular.ts +72 -0
- package/s/concepts/noise.ts +23 -0
- package/s/concepts/quat.ts +131 -0
- package/s/concepts/randy.ts +116 -0
- package/s/concepts/scalar.ts +224 -0
- package/s/concepts/spline.ts +88 -0
- package/s/concepts/vec2.ts +392 -0
- package/s/concepts/vec3.ts +439 -0
- package/s/concepts/vec4.ts +74 -0
- package/s/index.ts +12 -0
- package/x/concepts/angles.d.ts +29 -0
- package/x/concepts/angles.js +62 -0
- package/x/concepts/angles.js.map +1 -0
- package/x/concepts/circular.d.ts +17 -0
- package/x/concepts/circular.js +70 -0
- package/x/concepts/circular.js.map +1 -0
- package/x/concepts/noise.d.ts +9 -0
- package/x/concepts/noise.js +20 -0
- package/x/concepts/noise.js.map +1 -0
- package/x/concepts/quat.d.ts +35 -0
- package/x/concepts/quat.js +110 -0
- package/x/concepts/quat.js.map +1 -0
- package/x/concepts/randy.d.ts +39 -0
- package/x/concepts/randy.js +95 -0
- package/x/concepts/randy.js.map +1 -0
- package/x/concepts/scalar.d.ts +51 -0
- package/x/concepts/scalar.js +219 -0
- package/x/concepts/scalar.js.map +1 -0
- package/x/concepts/spline.d.ts +9 -0
- package/x/concepts/spline.js +59 -0
- package/x/concepts/spline.js.map +1 -0
- package/x/concepts/vec2.d.ts +114 -0
- package/x/concepts/vec2.js +314 -0
- package/x/concepts/vec2.js.map +1 -0
- package/x/concepts/vec3.d.ts +117 -0
- package/x/concepts/vec3.js +357 -0
- package/x/concepts/vec3.js.map +1 -0
- package/x/concepts/vec4.d.ts +21 -0
- package/x/concepts/vec4.js +62 -0
- package/x/concepts/vec4.js.map +1 -0
- package/x/importmap.json +9 -0
- package/x/index.d.ts +10 -0
- package/x/index.js +11 -0
- package/x/index.js.map +1 -0
package/license
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
|
|
2
|
+
MIT License
|
|
3
|
+
|
|
4
|
+
Copyright (c) 2025 Chase Moskal
|
|
5
|
+
|
|
6
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
7
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
8
|
+
in the Software without restriction, including without limitation the rights
|
|
9
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
10
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
11
|
+
furnished to do so, subject to the following conditions:
|
|
12
|
+
|
|
13
|
+
The above copyright notice and this permission notice shall be included in all
|
|
14
|
+
copies or substantial portions of the Software.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
17
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
18
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
19
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
20
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
21
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
22
|
+
SOFTWARE.
|
|
23
|
+
|
package/package.json
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@benev/math",
|
|
3
|
+
"version": "0.0.0-0",
|
|
4
|
+
"description": "game math toolkit",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "Chase Moskal <chasemoskal@gmail.com>",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"main": "x/index.js",
|
|
9
|
+
"files": [
|
|
10
|
+
"x",
|
|
11
|
+
"s"
|
|
12
|
+
],
|
|
13
|
+
"scripts": {
|
|
14
|
+
"build": "turtle build --out=x -v",
|
|
15
|
+
"start": "http-server x",
|
|
16
|
+
"test": "exit 0"
|
|
17
|
+
},
|
|
18
|
+
"dependencies": {
|
|
19
|
+
"@e280/stz": "^0.0.0-3",
|
|
20
|
+
"simplex-noise": "^4.0.3"
|
|
21
|
+
},
|
|
22
|
+
"devDependencies": {
|
|
23
|
+
"@benev/turtle": "^0.6.8",
|
|
24
|
+
"http-server": "^14.1.1",
|
|
25
|
+
"npm-run-all": "^4.1.5",
|
|
26
|
+
"typescript": "^5.8.3"
|
|
27
|
+
},
|
|
28
|
+
"keywords": [
|
|
29
|
+
"game",
|
|
30
|
+
"math",
|
|
31
|
+
"vectors"
|
|
32
|
+
],
|
|
33
|
+
"repository": {
|
|
34
|
+
"type": "git",
|
|
35
|
+
"url": "git+https://github.com/benevolent-games/math.git"
|
|
36
|
+
},
|
|
37
|
+
"bugs": {
|
|
38
|
+
"url": "https://github.com/benevolent-games/math/issues"
|
|
39
|
+
},
|
|
40
|
+
"homepage": "https://github.com/benevolent-games/math#readme"
|
|
41
|
+
}
|
package/readme.md
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
|
|
2
|
+
import {Scalar} from "./scalar.js"
|
|
3
|
+
|
|
4
|
+
const pi = Math.PI
|
|
5
|
+
const circle = 2 * Math.PI
|
|
6
|
+
|
|
7
|
+
const to = {
|
|
8
|
+
degrees(r: number) {
|
|
9
|
+
return r * (180 / pi)
|
|
10
|
+
},
|
|
11
|
+
arcseconds(r: number) {
|
|
12
|
+
return to.degrees(r) * 3600
|
|
13
|
+
},
|
|
14
|
+
turns(r: number) {
|
|
15
|
+
return r / circle
|
|
16
|
+
},
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const from = {
|
|
20
|
+
turns(t: number) {
|
|
21
|
+
return t * circle
|
|
22
|
+
},
|
|
23
|
+
degrees(d: number) {
|
|
24
|
+
return d * (pi / 180)
|
|
25
|
+
},
|
|
26
|
+
arcseconds(a: number) {
|
|
27
|
+
return from.degrees(a / 3600)
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export const Radians = {
|
|
32
|
+
pi,
|
|
33
|
+
circle,
|
|
34
|
+
|
|
35
|
+
/** @deprecated use Radians.toDegrees instead */
|
|
36
|
+
to,
|
|
37
|
+
|
|
38
|
+
/** @deprecated use Degrees.toRadians instead */
|
|
39
|
+
from,
|
|
40
|
+
|
|
41
|
+
toDegrees(r: number) {
|
|
42
|
+
return r * (180 / pi)
|
|
43
|
+
},
|
|
44
|
+
toArcseconds(r: number) {
|
|
45
|
+
return to.degrees(r) * 3600
|
|
46
|
+
},
|
|
47
|
+
toTurns(r: number) {
|
|
48
|
+
return r / circle
|
|
49
|
+
},
|
|
50
|
+
|
|
51
|
+
circleDistance(radiansA: number, radiansB: number): number {
|
|
52
|
+
const diff = Math.abs(Scalar.wrap(radiansA - radiansB, 0, Radians.circle))
|
|
53
|
+
return Math.min(diff, Radians.circle - diff)
|
|
54
|
+
},
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export const Turns = {
|
|
58
|
+
toRadians(t: number) {
|
|
59
|
+
return t * circle
|
|
60
|
+
},
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export const Arcseconds = {
|
|
64
|
+
toRadians(a: number) {
|
|
65
|
+
return from.degrees(a / 3600)
|
|
66
|
+
},
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export const Degrees = {
|
|
70
|
+
toRadians(d: number) {
|
|
71
|
+
return d * (pi / 180)
|
|
72
|
+
},
|
|
73
|
+
}
|
|
74
|
+
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
|
|
2
|
+
import {Scalar} from "./scalar.js"
|
|
3
|
+
|
|
4
|
+
export class Circular {
|
|
5
|
+
constructor(public x: number) {}
|
|
6
|
+
|
|
7
|
+
clone() {
|
|
8
|
+
return new Circular(this.x)
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
set(x: number) {
|
|
12
|
+
this.x = x
|
|
13
|
+
return this
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
static value(x: number | Circular) {
|
|
17
|
+
return typeof x === "number"
|
|
18
|
+
? x
|
|
19
|
+
: x.x
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
static normalize(x: number) {
|
|
23
|
+
return Scalar.wrap(x, 0, 2 * Math.PI)
|
|
24
|
+
} normalize() {
|
|
25
|
+
this.x = Circular.normalize(this.x)
|
|
26
|
+
return this
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
static difference(x: number, y: number) {
|
|
30
|
+
x = this.normalize(x)
|
|
31
|
+
y = this.normalize(y)
|
|
32
|
+
let delta = y - x
|
|
33
|
+
if (delta > Math.PI) delta -= 2 * Math.PI
|
|
34
|
+
if (delta < -Math.PI) delta += 2 * Math.PI
|
|
35
|
+
return delta
|
|
36
|
+
} difference(y: number | Circular) {
|
|
37
|
+
return Circular.difference(this.x, Circular.value(y))
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
static lerp(x: number, y: number, fraction: number, max?: number) {
|
|
41
|
+
const difference = this.difference(x, y)
|
|
42
|
+
let delta = difference * fraction
|
|
43
|
+
if (max !== undefined && Math.abs(delta) > max)
|
|
44
|
+
delta = Math.sign(delta) * max
|
|
45
|
+
return this.normalize(x + delta)
|
|
46
|
+
} lerp(y: number | Circular, fraction: number, max?: number) {
|
|
47
|
+
this.x = Circular.lerp(this.x, Circular.value(y), fraction, max)
|
|
48
|
+
return this
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
static step(x: number, y: number, delta: number) {
|
|
52
|
+
const difference = this.difference(x, y)
|
|
53
|
+
return this.normalize(
|
|
54
|
+
Math.abs(difference) <= delta
|
|
55
|
+
? y
|
|
56
|
+
: x + (Math.sign(difference) * delta)
|
|
57
|
+
)
|
|
58
|
+
} step(y: number | Circular, delta: number) {
|
|
59
|
+
this.x = Circular.step(this.x, Circular.value(y), delta)
|
|
60
|
+
return this
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
static approach(x: number, y: number, speed: number, deltaTime: number, speedLimit?: number) {
|
|
64
|
+
const difference = this.difference(x, y)
|
|
65
|
+
const change = Scalar.creep(difference, speed, deltaTime, speedLimit)
|
|
66
|
+
return this.normalize(x + change)
|
|
67
|
+
} approach(y: number | Circular, speed: number, deltaTime: number, speedLimit?: number) {
|
|
68
|
+
this.x = Circular.approach(this.x, Circular.value(y), speed, deltaTime, speedLimit)
|
|
69
|
+
return this
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
|
|
2
|
+
import {Randy, Random} from "./randy.js"
|
|
3
|
+
import {createNoise2D} from "simplex-noise"
|
|
4
|
+
|
|
5
|
+
export class Noise {
|
|
6
|
+
static seed(seed: number = Randy.randomSeed()) {
|
|
7
|
+
const random = Randy.makeRandom(seed)
|
|
8
|
+
return new this(random)
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
#n2d: (x: number, y: number) => number
|
|
12
|
+
|
|
13
|
+
constructor(public readonly random: Random) {
|
|
14
|
+
this.#n2d = createNoise2D(random)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** sample the noise field, returning a number from 0 to 1. */
|
|
18
|
+
sample(x: number, y = 0, scale = 1) {
|
|
19
|
+
const s = this.#n2d(x * scale, y * scale)
|
|
20
|
+
return (s + 1) / 2
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
|
|
2
|
+
import {Xyz} from "./vec3.js"
|
|
3
|
+
|
|
4
|
+
export type QuatArray = [number, number, number, number]
|
|
5
|
+
export type Xyzw = {x: number, y: number, z: number, w: number}
|
|
6
|
+
|
|
7
|
+
export class Quat {
|
|
8
|
+
constructor(
|
|
9
|
+
public x: number,
|
|
10
|
+
public y: number,
|
|
11
|
+
public z: number,
|
|
12
|
+
public w: number,
|
|
13
|
+
) {}
|
|
14
|
+
|
|
15
|
+
static new(x: number, y: number, z: number, w: number) {
|
|
16
|
+
return new this(x, y, z, w)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
static identity() {
|
|
20
|
+
return new this(0, 0, 0, 1)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
static array(q: QuatArray) {
|
|
24
|
+
return new this(...q)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
static import({x, y, z, w}: Xyzw) {
|
|
28
|
+
return new this(x, y, z, w)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
static from(q: QuatArray | Xyzw) {
|
|
32
|
+
return Array.isArray(q)
|
|
33
|
+
? this.array(q)
|
|
34
|
+
: this.import(q)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
static rotate_(pitch: number, yaw: number, roll: number) {
|
|
38
|
+
return this.identity().rotate_(pitch, yaw, roll)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
static rotate(vec: Xyz) {
|
|
42
|
+
return this.identity().rotate(vec)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
array(): QuatArray {
|
|
46
|
+
const {x, y, z, w} = this
|
|
47
|
+
return [x, y, z, w]
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
toString() {
|
|
51
|
+
return `(Quat x${this.x.toFixed(2)}, y${this.y.toFixed(2)}, z${this.z.toFixed(2)}, w${this.w.toFixed(2)})`
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
clone() {
|
|
55
|
+
return new Quat(...this.array())
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
transform_(x: number, y: number, z: number, w: number, global = false) {
|
|
59
|
+
if (global) {
|
|
60
|
+
const original = this.array()
|
|
61
|
+
return this.set_(x, y, z, w).multiply_(...original)
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
return this.multiply_(x, y, z, w)
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
transform({x, y, z, w}: Xyzw, global = false) {
|
|
69
|
+
return this.transform_(x, y, z, w, global)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
rotate_(pitch: number, yaw: number, roll: number, global = false): Quat {
|
|
73
|
+
const cx = Math.cos(pitch * 0.5), sx = Math.sin(pitch * 0.5)
|
|
74
|
+
const cy = Math.cos(yaw * 0.5), sy = Math.sin(yaw * 0.5)
|
|
75
|
+
const cz = Math.cos(roll * 0.5), sz = Math.sin(roll * 0.5)
|
|
76
|
+
const x = sx * cy * cz + cx * sy * sz
|
|
77
|
+
const y = cx * sy * cz - sx * cy * sz
|
|
78
|
+
const z = cx * cy * sz + sx * sy * cz
|
|
79
|
+
const w = cx * cy * cz - sx * sy * sz
|
|
80
|
+
return this.transform_(x, y, z, w, global)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
rotate({x: pitch, y: yaw, z: roll}: Xyz, global = false) {
|
|
84
|
+
return this.rotate_(pitch, yaw, roll, global)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
rotateAroundAxis_(angle: number, axisX: number, axisY: number, axisZ: number, global = false): Quat {
|
|
88
|
+
const halfAngle = angle * 0.5
|
|
89
|
+
const sinHalf = Math.sin(halfAngle)
|
|
90
|
+
const x = axisX * sinHalf
|
|
91
|
+
const y = axisY * sinHalf
|
|
92
|
+
const z = axisZ * sinHalf
|
|
93
|
+
const w = Math.cos(halfAngle)
|
|
94
|
+
return this.transform_(x, y, z, w, global)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
rotateAroundAxis(angle: number, axis: Xyz, global = false): Quat {
|
|
98
|
+
return this.rotateAroundAxis_(angle, axis.x, axis.y, axis.z, global)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
set_(x: number, y: number, z: number, w: number) {
|
|
102
|
+
this.x = x
|
|
103
|
+
this.y = y
|
|
104
|
+
this.z = z
|
|
105
|
+
this.w = w
|
|
106
|
+
return this
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
set({x, y, z, w}: Xyzw) {
|
|
110
|
+
this.x = x
|
|
111
|
+
this.y = y
|
|
112
|
+
this.z = z
|
|
113
|
+
this.w = w
|
|
114
|
+
return this
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
multiply_(x2: number, y2: number, z2: number, w2: number): Quat {
|
|
118
|
+
const {x, y, z, w} = this
|
|
119
|
+
this.x = w * x2 + x * w2 + y * z2 - z * y2
|
|
120
|
+
this.y = w * y2 - x * z2 + y * w2 + z * x2
|
|
121
|
+
this.z = w * z2 + x * y2 - y * x2 + z * w2
|
|
122
|
+
this.w = w * w2 - x * x2 - y * y2 - z * z2
|
|
123
|
+
return this
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
multiply(...quats: Quat[]) {
|
|
127
|
+
for (const {x, y, z, w} of quats) this.multiply_(x, y, z, w)
|
|
128
|
+
return this
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
|
|
2
|
+
export type Random = () => number
|
|
3
|
+
|
|
4
|
+
/** utility for generating and using random numbers. */
|
|
5
|
+
export class Randy {
|
|
6
|
+
random: Random
|
|
7
|
+
|
|
8
|
+
constructor(public readonly seed: number = Randy.randomSeed()) {
|
|
9
|
+
this.random = Randy.makeRandom(seed)
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/** obtain a random positive 32 bit integer. */
|
|
13
|
+
static randomSeed() {
|
|
14
|
+
return Math.floor(Math.random() * 2147483647)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** seed a pseudo-random number generator function that produces numbers between 0 and 1. */
|
|
18
|
+
static makeRandom(seed: number): Random {
|
|
19
|
+
seed = (seed ^ 0x6D2B79F5) + 0x1E35A7BD
|
|
20
|
+
seed = (Math.abs(seed | 0) % 2147483647) || 1
|
|
21
|
+
|
|
22
|
+
function random() {
|
|
23
|
+
seed = (Math.imul(48271, seed) | 0) % 2147483647
|
|
24
|
+
return (seed & 2147483647) / 2147483648
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
random() // discard first value
|
|
28
|
+
return random
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** obtain a random positive integer. */
|
|
32
|
+
integer() {
|
|
33
|
+
return Math.floor(this.random() * 2147483647)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** return true or false, given a 0 to 1 probability fraction. */
|
|
37
|
+
roll(chance = 0.5) {
|
|
38
|
+
return this.random() <= chance
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** generate a random number between two numbers. */
|
|
42
|
+
range(a: number, b: number) {
|
|
43
|
+
const difference = b - a
|
|
44
|
+
const value = difference * this.random()
|
|
45
|
+
return a + value
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** generate a random integer between two numbers (inclusive). */
|
|
49
|
+
integerRange(a: number, b: number) {
|
|
50
|
+
return Math.round(this.range(a, b))
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** randomly choose an index given an array length. */
|
|
54
|
+
index(length: number) {
|
|
55
|
+
return Math.floor(this.random() * length)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** return a random item from the given array. */
|
|
59
|
+
choose<T>(array: T[]) {
|
|
60
|
+
return array[this.index(array.length)]
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** remove and return a random item from the given array. */
|
|
64
|
+
yoink<T>(array: T[]) {
|
|
65
|
+
const index = this.index(array.length)
|
|
66
|
+
const [item] = array.splice(index, 1)
|
|
67
|
+
return item
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** randomly select a number of array items. */
|
|
71
|
+
select<T>(count: number, array: T[]) {
|
|
72
|
+
const copy = [...array]
|
|
73
|
+
if (count >= array.length)
|
|
74
|
+
return copy
|
|
75
|
+
|
|
76
|
+
const selection: T[] = []
|
|
77
|
+
for (let i = 0; i < count; i++)
|
|
78
|
+
selection.push(this.yoink(copy))
|
|
79
|
+
return selection
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** remove and return a number of items from the given array. */
|
|
83
|
+
take<T>(count: number, array: T[]) {
|
|
84
|
+
const selection: T[] = []
|
|
85
|
+
for (let i = 0; i < count; i++) {
|
|
86
|
+
if (array.length === 0)
|
|
87
|
+
return selection
|
|
88
|
+
selection.push(this.yoink(array))
|
|
89
|
+
}
|
|
90
|
+
return selection
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** shuffle an array in-place using (fisher-yates) */
|
|
94
|
+
shuffle<T>(array: T[]) {
|
|
95
|
+
for (let i = array.length - 1; i > 0; i--) {
|
|
96
|
+
const j = Math.floor(this.random() * (i + 1))
|
|
97
|
+
;[array[i], array[j]] = [array[j], array[i]]
|
|
98
|
+
}
|
|
99
|
+
return array
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/** @deprecated create an instance with the given seed number. */
|
|
103
|
+
static seed(seed: number): Randy {
|
|
104
|
+
return new this(seed)
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/** @deprecated renamed to `take` */
|
|
108
|
+
extract<T>(count: number, array: T[]) { return this.take(count, array) }
|
|
109
|
+
|
|
110
|
+
/** @deprecated renamed to `range` */
|
|
111
|
+
between(a: number, b: number) { return this.range(a, b) }
|
|
112
|
+
|
|
113
|
+
/** @deprecated renamed to `integerRange` */
|
|
114
|
+
integerBetween(a: number, b: number) { return this.integerRange(a, b) }
|
|
115
|
+
}
|
|
116
|
+
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
|
|
2
|
+
export class Scalar {
|
|
3
|
+
constructor(public x: number) {}
|
|
4
|
+
|
|
5
|
+
static new(x: number) {
|
|
6
|
+
return new this(x)
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
clone() {
|
|
10
|
+
return new Scalar(this.x)
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
set(x: number) {
|
|
14
|
+
this.x = x
|
|
15
|
+
return this
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
//
|
|
19
|
+
// queries
|
|
20
|
+
//
|
|
21
|
+
|
|
22
|
+
static isBetween(x: number, a: number = 0, b: number = 1) {
|
|
23
|
+
const min = Math.min(a, b)
|
|
24
|
+
const max = Math.max(a, b)
|
|
25
|
+
return (x >= min) && (x <= max)
|
|
26
|
+
} isBetween(a: number = 0, b: number = 1) {
|
|
27
|
+
return Scalar.isBetween(this.x, a, b)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
static isNear(x: number, y: number, epsilon: number = 0.01) {
|
|
31
|
+
return Math.abs(x - y) <= epsilon
|
|
32
|
+
} isNear(y: number, epsilon: number = 0.01) {
|
|
33
|
+
return Scalar.isNear(this.x, y, epsilon)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
//
|
|
37
|
+
// chainable transforms
|
|
38
|
+
//
|
|
39
|
+
|
|
40
|
+
static add(...nums: number[]) {
|
|
41
|
+
let x = 0
|
|
42
|
+
for (const n of nums)
|
|
43
|
+
x += n
|
|
44
|
+
return x
|
|
45
|
+
} add(...nums: number[]) {
|
|
46
|
+
this.x = Scalar.add(...nums)
|
|
47
|
+
return this
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
subtract(...nums: number[]) {
|
|
51
|
+
for (const n of nums)
|
|
52
|
+
this.x -= n
|
|
53
|
+
return this
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
static min(x: number, minimum: number = 0) {
|
|
57
|
+
return Math.max(x, minimum)
|
|
58
|
+
} min(minimum: number = 0) {
|
|
59
|
+
this.x = Scalar.min(this.x, minimum)
|
|
60
|
+
return this
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
static max(x: number, maximum: number = 1) {
|
|
64
|
+
return Math.min(x, maximum)
|
|
65
|
+
} max(maximum: number = 1) {
|
|
66
|
+
this.x = Scalar.max(this.x, maximum)
|
|
67
|
+
return this
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
static clamp(x: number, a: number = 0, b: number = 1) {
|
|
71
|
+
x = Scalar.min(x, Math.min(a, b))
|
|
72
|
+
x = Scalar.max(x, Math.max(a, b))
|
|
73
|
+
return x
|
|
74
|
+
} clamp(a: number = 0, b: number = 1) {
|
|
75
|
+
this.x = Scalar.clamp(this.x, a, b)
|
|
76
|
+
return this
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
static lerp(x: number, y: number, fraction: number, max?: number) {
|
|
80
|
+
const difference = y - x
|
|
81
|
+
let delta = difference * fraction
|
|
82
|
+
if (max !== undefined && Math.abs(delta) > max)
|
|
83
|
+
delta = Math.sign(delta) * max
|
|
84
|
+
return x + delta
|
|
85
|
+
} lerp(y: number, fraction: number, max?: number) {
|
|
86
|
+
this.x = Scalar.lerp(this.x, y, fraction, max)
|
|
87
|
+
return this
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
static step(x: number, y: number, delta: number) {
|
|
91
|
+
const difference = y - x
|
|
92
|
+
return (Math.abs(difference) <= delta)
|
|
93
|
+
? y
|
|
94
|
+
: x + (Math.sign(difference) * delta)
|
|
95
|
+
} step(y: number, delta: number) {
|
|
96
|
+
this.x = Scalar.step(this.x, y, delta)
|
|
97
|
+
return this
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
static creep(difference: number, speed: number, deltaTime: number, speedLimit?: number) {
|
|
101
|
+
let change = (difference * (1 - Math.exp(-speed * deltaTime)))
|
|
102
|
+
if (speedLimit !== undefined) {
|
|
103
|
+
const changeLimit = speedLimit * deltaTime
|
|
104
|
+
if (Math.abs(change) > changeLimit)
|
|
105
|
+
change = Math.sign(change) * changeLimit
|
|
106
|
+
}
|
|
107
|
+
return change
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
static approach(x: number, y: number, speed: number, deltaTime: number, speedLimit?: number) {
|
|
111
|
+
const difference = y - x
|
|
112
|
+
const change = this.creep(difference, speed, deltaTime, speedLimit)
|
|
113
|
+
return x + change
|
|
114
|
+
} approach(y: number, speed: number, deltaTime: number, speedLimit?: number) {
|
|
115
|
+
this.x = Scalar.approach(this.x, y, speed, deltaTime, speedLimit)
|
|
116
|
+
return this
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
static wrap(x: number, a: number = 0, b: number = 1) {
|
|
120
|
+
const min = Math.min(a, b)
|
|
121
|
+
const max = Math.max(a, b)
|
|
122
|
+
const span = max - min
|
|
123
|
+
const adjusted = x - min
|
|
124
|
+
const wrapped = (adjusted < 0)
|
|
125
|
+
? span - (-adjusted % span)
|
|
126
|
+
: adjusted % span
|
|
127
|
+
return min + wrapped
|
|
128
|
+
} wrap(a: number = 0, b: number = 1) {
|
|
129
|
+
this.x = Scalar.wrap(this.x, a, b)
|
|
130
|
+
return this
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
static constrainProximity(x: number, y: number, range: number) {
|
|
134
|
+
const trueDiff = y - x
|
|
135
|
+
const positiveDiff = Math.abs(trueDiff)
|
|
136
|
+
const cappedDiff = (positiveDiff > range)
|
|
137
|
+
? range
|
|
138
|
+
: positiveDiff
|
|
139
|
+
const newDiff = (trueDiff < 0) ? -cappedDiff : cappedDiff
|
|
140
|
+
return x + newDiff
|
|
141
|
+
} constrainProximity(y: number, range: number) {
|
|
142
|
+
this.x = Scalar.constrainProximity(this.x, y, range)
|
|
143
|
+
return this
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
static inverse(x: number) {
|
|
147
|
+
return 1 - x
|
|
148
|
+
} inverse() {
|
|
149
|
+
this.x = Scalar.inverse(this.x)
|
|
150
|
+
return this
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
static center(x: number) {
|
|
154
|
+
return (x * 2) - 1
|
|
155
|
+
} center() {
|
|
156
|
+
this.x = Scalar.center(this.x)
|
|
157
|
+
return this
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
static uncenter(x: number) {
|
|
161
|
+
return (x + 1) / 2
|
|
162
|
+
} uncenter() {
|
|
163
|
+
this.x = Scalar.uncenter(this.x)
|
|
164
|
+
return this
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
static map(x: number, a: number, b: number) {
|
|
168
|
+
const difference = b - a
|
|
169
|
+
const value = difference * x
|
|
170
|
+
return a + value
|
|
171
|
+
} map(a: number, b: number) {
|
|
172
|
+
this.x = Scalar.map(this.x, a, b)
|
|
173
|
+
return this
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
static remap(x: number, a1: number, a2: number, b1: number = 0, b2: number = 1, clamp = false) {
|
|
177
|
+
const fraction = (x - a1) / (a2 - a1)
|
|
178
|
+
const result = (fraction * (b2 - b1)) + b1
|
|
179
|
+
return clamp
|
|
180
|
+
? Scalar.clamp(result, b1, b2)
|
|
181
|
+
: result
|
|
182
|
+
} remap(a1: number, a2: number, b1: number = 0, b2: number = 1, clamp = false) {
|
|
183
|
+
this.x = Scalar.remap(this.x, a1, a2, b1, b2, clamp)
|
|
184
|
+
return this
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
static magnify(x: number) {
|
|
188
|
+
return 4 * Math.pow(x - 0.5, 3) + 0.5
|
|
189
|
+
} magnify() {
|
|
190
|
+
this.x = Scalar.magnify(this.x)
|
|
191
|
+
return this
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
static floor(x: number) {
|
|
195
|
+
return Math.floor(x)
|
|
196
|
+
} floor() {
|
|
197
|
+
this.x = Scalar.floor(this.x)
|
|
198
|
+
return this
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
static ceil(x: number) {
|
|
202
|
+
return Math.ceil(x)
|
|
203
|
+
} ceil() {
|
|
204
|
+
this.x = Scalar.ceil(this.x)
|
|
205
|
+
return this
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
static round(x: number) {
|
|
209
|
+
return Math.round(x)
|
|
210
|
+
} round() {
|
|
211
|
+
this.x = Scalar.round(this.x)
|
|
212
|
+
return this
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
static smooth(x: number, y: number, smoothing: number) {
|
|
216
|
+
return smoothing <= 1
|
|
217
|
+
? y
|
|
218
|
+
: x + ((y - x) / smoothing)
|
|
219
|
+
} smooth(y: number, smoothing: number) {
|
|
220
|
+
this.x = Scalar.smooth(this.x, y, smoothing)
|
|
221
|
+
return this
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|