@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.
Files changed (48) hide show
  1. package/license +23 -0
  2. package/package.json +41 -0
  3. package/readme.md +3 -0
  4. package/s/concepts/angles.ts +74 -0
  5. package/s/concepts/circular.ts +72 -0
  6. package/s/concepts/noise.ts +23 -0
  7. package/s/concepts/quat.ts +131 -0
  8. package/s/concepts/randy.ts +116 -0
  9. package/s/concepts/scalar.ts +224 -0
  10. package/s/concepts/spline.ts +88 -0
  11. package/s/concepts/vec2.ts +392 -0
  12. package/s/concepts/vec3.ts +439 -0
  13. package/s/concepts/vec4.ts +74 -0
  14. package/s/index.ts +12 -0
  15. package/x/concepts/angles.d.ts +29 -0
  16. package/x/concepts/angles.js +62 -0
  17. package/x/concepts/angles.js.map +1 -0
  18. package/x/concepts/circular.d.ts +17 -0
  19. package/x/concepts/circular.js +70 -0
  20. package/x/concepts/circular.js.map +1 -0
  21. package/x/concepts/noise.d.ts +9 -0
  22. package/x/concepts/noise.js +20 -0
  23. package/x/concepts/noise.js.map +1 -0
  24. package/x/concepts/quat.d.ts +35 -0
  25. package/x/concepts/quat.js +110 -0
  26. package/x/concepts/quat.js.map +1 -0
  27. package/x/concepts/randy.d.ts +39 -0
  28. package/x/concepts/randy.js +95 -0
  29. package/x/concepts/randy.js.map +1 -0
  30. package/x/concepts/scalar.d.ts +51 -0
  31. package/x/concepts/scalar.js +219 -0
  32. package/x/concepts/scalar.js.map +1 -0
  33. package/x/concepts/spline.d.ts +9 -0
  34. package/x/concepts/spline.js +59 -0
  35. package/x/concepts/spline.js.map +1 -0
  36. package/x/concepts/vec2.d.ts +114 -0
  37. package/x/concepts/vec2.js +314 -0
  38. package/x/concepts/vec2.js.map +1 -0
  39. package/x/concepts/vec3.d.ts +117 -0
  40. package/x/concepts/vec3.js +357 -0
  41. package/x/concepts/vec3.js.map +1 -0
  42. package/x/concepts/vec4.d.ts +21 -0
  43. package/x/concepts/vec4.js +62 -0
  44. package/x/concepts/vec4.js.map +1 -0
  45. package/x/importmap.json +9 -0
  46. package/x/index.d.ts +10 -0
  47. package/x/index.js +11 -0
  48. package/x/index.js.map +1 -0
@@ -0,0 +1,88 @@
1
+
2
+ import {Scalar} from "./scalar.js"
3
+ import {Vec2Array} from "./vec2.js"
4
+
5
+ /** resolve a number within a linear spline. */
6
+ export function linear(x: number, points: Vec2Array[]): number {
7
+ if (points.length < 2)
8
+ throw new Error("need at least two points, come on")
9
+
10
+ const [first] = points.at(0)!
11
+ const [last] = points.at(-1)!
12
+
13
+ x = Scalar.clamp(x, first, last)
14
+
15
+ for (let i = 0; i < points.length - 1; i++) {
16
+ const [x0, y0] = points[i]
17
+ const [x1, y1] = points[i + 1]
18
+
19
+ if (x >= x0 && x <= x1) {
20
+ const t = (x - x0) / (x1 - x0)
21
+ return y0 + t * (y1 - y0)
22
+ }
23
+ }
24
+
25
+ throw new Error("x is out of bounds, what are you even doing")
26
+ }
27
+
28
+ /** resolve a number within a catmull-rom spline, that's all smooth-like. */
29
+ export function catmullRom(x: number, points: Vec2Array[]) {
30
+ if (points.length < 4)
31
+ throw new Error("need at least four points for this magic")
32
+
33
+ x = Scalar.clamp(x)
34
+
35
+ // find the segment where 'x' fits
36
+ for (let i = 1; i < points.length - 2; i++) {
37
+ const [x1, ] = points[i]
38
+ const [x2, ] = points[i + 1]
39
+
40
+ if (x >= x1 && x <= x2) {
41
+ const t = (x - x1) / (x2 - x1)
42
+ return helpers.catmullRom(t, points[i - 1], points[i], points[i + 1], points[i + 2])
43
+ }
44
+ }
45
+
46
+ throw new Error("x is out of bounds, try again")
47
+ }
48
+
49
+ export const ez = {
50
+
51
+ /** simple linear spline where the control points are equally-spaced based on their array indices (x is expected to be between 0 and 1). */
52
+ linear(x: number, points: number[]) {
53
+ if (points.length < 2)
54
+ throw new Error("need at least two points, come on")
55
+
56
+ const points2 = points.map(
57
+ (p, index): Vec2Array =>
58
+ [Scalar.clamp(index / (points.length - 1)), p]
59
+ )
60
+
61
+ return linear(Scalar.clamp(x), points2)
62
+ }
63
+ }
64
+
65
+ namespace helpers {
66
+
67
+ /** internal big-brain maths for the catmull-rom implementation */
68
+ export function catmullRom(
69
+ t: number,
70
+ [,p0]: Vec2Array,
71
+ [,p1]: Vec2Array,
72
+ [,p2]: Vec2Array,
73
+ [,p3]: Vec2Array,
74
+ ) {
75
+
76
+ const t2 = t * t
77
+ const t3 = t2 * t
78
+
79
+ // coefficients for the cubic polynomial (Catmull-Rom)
80
+ const a = -0.5 * p0 + 1.5 * p1 - 1.5 * p2 + 0.5 * p3
81
+ const b = p0 - 2.5 * p1 + 2 * p2 - 0.5 * p3
82
+ const c = -0.5 * p0 + 0.5 * p2
83
+ const d = p1
84
+
85
+ return a * t3 + b * t2 + c * t + d
86
+ }
87
+ }
88
+
@@ -0,0 +1,392 @@
1
+
2
+ import {Scalar} from "./scalar.js"
3
+
4
+ export type Vec2Array = [number, number]
5
+ export type Xy = {x: number, y: number}
6
+
7
+ /** https://github.com/microsoft/TypeScript/issues/5863 */
8
+ type TsHack<T> = {new(...a: ConstructorParameters<typeof Vec2>): T}
9
+
10
+ export class Vec2 implements Xy {
11
+ constructor(
12
+ public x: number,
13
+ public y: number,
14
+ ) {}
15
+
16
+ ///////////////////////////////////////////////////////////////////////
17
+
18
+ static new<T extends Vec2>(this: TsHack<T>, x: number, y: number): T {
19
+ return new this(x, y)
20
+ }
21
+
22
+ static zero<T extends Vec2>(this: TsHack<T>) {
23
+ return new this(0, 0)
24
+ }
25
+
26
+ static all<T extends Vec2>(this: TsHack<T>, value: number) {
27
+ return new this(value, value)
28
+ }
29
+
30
+ static array<T extends Vec2>(this: TsHack<T>, v: Vec2Array) {
31
+ return new this(...v)
32
+ }
33
+
34
+ static import<T extends Vec2>(this: TsHack<T>, {x, y}: Xy) {
35
+ return new this(x, y)
36
+ }
37
+
38
+ static from(v: Vec2Array | Xy) {
39
+ return Array.isArray(v)
40
+ ? this.array(v)
41
+ : this.import(v)
42
+ }
43
+
44
+ static magnitudeSquared(x: number, y: number) {
45
+ return (x * x) + (y * y)
46
+ }
47
+
48
+ static magnitude(x: number, y: number) {
49
+ return Math.sqrt(this.magnitudeSquared(x, y))
50
+ }
51
+
52
+ static average(...vectors: Xy[]) {
53
+ return this.zero()
54
+ .add(...vectors)
55
+ .divideBy(vectors.length)
56
+ }
57
+
58
+ static min(...vecs: Vec2[]) {
59
+ return new Vec2(
60
+ Math.min(...vecs.map(v => v.x)),
61
+ Math.min(...vecs.map(v => v.y)),
62
+ )
63
+ }
64
+
65
+ static max(...vecs: Vec2[]) {
66
+ return new Vec2(
67
+ Math.max(...vecs.map(v => v.x)),
68
+ Math.max(...vecs.map(v => v.y)),
69
+ )
70
+ }
71
+
72
+ ///////////////////////////////////////////////////////////////////////
73
+
74
+ clone() {
75
+ return new Vec2(this.x, this.y)
76
+ }
77
+
78
+ array(): Vec2Array {
79
+ return [this.x, this.y]
80
+ }
81
+
82
+ toString() {
83
+ return `(Vec2 x${this.x.toFixed(2)}, y${this.y.toFixed(2)})`
84
+ }
85
+
86
+ /** mutator */
87
+ set_(x: number, y: number) {
88
+ this.x = x
89
+ this.y = y
90
+ return this
91
+ }
92
+
93
+ /** mutator */
94
+ set({x, y}: Xy) {
95
+ this.x = x
96
+ this.y = y
97
+ return this
98
+ }
99
+
100
+ ///////////////////////////////////////////////////////////////////////
101
+
102
+ magnitudeSquared() {
103
+ return Vec2.magnitudeSquared(this.x, this.y)
104
+ }
105
+
106
+ magnitude() {
107
+ return Vec2.magnitude(this.x, this.y)
108
+ }
109
+
110
+ rotation() {
111
+ return Math.atan2(this.y, this.x)
112
+ }
113
+
114
+ equals_(x: number, y: number) {
115
+ return (
116
+ this.x === x &&
117
+ this.y === y
118
+ )
119
+ }
120
+
121
+ equals(...vecs: Xy[]) {
122
+ return vecs.every(({x, y}) => this.equals_(x, y))
123
+ }
124
+
125
+ dot_(x: number, y: number) {
126
+ return (this.x * x) + (this.y * y)
127
+ }
128
+
129
+ dot({x, y}: Xy) {
130
+ return this.dot_(x, y)
131
+ }
132
+
133
+ distanceSquared_(x: number, y: number) {
134
+ x = this.x - x
135
+ y = this.y - y
136
+ return (x * x) + (y * y)
137
+ }
138
+
139
+ distanceSquared({x, y}: Xy) {
140
+ return this.distanceSquared_(x, y)
141
+ }
142
+
143
+ distance_(x: number, y: number) {
144
+ return Math.sqrt(this.distanceSquared_(x, y))
145
+ }
146
+
147
+ distance({x, y}: Xy) {
148
+ return this.distance_(x, y)
149
+ }
150
+
151
+ angleBetween_(x: number, y: number) {
152
+ const dot = this.dot_(x, y)
153
+ const magnitudes = this.magnitude() * Vec2.magnitude(x, y)
154
+ return Math.acos(dot / magnitudes)
155
+ }
156
+
157
+ angleBetween({x, y}: Xy) {
158
+ return this.angleBetween_(x, y)
159
+ }
160
+
161
+ ///////////////////////////////////////////////////////////////////////
162
+
163
+ /** mutator */
164
+ normalize() {
165
+ return this.divideBy(this.magnitude())
166
+ }
167
+
168
+ /** mutator */
169
+ half() {
170
+ return this.divideBy(2)
171
+ }
172
+
173
+ /** mutator */
174
+ double() {
175
+ return this.multiplyBy(2)
176
+ }
177
+
178
+ /** mutator */
179
+ abs() {
180
+ return this.map(x => Math.abs(x))
181
+ }
182
+
183
+ /** mutator */
184
+ rotate(radians: number) {
185
+ const {x, y} = this
186
+ this.x = (x * Math.cos(radians)) - (y * Math.sin(radians))
187
+ this.y = (x * Math.sin(radians)) + (y * Math.cos(radians))
188
+ return this
189
+ }
190
+
191
+ /** mutator */
192
+ perpendicular() {
193
+ const {x, y} = this
194
+ this.x = -y
195
+ this.y = x
196
+ return this
197
+ }
198
+
199
+ /** mutator */
200
+ clampMagnitude(max: number) {
201
+ const mag = this.magnitude()
202
+ if (mag > max)
203
+ this.normalize().multiplyBy(max)
204
+ return this
205
+ }
206
+
207
+ /** mutator */
208
+ floor() {
209
+ this.x = Math.floor(this.x)
210
+ this.y = Math.floor(this.y)
211
+ return this
212
+ }
213
+
214
+ /** mutator */
215
+ ceil() {
216
+ this.x = Math.ceil(this.x)
217
+ this.y = Math.ceil(this.y)
218
+ return this
219
+ }
220
+
221
+ /** mutator */
222
+ round() {
223
+ this.x = Math.round(this.x)
224
+ this.y = Math.round(this.y)
225
+ return this
226
+ }
227
+
228
+ /** mutator */
229
+ map(fn: (a: number, index: number) => number) {
230
+ this.x = fn(this.x, 0)
231
+ this.y = fn(this.y, 1)
232
+ return this
233
+ }
234
+
235
+ /** mutator */
236
+ clamp(min: number, max: number) {
237
+ const clamp = (val: number) => Math.max(min, Math.min(max, val))
238
+ return this.map(clamp)
239
+ }
240
+
241
+ /** mutator */
242
+ negate() {
243
+ return this.map(a => a * -1)
244
+ }
245
+
246
+ /** mutator */
247
+ addBy(delta: number) {
248
+ this.x += delta
249
+ this.y += delta
250
+ return this
251
+ }
252
+
253
+ /** mutator */
254
+ subtractBy(delta: number) {
255
+ this.x -= delta
256
+ this.y -= delta
257
+ return this
258
+ }
259
+
260
+ /** mutator */
261
+ multiplyBy(coefficient: number) {
262
+ this.x *= coefficient
263
+ this.y *= coefficient
264
+ return this
265
+ }
266
+
267
+ /** mutator */
268
+ divideBy(divisor: number) {
269
+ if (divisor === 0) return this
270
+ this.x /= divisor
271
+ this.y /= divisor
272
+ return this
273
+ }
274
+
275
+ ///////////////////////////////////////////////////////////////////////
276
+
277
+ /** mutator */
278
+ add_(x: number, y: number) {
279
+ this.x += x
280
+ this.y += y
281
+ return this
282
+ }
283
+
284
+ /** mutator */
285
+ add(...vecs: Xy[]) {
286
+ for (const {x, y} of vecs) this.add_(x, y)
287
+ return this
288
+ }
289
+
290
+ /** mutator */
291
+ subtract_(x: number, y: number) {
292
+ this.x -= x
293
+ this.y -= y
294
+ return this
295
+ }
296
+
297
+ /** mutator */
298
+ subtract(...vecs: Xy[]) {
299
+ for (const {x, y} of vecs) this.subtract_(x, y)
300
+ return this
301
+ }
302
+
303
+ /** mutator */
304
+ multiply_(x: number, y: number) {
305
+ this.x *= x
306
+ this.y *= y
307
+ return this
308
+ }
309
+
310
+ /** mutator */
311
+ multiply(...vecs: Xy[]) {
312
+ for (const {x, y} of vecs) this.multiply_(x, y)
313
+ return this
314
+ }
315
+
316
+ /** mutator */
317
+ divide_(x: number, y: number) {
318
+ this.x /= x
319
+ this.y /= y
320
+ return this
321
+ }
322
+
323
+ /** mutator */
324
+ divide(...vecs: Xy[]) {
325
+ for (const {x, y} of vecs) this.divide_(x, y)
326
+ return this
327
+ }
328
+
329
+ /** mutator */
330
+ lerp_(x: number, y: number, fraction: number) {
331
+ this.x += (x - this.x) * fraction
332
+ this.y += (y - this.y) * fraction
333
+ return this
334
+ }
335
+
336
+ /** mutator */
337
+ lerp({x, y}: Xy, fraction: number) {
338
+ return this.lerp_(x, y, fraction)
339
+ }
340
+
341
+ approach_(x: number, y: number, speed: number, deltaTime: number, speedLimit?: number) {
342
+ this.x = Scalar.approach(this.x, x, speed, deltaTime, speedLimit)
343
+ this.y = Scalar.approach(this.y, y, speed, deltaTime, speedLimit)
344
+ return this
345
+ }
346
+
347
+ approach({x, y}: Xy, speed: number, deltaTime: number, speedLimit?: number) {
348
+ return this.approach_(x, y, speed, deltaTime, speedLimit)
349
+ }
350
+
351
+ /** mutator */
352
+ reflect_(x: number, y: number) {
353
+ const dot = 2 * this.dot_(x, y)
354
+ this.x -= dot * x
355
+ this.y -= dot * y
356
+ return this
357
+ }
358
+
359
+ /** mutator */
360
+ reflect({x, y}: Xy) {
361
+ return this.reflect_(x, y)
362
+ }
363
+
364
+ /** mutator */
365
+ rotateAroundPoint_(x: number, y: number, radians: number) {
366
+ const dx = this.x - x
367
+ const dy = this.y - y
368
+ const cos = Math.cos(radians)
369
+ const sin = Math.sin(radians)
370
+ this.x = cos * dx - sin * dy + x
371
+ this.y = sin * dx + cos * dy + y
372
+ return this
373
+ }
374
+
375
+ /** mutator */
376
+ rotateAroundPoint({x, y}: Vec2, radians: number) {
377
+ return this.rotateAroundPoint_(x, y, radians)
378
+ }
379
+
380
+ /** mutator */
381
+ smooth_(x: number, y: number, smoothing: number) {
382
+ this.x = Scalar.smooth(this.x, x, smoothing)
383
+ this.y = Scalar.smooth(this.y, y, smoothing)
384
+ return this
385
+ }
386
+
387
+ /** mutator */
388
+ smooth({x, y}: Xy, smoothing: number) {
389
+ return this.smooth_(x, y, smoothing)
390
+ }
391
+ }
392
+