@benev/math 0.1.0 → 0.2.0-1

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 (118) hide show
  1. package/README.md +110 -23
  2. package/package.json +3 -1
  3. package/s/{concepts → core}/quat.ts +56 -16
  4. package/s/{concepts → core}/vec2.ts +52 -38
  5. package/s/{concepts → core}/vec3.ts +57 -47
  6. package/s/{concepts → core}/vec4.ts +13 -16
  7. package/s/index.ts +25 -10
  8. package/s/optimizers/hash-map.ts +71 -0
  9. package/s/optimizers/hash-set.ts +50 -0
  10. package/s/optimizers/zen.ts +151 -0
  11. package/s/physics/2d/collide2d.ts +51 -0
  12. package/s/physics/2d/intersect2d.ts +80 -0
  13. package/s/shapes/2d/circle.ts +43 -0
  14. package/s/shapes/2d/edge.ts +3 -0
  15. package/s/shapes/2d/index.ts +6 -0
  16. package/s/shapes/2d/pill.ts +3 -0
  17. package/s/shapes/2d/rect.ts +74 -0
  18. package/s/shapes/3d/box.ts +84 -0
  19. package/s/shapes/3d/capsule.ts +3 -0
  20. package/s/shapes/3d/index.ts +6 -0
  21. package/s/shapes/3d/segment.ts +48 -0
  22. package/s/shapes/3d/sphere.ts +3 -0
  23. package/s/tools/angles.ts +49 -0
  24. package/s/{concepts → tools}/circular.ts +5 -3
  25. package/s/{concepts → tools}/randy.ts +0 -14
  26. package/s/{concepts → tools}/scalar.ts +11 -11
  27. package/s/{concepts → tools}/spline.ts +11 -10
  28. package/x/{concepts → core}/quat.d.ts +10 -5
  29. package/x/{concepts → core}/quat.js +49 -12
  30. package/x/core/quat.js.map +1 -0
  31. package/x/{concepts → core}/vec2.d.ts +16 -17
  32. package/x/{concepts → core}/vec2.js +37 -21
  33. package/x/core/vec2.js.map +1 -0
  34. package/x/{concepts → core}/vec3.d.ts +19 -20
  35. package/x/{concepts → core}/vec3.js +46 -27
  36. package/x/core/vec3.js.map +1 -0
  37. package/x/{concepts → core}/vec4.d.ts +5 -7
  38. package/x/{concepts → core}/vec4.js +12 -12
  39. package/x/core/vec4.js.map +1 -0
  40. package/x/index.d.ts +21 -10
  41. package/x/index.js +21 -10
  42. package/x/index.js.map +1 -1
  43. package/x/optimizers/hash-map.d.ts +17 -0
  44. package/x/optimizers/hash-map.js +55 -0
  45. package/x/optimizers/hash-map.js.map +1 -0
  46. package/x/optimizers/hash-set.d.ts +13 -0
  47. package/x/optimizers/hash-set.js +39 -0
  48. package/x/optimizers/hash-set.js.map +1 -0
  49. package/x/optimizers/zen.d.ts +33 -0
  50. package/x/optimizers/zen.js +121 -0
  51. package/x/optimizers/zen.js.map +1 -0
  52. package/x/physics/2d/collide2d.d.ts +8 -0
  53. package/x/physics/2d/collide2d.js +36 -0
  54. package/x/physics/2d/collide2d.js.map +1 -0
  55. package/x/physics/2d/intersect2d.d.ts +13 -0
  56. package/x/physics/2d/intersect2d.js +57 -0
  57. package/x/physics/2d/intersect2d.js.map +1 -0
  58. package/x/shapes/2d/circle.d.ts +18 -0
  59. package/x/shapes/2d/circle.js +34 -0
  60. package/x/shapes/2d/circle.js.map +1 -0
  61. package/x/shapes/2d/edge.d.ts +1 -0
  62. package/x/shapes/2d/edge.js +3 -0
  63. package/x/shapes/2d/edge.js.map +1 -0
  64. package/x/shapes/2d/index.d.ts +4 -0
  65. package/x/shapes/2d/index.js +5 -0
  66. package/x/shapes/2d/index.js.map +1 -0
  67. package/x/shapes/2d/pill.d.ts +1 -0
  68. package/x/shapes/2d/pill.js +3 -0
  69. package/x/shapes/2d/pill.js.map +1 -0
  70. package/x/shapes/2d/rect.d.ts +23 -0
  71. package/x/shapes/2d/rect.js +59 -0
  72. package/x/shapes/2d/rect.js.map +1 -0
  73. package/x/shapes/3d/box.d.ts +24 -0
  74. package/x/shapes/3d/box.js +68 -0
  75. package/x/shapes/3d/box.js.map +1 -0
  76. package/x/shapes/3d/capsule.d.ts +1 -0
  77. package/x/shapes/3d/capsule.js +3 -0
  78. package/x/shapes/3d/capsule.js.map +1 -0
  79. package/x/shapes/3d/index.d.ts +4 -0
  80. package/x/shapes/3d/index.js +5 -0
  81. package/x/shapes/3d/index.js.map +1 -0
  82. package/x/shapes/3d/segment.d.ts +13 -0
  83. package/x/shapes/3d/segment.js +37 -0
  84. package/x/shapes/3d/segment.js.map +1 -0
  85. package/x/shapes/3d/sphere.d.ts +1 -0
  86. package/x/shapes/3d/sphere.js +3 -0
  87. package/x/shapes/3d/sphere.js.map +1 -0
  88. package/x/{concepts → tools}/angles.d.ts +3 -13
  89. package/x/tools/angles.js +41 -0
  90. package/x/tools/angles.js.map +1 -0
  91. package/x/{concepts → tools}/circular.js +4 -3
  92. package/x/tools/circular.js.map +1 -0
  93. package/x/tools/noise.js.map +1 -0
  94. package/x/{concepts → tools}/randy.d.ts +0 -8
  95. package/x/{concepts → tools}/randy.js +0 -10
  96. package/x/tools/randy.js.map +1 -0
  97. package/x/{concepts → tools}/scalar.d.ts +4 -4
  98. package/x/{concepts → tools}/scalar.js +11 -11
  99. package/x/tools/scalar.js.map +1 -0
  100. package/x/{concepts → tools}/spline.d.ts +3 -3
  101. package/x/{concepts → tools}/spline.js +3 -1
  102. package/x/tools/spline.js.map +1 -0
  103. package/s/concepts/angles.ts +0 -74
  104. package/x/concepts/angles.js +0 -62
  105. package/x/concepts/angles.js.map +0 -1
  106. package/x/concepts/circular.js.map +0 -1
  107. package/x/concepts/noise.js.map +0 -1
  108. package/x/concepts/quat.js.map +0 -1
  109. package/x/concepts/randy.js.map +0 -1
  110. package/x/concepts/scalar.js.map +0 -1
  111. package/x/concepts/spline.js.map +0 -1
  112. package/x/concepts/vec2.js.map +0 -1
  113. package/x/concepts/vec3.js.map +0 -1
  114. package/x/concepts/vec4.js.map +0 -1
  115. /package/s/{concepts → tools}/noise.ts +0 -0
  116. /package/x/{concepts → tools}/circular.d.ts +0 -0
  117. /package/x/{concepts → tools}/noise.d.ts +0 -0
  118. /package/x/{concepts → tools}/noise.js +0 -0
package/README.md CHANGED
@@ -1,26 +1,113 @@
1
1
 
2
2
  # @benev/math
3
- > benevolent's game math library.
4
-
5
- ***TODO*** actual documentation coming soon.
6
-
7
- ### numeric primitives
8
- - [Scalar](./s/concepts/scalar.ts)
9
- - [Vec2](./s/concepts/vec2.ts)
10
- - [Vec3](./s/concepts/vec3.ts)
11
- - [Vec4](./s/concepts/vec4.ts)
12
- - [Quat](./s/concepts/quat.ts)
13
-
14
- ### cool utilities
15
- - [Randy](./s/concepts/randy.ts)
16
- - [Circular](./s/concepts/circular.ts)
17
- - [Noise](./s/concepts/noise.ts)
18
- - [spline](./s/concepts/spline.ts)
19
-
20
- ### angular stuff
21
- - [angles.ts](./s/concepts/angles.ts)
22
- - Radians
23
- - Arcseconds
24
- - Turns
25
- - Degrees
3
+ > benevolent's typescript math library for games
4
+
5
+ <br/>
6
+
7
+ ## 🍋 CORE
8
+ > common numerical structures
9
+
10
+ > [!TIP]
11
+ > until real docs are written, see the relevant sourcecode in [s/core/](./s/core/)
12
+
13
+ ### 🍏 conventions for all core classes
14
+ - **mutable by default.**
15
+ operations happen in-place, for efficiency (we're trying to reduce gc churn).
16
+ ```ts
17
+ // allocate a single vector instance
18
+ const vector = new Vec2(0, 0)
19
+ .add({x: 1, y: 2})
20
+ .multiplyBy(2)
21
+ ```
22
+ - **explicit cloning.**
23
+ use `.clone()` to avoid mutating the original.
24
+ ```ts
25
+ // modify a clone (not the original)
26
+ const vector2 = vector
27
+ .clone()
28
+ .normalize()
29
+ ```
30
+ - **underscore-suffixed methods take direct args.**
31
+ - methods normally take in other class instances:
32
+ ```ts
33
+ vectorA.add(vectorB)
34
+ ```
35
+ - but underscore-suffixed methods take raw naked args (helping you avoid making instances)
36
+ ```ts
37
+ vectorA.add_(x, y)
38
+ ```
39
+
40
+ ### 🍏 Vec2
41
+ ### 🍏 Vec3
42
+ ### 🍏 Vec4
43
+ ### 🍏 Quat
44
+
45
+ <br/>
46
+
47
+ ## 🍋 TOOLS
48
+ > handy utilities
49
+
50
+ > [!TIP]
51
+ > until real docs are written, see the relevant sourcecode in [s/tools/](./s/tools/)
52
+
53
+ ### 🍏 Scalar
54
+ ### 🍏 Circular
55
+ ### 🍏 Randy
56
+ ### 🍏 Noise
57
+ ### 🍏 Spline
58
+ ### 🍏 Angles
59
+ - **Radians**
60
+ - **Degrees**
61
+ - **Turns**
62
+ - **Arcseconds**
63
+
64
+ <br/>
65
+
66
+ ## 🍋 SHAPES
67
+ > geometric concepts
68
+
69
+ > [!TIP]
70
+ > until real docs are written, see the relevant sourcecode in [s/shapes/](./s/shapes/)
71
+
72
+ ### 🍏 2d shapes
73
+ - **Edge** — a line segment
74
+ - **Pill** — a fat line segment (like a sausage)
75
+ - **Rect** — a rectangle
76
+ - **Circle** — a point with a radius
77
+
78
+ ### 🍏 3d shapes
79
+ - **Segment** — a line segment
80
+ - **Capsule** — a fat line segment (like a hoagie)
81
+ - **Box** — a cuboid
82
+ - **Sphere** — a point with a radius
83
+
84
+ <br/>
85
+
86
+ ## 🍋 OPTIMIZERS
87
+ > spatial optimization data structures
88
+
89
+ > [!TIP]
90
+ > until real docs are written, see the relevant sourcecode in [s/optimizers/](./s/optimizers/)
91
+
92
+ ### 🍏 HashMap
93
+ ### 🍏 HashSet
94
+ ### 🍏 ZenGrid
95
+
96
+ <br/>
97
+
98
+ ## 🍋 PHYSICS
99
+ > functionality for doing basic physics
100
+
101
+ > [!TIP]
102
+ > until real docs are written, see the relevant sourcecode in [s/physics/](./s/physics/)
103
+
104
+ ### 🍏 collide2d
105
+ ### 🍏 collide3d
106
+ ### 🍏 intersect2d
107
+ ### 🍏 intersect3d
108
+
109
+ <br/>
110
+
111
+ ## 👼 https://benevolent.games/
112
+ star this on github if you use it 👍
26
113
 
package/package.json CHANGED
@@ -1,10 +1,11 @@
1
1
  {
2
2
  "name": "@benev/math",
3
- "version": "0.1.0",
3
+ "version": "0.2.0-1",
4
4
  "description": "game math toolkit",
5
5
  "license": "MIT",
6
6
  "author": "Chase Moskal <chasemoskal@gmail.com>",
7
7
  "type": "module",
8
+ "sideEffects": false,
8
9
  "main": "./x/index.js",
9
10
  "exports": {
10
11
  ".": "./x/index.js"
@@ -21,6 +22,7 @@
21
22
  "count": "find s -path '*/_archive' -prune -o -name '*.ts' -exec wc -l {} +"
22
23
  },
23
24
  "dependencies": {
25
+ "@e280/stz": "^0.1.0",
24
26
  "simplex-noise": "^4.0.3"
25
27
  },
26
28
  "devDependencies": {
@@ -1,7 +1,7 @@
1
1
 
2
2
  import {Xyz} from "./vec3.js"
3
3
 
4
- export type QuatArray = [number, number, number, number]
4
+ export type XyzwArray = [x: number, y: number, z: number, w: number]
5
5
  export type Xyzw = {x: number, y: number, z: number, w: number}
6
6
 
7
7
  export class Quat {
@@ -20,18 +20,10 @@ export class Quat {
20
20
  return new this(0, 0, 0, 1)
21
21
  }
22
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) {
23
+ static from(q: XyzwArray | Xyzw) {
32
24
  return Array.isArray(q)
33
- ? this.array(q)
34
- : this.import(q)
25
+ ? new this(...q)
26
+ : new this(q.x, q.y, q.z, q.w)
35
27
  }
36
28
 
37
29
  static rotate_(pitch: number, yaw: number, roll: number) {
@@ -42,7 +34,7 @@ export class Quat {
42
34
  return this.identity().rotate(vec)
43
35
  }
44
36
 
45
- array(): QuatArray {
37
+ toJSON(): XyzwArray {
46
38
  const {x, y, z, w} = this
47
39
  return [x, y, z, w]
48
40
  }
@@ -52,13 +44,61 @@ export class Quat {
52
44
  }
53
45
 
54
46
  clone() {
55
- return new Quat(...this.array())
47
+ return new Quat(...this.toJSON())
48
+ }
49
+
50
+ lengthSquared() {
51
+ return this.x*this.x + this.y*this.y + this.z*this.z + this.w*this.w
52
+ }
53
+
54
+ length() {
55
+ return Math.sqrt(this.lengthSquared())
56
+ }
57
+
58
+ isUnit(epsilon=1e-6) {
59
+ return Math.abs(this.length() - 1) <= epsilon
60
+ }
61
+
62
+ ensureUnit(){
63
+ if (!this.isUnit()) this.normalize()
64
+ return this
65
+ }
66
+
67
+ normalize(){
68
+ const m = this.length()
69
+ if (!m) {
70
+ this.x = this.y = this.z = 0
71
+ this.w = 1
72
+ return this
73
+ }
74
+ this.x /= m
75
+ this.y /= m
76
+ this.z /= m
77
+ this.w /= m
78
+ return this
79
+ }
80
+
81
+ conjugate() {
82
+ this.x *= -1
83
+ this.y *= -1
84
+ this.z *= -1
85
+ return this
86
+ }
87
+
88
+ invert() {
89
+ const len2 = this.lengthSquared()
90
+ if (!len2) return this.set_(0, 0, 0, 1)
91
+ this.x = -this.x / len2
92
+ this.y = -this.y / len2
93
+ this.z = -this.z / len2
94
+ this.w = this.w / len2
95
+ return this
56
96
  }
57
97
 
58
98
  transform_(x: number, y: number, z: number, w: number, global = false) {
59
99
  if (global) {
60
- const original = this.array()
61
- return this.set_(x, y, z, w).multiply_(...original)
100
+ const original = this.clone()
101
+ return this.set_(x, y, z, w).multiply(original)
62
102
  }
63
103
  else {
64
104
  return this.multiply_(x, y, z, w)
@@ -1,44 +1,31 @@
1
1
 
2
- import {Scalar} from "./scalar.js"
2
+ import {Scalar} from "../tools/scalar.js"
3
3
 
4
- export type Vec2Array = [number, number]
4
+ export type XyArray = [x: number, y: number]
5
5
  export type Xy = {x: number, y: number}
6
6
 
7
- /** https://github.com/microsoft/TypeScript/issues/5863 */
8
- type TsHack<T> = {new(...a: ConstructorParameters<typeof Vec2>): T}
9
-
10
7
  export class Vec2 implements Xy {
11
8
  constructor(
12
9
  public x: number,
13
10
  public y: number,
14
11
  ) {}
15
12
 
16
- ///////////////////////////////////////////////////////////////////////
17
-
18
- static new<T extends Vec2>(this: TsHack<T>, x: number, y: number): T {
13
+ static new(x: number, y: number) {
19
14
  return new this(x, y)
20
15
  }
21
16
 
22
- static zero<T extends Vec2>(this: TsHack<T>) {
17
+ static zero() {
23
18
  return new this(0, 0)
24
19
  }
25
20
 
26
- static all<T extends Vec2>(this: TsHack<T>, value: number) {
21
+ static all(value: number) {
27
22
  return new this(value, value)
28
23
  }
29
24
 
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) {
25
+ static from(v: XyArray | Xy) {
39
26
  return Array.isArray(v)
40
- ? this.array(v)
41
- : this.import(v)
27
+ ? new this(...v)
28
+ : new this(v.x, v.y)
42
29
  }
43
30
 
44
31
  static magnitudeSquared(x: number, y: number) {
@@ -55,27 +42,37 @@ export class Vec2 implements Xy {
55
42
  .divideBy(vectors.length)
56
43
  }
57
44
 
58
- static min(...vecs: Vec2[]) {
59
- return new Vec2(
45
+ static min(...vecs: Xy[]) {
46
+ return new this(
60
47
  Math.min(...vecs.map(v => v.x)),
61
48
  Math.min(...vecs.map(v => v.y)),
62
49
  )
63
50
  }
64
51
 
65
- static max(...vecs: Vec2[]) {
66
- return new Vec2(
52
+ static max(...vecs: Xy[]) {
53
+ return new this(
67
54
  Math.max(...vecs.map(v => v.x)),
68
55
  Math.max(...vecs.map(v => v.y)),
69
56
  )
70
57
  }
71
58
 
72
- ///////////////////////////////////////////////////////////////////////
59
+ static fromAngle(radians: number) {
60
+ return new this(
61
+ Math.cos(radians),
62
+ Math.sin(radians),
63
+ )
64
+ }
73
65
 
74
66
  clone() {
75
67
  return new Vec2(this.x, this.y)
76
68
  }
77
69
 
78
- array(): Vec2Array {
70
+ *[Symbol.iterator]() {
71
+ yield this.x
72
+ yield this.y
73
+ }
74
+
75
+ toJSON(): XyArray {
79
76
  return [this.x, this.y]
80
77
  }
81
78
 
@@ -97,8 +94,6 @@ export class Vec2 implements Xy {
97
94
  return this
98
95
  }
99
96
 
100
- ///////////////////////////////////////////////////////////////////////
101
-
102
97
  magnitudeSquared() {
103
98
  return Vec2.magnitudeSquared(this.x, this.y)
104
99
  }
@@ -122,6 +117,17 @@ export class Vec2 implements Xy {
122
117
  return vecs.every(({x, y}) => this.equals_(x, y))
123
118
  }
124
119
 
120
+ near_(x: number, y: number, epsilon = 1e-6) {
121
+ return (
122
+ Math.abs(this.x - x) <= epsilon &&
123
+ Math.abs(this.y - y) <= epsilon
124
+ )
125
+ }
126
+
127
+ near({x, y}: Xy, epsilon = 1e-6) {
128
+ return this.near_(x, y, epsilon)
129
+ }
130
+
125
131
  dot_(x: number, y: number) {
126
132
  return (this.x * x) + (this.y * y)
127
133
  }
@@ -151,15 +157,15 @@ export class Vec2 implements Xy {
151
157
  angleBetween_(x: number, y: number) {
152
158
  const dot = this.dot_(x, y)
153
159
  const magnitudes = this.magnitude() * Vec2.magnitude(x, y)
154
- return Math.acos(dot / magnitudes)
160
+ if (magnitudes === 0) return 0
161
+ const ratio = Scalar.clamp(dot / magnitudes, -1, 1)
162
+ return Math.acos(ratio)
155
163
  }
156
164
 
157
165
  angleBetween({x, y}: Xy) {
158
166
  return this.angleBetween_(x, y)
159
167
  }
160
168
 
161
- ///////////////////////////////////////////////////////////////////////
162
-
163
169
  /** mutator */
164
170
  normalize() {
165
171
  return this.divideBy(this.magnitude())
@@ -233,9 +239,17 @@ export class Vec2 implements Xy {
233
239
  }
234
240
 
235
241
  /** mutator */
236
- clamp(min: number, max: number) {
237
- const clamp = (val: number) => Math.max(min, Math.min(max, val))
238
- return this.map(clamp)
242
+ clamp(min: Xy, max: Xy) {
243
+ this.x = Scalar.clamp(this.x, min.x, max.x)
244
+ this.y = Scalar.clamp(this.y, min.y, max.y)
245
+ return this
246
+ }
247
+
248
+ /** mutator */
249
+ clampBy(min: number, max: number) {
250
+ this.x = Scalar.clamp(this.x, min, max)
251
+ this.y = Scalar.clamp(this.y, min, max)
252
+ return this
239
253
  }
240
254
 
241
255
  /** mutator */
@@ -315,8 +329,8 @@ export class Vec2 implements Xy {
315
329
 
316
330
  /** mutator */
317
331
  divide_(x: number, y: number) {
318
- this.x /= x
319
- this.y /= y
332
+ if (x !== 0) this.x /= x
333
+ if (y !== 0) this.y /= y
320
334
  return this
321
335
  }
322
336
 
@@ -373,7 +387,7 @@ export class Vec2 implements Xy {
373
387
  }
374
388
 
375
389
  /** mutator */
376
- rotateAroundPoint({x, y}: Vec2, radians: number) {
390
+ rotateAroundPoint({x, y}: Xy, radians: number) {
377
391
  return this.rotateAroundPoint_(x, y, radians)
378
392
  }
379
393
 
@@ -1,12 +1,9 @@
1
1
 
2
- import {Scalar} from "./scalar.js"
2
+ import {Scalar} from "../tools/scalar.js"
3
3
 
4
- export type Vec3Array = [number, number, number]
4
+ export type XyzArray = [x: number, y: number, z: number]
5
5
  export type Xyz = {x: number, y: number, z: number}
6
6
 
7
- /** https://github.com/microsoft/TypeScript/issues/5863 */
8
- type TsHack<T> = {new(...a: ConstructorParameters<typeof Vec3>): T}
9
-
10
7
  export class Vec3 {
11
8
  constructor(
12
9
  public x: number,
@@ -14,32 +11,22 @@ export class Vec3 {
14
11
  public z: number,
15
12
  ) {}
16
13
 
17
- ///////////////////////////////////////////////////////////////////////
18
-
19
- static new<T extends Vec3>(this: TsHack<T>, x: number, y: number, z: number) {
14
+ static new(x: number, y: number, z: number) {
20
15
  return new this(x, y, z)
21
16
  }
22
17
 
23
- static zero<T extends Vec3>(this: TsHack<T>) {
18
+ static zero() {
24
19
  return new this(0, 0, 0)
25
20
  }
26
21
 
27
- static all<T extends Vec3>(this: TsHack<T>, value: number) {
22
+ static all(value: number) {
28
23
  return new this(value, value, value)
29
24
  }
30
25
 
31
- static array<T extends Vec3>(this: TsHack<T>, v: Vec3Array) {
32
- return new this(...v)
33
- }
34
-
35
- static import<T extends Vec3>(this: TsHack<T>, {x, y, z}: Xyz): Vec3 {
36
- return new this(x, y, z)
37
- }
38
-
39
- static from(v: Vec3Array | Xyz) {
26
+ static from(v: XyzArray | Xyz) {
40
27
  return Array.isArray(v)
41
- ? this.array(v)
42
- : this.import(v)
28
+ ? new this(...v)
29
+ : new this(v.x, v.y, v.z)
43
30
  }
44
31
 
45
32
  static magnitudeSquared(x: number, y: number, z: number) {
@@ -56,23 +43,23 @@ export class Vec3 {
56
43
  .divideBy(vecs.length)
57
44
  }
58
45
 
59
- static min(...vecs: Vec3[]) {
60
- return new Vec3(
46
+ static min(...vecs: Xyz[]) {
47
+ return new this(
61
48
  Math.min(...vecs.map(v => v.x)),
62
49
  Math.min(...vecs.map(v => v.y)),
63
50
  Math.min(...vecs.map(v => v.z)),
64
51
  )
65
52
  }
66
53
 
67
- static max(...vecs: Vec3[]) {
68
- return new Vec3(
54
+ static max(...vecs: Xyz[]) {
55
+ return new this(
69
56
  Math.max(...vecs.map(v => v.x)),
70
57
  Math.max(...vecs.map(v => v.y)),
71
58
  Math.max(...vecs.map(v => v.z)),
72
59
  )
73
60
  }
74
61
 
75
- static hexColor(hex: string): Vec3 {
62
+ static fromHex(hex: string): Vec3 {
76
63
  if (hex.startsWith("#") && hex.length === 7) {
77
64
  const r = parseInt(hex.slice(1, 3), 16) / 255
78
65
  const g = parseInt(hex.slice(3, 5), 16) / 255
@@ -83,13 +70,17 @@ export class Vec3 {
83
70
  }
84
71
  }
85
72
 
86
- ///////////////////////////////////////////////////////////////////////
87
-
88
73
  clone() {
89
74
  return new Vec3(this.x, this.y, this.z)
90
75
  }
91
76
 
92
- array(): Vec3Array {
77
+ *[Symbol.iterator]() {
78
+ yield this.x
79
+ yield this.y
80
+ yield this.z
81
+ }
82
+
83
+ toJSON(): XyzArray {
93
84
  return [this.x, this.y, this.z]
94
85
  }
95
86
 
@@ -111,8 +102,6 @@ export class Vec3 {
111
102
  return this
112
103
  }
113
104
 
114
- ///////////////////////////////////////////////////////////////////////
115
-
116
105
  magnitudeSquared() {
117
106
  return Vec3.magnitudeSquared(this.x, this.y, this.z)
118
107
  }
@@ -121,14 +110,13 @@ export class Vec3 {
121
110
  return Vec3.magnitude(this.x, this.y, this.z)
122
111
  }
123
112
 
124
- hexColor() {
125
- const to255 = (val: number) => Math.round(val * 255)
113
+ /** rgb values from 0-1 */
114
+ toHex() {
115
+ const to255 = (val: number) => Math.round(Scalar.clamp(val * 255, 0, 255))
126
116
  const toHex = (val: number) => to255(val).toString(16).padStart(2, '0')
127
117
  return `#${toHex(this.x)}${toHex(this.y)}${toHex(this.z)}`
128
118
  }
129
119
 
130
- ///////////////////////////////////////////////////////////////////////
131
-
132
120
  equals_(x: number, y: number, z: number) {
133
121
  return (
134
122
  this.x === x &&
@@ -138,7 +126,19 @@ export class Vec3 {
138
126
  }
139
127
 
140
128
  equals(...vecs: Xyz[]) {
141
- return vecs.every(v => this.equals_(v.x, v.y, v.z))
129
+ return vecs.every(({x, y, z}) => this.equals_(x, y, z))
130
+ }
131
+
132
+ near_(x: number, y: number, z: number, epsilon = 1e-6) {
133
+ return (
134
+ Math.abs(this.x - x) <= epsilon &&
135
+ Math.abs(this.y - y) <= epsilon &&
136
+ Math.abs(this.z - z) <= epsilon
137
+ )
138
+ }
139
+
140
+ near({x, y, z}: Xyz, epsilon = 1e-6) {
141
+ return this.near_(x, y, z, epsilon)
142
142
  }
143
143
 
144
144
  distanceSquared_(x: number, y: number, z: number) {
@@ -179,16 +179,16 @@ export class Vec3 {
179
179
 
180
180
  angleBetween_(x: number, y: number, z: number) {
181
181
  const dotProduct = this.dot_(x, y, z)
182
- const magnitudes = this.magnitude() * Vec3.new(x, y, z).magnitude()
183
- return Math.acos(dotProduct / magnitudes)
182
+ const magnitudes = this.magnitude() * Vec3.magnitude(x, y, z)
183
+ if (magnitudes === 0) return 0
184
+ const ratio = Scalar.clamp(dotProduct / magnitudes, -1, 1)
185
+ return Math.acos(ratio)
184
186
  }
185
187
 
186
188
  angleBetween({x, y, z}: Xyz) {
187
189
  return this.angleBetween_(x, y, z)
188
190
  }
189
191
 
190
- ///////////////////////////////////////////////////////////////////////
191
-
192
192
  /** mutator */
193
193
  add_(x: number, y: number, z: number) {
194
194
  this.x += x
@@ -226,21 +226,21 @@ export class Vec3 {
226
226
  }
227
227
 
228
228
  /** mutator */
229
- multiply(...vecs: Vec3[]) {
229
+ multiply(...vecs: Xyz[]) {
230
230
  for (const {x, y, z} of vecs) this.multiply_(x, y, z)
231
231
  return this
232
232
  }
233
233
 
234
234
  /** mutator */
235
235
  divide_(x: number, y: number, z: number) {
236
- this.x /= x
237
- this.y /= y
238
- this.z /= z
236
+ if (x !== 0) this.x /= x
237
+ if (y !== 0) this.y /= y
238
+ if (z !== 0) this.z /= z
239
239
  return this
240
240
  }
241
241
 
242
242
  /** mutator */
243
- divide(...vecs: Vec3[]) {
243
+ divide(...vecs: Xyz[]) {
244
244
  for (const {x, y, z} of vecs) this.divide_(x, y, z)
245
245
  return this
246
246
  }
@@ -286,6 +286,14 @@ export class Vec3 {
286
286
  return this
287
287
  }
288
288
 
289
+ /** mutator */
290
+ subtractBy(delta: number) {
291
+ this.x -= delta
292
+ this.y -= delta
293
+ this.z -= delta
294
+ return this
295
+ }
296
+
289
297
  /** mutator */
290
298
  multiplyBy(delta: number) {
291
299
  this.x *= delta
@@ -381,7 +389,9 @@ export class Vec3 {
381
389
 
382
390
  /** mutator */
383
391
  projectOnto_(x: number, y: number, z: number) {
384
- const scalar = this.dot_(x, y, z) / Vec3.magnitudeSquared(x, y, z)
392
+ const m2 = Vec3.magnitudeSquared(x, y, z)
393
+ if (!m2) return this
394
+ const scalar = this.dot_(x, y, z) / m2
385
395
  this.x = scalar * x
386
396
  this.y = scalar * y
387
397
  this.z = scalar * z
@@ -389,7 +399,7 @@ export class Vec3 {
389
399
  }
390
400
 
391
401
  /** mutator */
392
- projectOnto({x, y, z}: Vec3) {
402
+ projectOnto({x, y, z}: Xyz) {
393
403
  return this.projectOnto_(x, y, z)
394
404
  }
395
405