@forgeax/engine-math 0.0.0-dev.8d955ade1c79

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 (150) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +294 -0
  3. package/dist/.tsbuildinfo +1 -0
  4. package/dist/__tests__/_arbs.d.ts +36 -0
  5. package/dist/__tests__/_arbs.d.ts.map +1 -0
  6. package/dist/__tests__/_fixtures.d.ts +61 -0
  7. package/dist/__tests__/_fixtures.d.ts.map +1 -0
  8. package/dist/__tests__/bounds2.test.d.ts +2 -0
  9. package/dist/__tests__/bounds2.test.d.ts.map +1 -0
  10. package/dist/__tests__/box3.test.d.ts +2 -0
  11. package/dist/__tests__/box3.test.d.ts.map +1 -0
  12. package/dist/__tests__/easing.test.d.ts +2 -0
  13. package/dist/__tests__/easing.test.d.ts.map +1 -0
  14. package/dist/__tests__/euler.test-d.d.ts +2 -0
  15. package/dist/__tests__/euler.test-d.d.ts.map +1 -0
  16. package/dist/__tests__/mat3.test-d.d.ts +2 -0
  17. package/dist/__tests__/mat3.test-d.d.ts.map +1 -0
  18. package/dist/__tests__/mat4.property.test.d.ts +2 -0
  19. package/dist/__tests__/mat4.property.test.d.ts.map +1 -0
  20. package/dist/__tests__/mat4.test-d.d.ts +2 -0
  21. package/dist/__tests__/mat4.test-d.d.ts.map +1 -0
  22. package/dist/__tests__/mat4.test.d.ts +2 -0
  23. package/dist/__tests__/mat4.test.d.ts.map +1 -0
  24. package/dist/__tests__/noise.test.d.ts +2 -0
  25. package/dist/__tests__/noise.test.d.ts.map +1 -0
  26. package/dist/__tests__/quat.basis.test.d.ts +2 -0
  27. package/dist/__tests__/quat.basis.test.d.ts.map +1 -0
  28. package/dist/__tests__/quat.interpolation.test.d.ts +2 -0
  29. package/dist/__tests__/quat.interpolation.test.d.ts.map +1 -0
  30. package/dist/__tests__/quat.lookat.test.d.ts +2 -0
  31. package/dist/__tests__/quat.lookat.test.d.ts.map +1 -0
  32. package/dist/__tests__/quat.property.test.d.ts +2 -0
  33. package/dist/__tests__/quat.property.test.d.ts.map +1 -0
  34. package/dist/__tests__/quat.rotateaxis.test.d.ts +2 -0
  35. package/dist/__tests__/quat.rotateaxis.test.d.ts.map +1 -0
  36. package/dist/__tests__/quat.test-d.d.ts +2 -0
  37. package/dist/__tests__/quat.test-d.d.ts.map +1 -0
  38. package/dist/__tests__/ray.property.test.d.ts +2 -0
  39. package/dist/__tests__/ray.property.test.d.ts.map +1 -0
  40. package/dist/__tests__/ray.test.d.ts +2 -0
  41. package/dist/__tests__/ray.test.d.ts.map +1 -0
  42. package/dist/__tests__/types.test-d.d.ts +2 -0
  43. package/dist/__tests__/types.test-d.d.ts.map +1 -0
  44. package/dist/__tests__/vec-catmull-rom.test.d.ts +2 -0
  45. package/dist/__tests__/vec-catmull-rom.test.d.ts.map +1 -0
  46. package/dist/__tests__/vec-smooth-damp.test.d.ts +2 -0
  47. package/dist/__tests__/vec-smooth-damp.test.d.ts.map +1 -0
  48. package/dist/__tests__/vec2.test-d.d.ts +2 -0
  49. package/dist/__tests__/vec2.test-d.d.ts.map +1 -0
  50. package/dist/__tests__/vec3.property.test.d.ts +2 -0
  51. package/dist/__tests__/vec3.property.test.d.ts.map +1 -0
  52. package/dist/__tests__/vec3.test-d.d.ts +2 -0
  53. package/dist/__tests__/vec3.test-d.d.ts.map +1 -0
  54. package/dist/__tests__/vec4.test-d.d.ts +2 -0
  55. package/dist/__tests__/vec4.test-d.d.ts.map +1 -0
  56. package/dist/_internal/epsilon.d.ts +9 -0
  57. package/dist/_internal/epsilon.d.ts.map +1 -0
  58. package/dist/_internal/scalar.d.ts +40 -0
  59. package/dist/_internal/scalar.d.ts.map +1 -0
  60. package/dist/box2.d.ts +35 -0
  61. package/dist/box2.d.ts.map +1 -0
  62. package/dist/box3.d.ts +76 -0
  63. package/dist/box3.d.ts.map +1 -0
  64. package/dist/circle2.d.ts +29 -0
  65. package/dist/circle2.d.ts.map +1 -0
  66. package/dist/color.d.ts +64 -0
  67. package/dist/color.d.ts.map +1 -0
  68. package/dist/easing.d.ts +18 -0
  69. package/dist/easing.d.ts.map +1 -0
  70. package/dist/euler.d.ts +51 -0
  71. package/dist/euler.d.ts.map +1 -0
  72. package/dist/f32-to-f16-bytes.d.ts +10 -0
  73. package/dist/f32-to-f16-bytes.d.ts.map +1 -0
  74. package/dist/frustum.d.ts +49 -0
  75. package/dist/frustum.d.ts.map +1 -0
  76. package/dist/index.d.ts +20 -0
  77. package/dist/index.d.ts.map +1 -0
  78. package/dist/index.mjs +3688 -0
  79. package/dist/index.mjs.map +1 -0
  80. package/dist/mat3.d.ts +70 -0
  81. package/dist/mat3.d.ts.map +1 -0
  82. package/dist/mat4.d.ts +329 -0
  83. package/dist/mat4.d.ts.map +1 -0
  84. package/dist/noise.d.ts +12 -0
  85. package/dist/noise.d.ts.map +1 -0
  86. package/dist/quat.d.ts +312 -0
  87. package/dist/quat.d.ts.map +1 -0
  88. package/dist/ray.d.ts +144 -0
  89. package/dist/ray.d.ts.map +1 -0
  90. package/dist/ray2.d.ts +38 -0
  91. package/dist/ray2.d.ts.map +1 -0
  92. package/dist/sphere.d.ts +41 -0
  93. package/dist/sphere.d.ts.map +1 -0
  94. package/dist/types.d.ts +57 -0
  95. package/dist/types.d.ts.map +1 -0
  96. package/dist/vec2.d.ts +76 -0
  97. package/dist/vec2.d.ts.map +1 -0
  98. package/dist/vec3.d.ts +82 -0
  99. package/dist/vec3.d.ts.map +1 -0
  100. package/dist/vec4.d.ts +61 -0
  101. package/dist/vec4.d.ts.map +1 -0
  102. package/package.json +57 -0
  103. package/src/__tests__/_arbs.ts +149 -0
  104. package/src/__tests__/_fixtures.ts +118 -0
  105. package/src/__tests__/bounds2.test.ts +146 -0
  106. package/src/__tests__/box3.test.ts +277 -0
  107. package/src/__tests__/easing.test.ts +109 -0
  108. package/src/__tests__/euler.test-d.ts +63 -0
  109. package/src/__tests__/mat3.test-d.ts +47 -0
  110. package/src/__tests__/mat4.property.test.ts +256 -0
  111. package/src/__tests__/mat4.test-d.ts +130 -0
  112. package/src/__tests__/mat4.test.ts +162 -0
  113. package/src/__tests__/noise.test.ts +75 -0
  114. package/src/__tests__/quat.basis.test.ts +134 -0
  115. package/src/__tests__/quat.interpolation.test.ts +45 -0
  116. package/src/__tests__/quat.lookat.test.ts +103 -0
  117. package/src/__tests__/quat.property.test.ts +150 -0
  118. package/src/__tests__/quat.rotateaxis.test.ts +149 -0
  119. package/src/__tests__/quat.test-d.ts +138 -0
  120. package/src/__tests__/ray.property.test.ts +105 -0
  121. package/src/__tests__/ray.test.ts +539 -0
  122. package/src/__tests__/types.test-d.ts +64 -0
  123. package/src/__tests__/vec-catmull-rom.test.ts +125 -0
  124. package/src/__tests__/vec-smooth-damp.test.ts +167 -0
  125. package/src/__tests__/vec2.test-d.ts +59 -0
  126. package/src/__tests__/vec3.property.test.ts +72 -0
  127. package/src/__tests__/vec3.test-d.ts +65 -0
  128. package/src/__tests__/vec4.test-d.ts +61 -0
  129. package/src/_internal/epsilon.ts +29 -0
  130. package/src/_internal/scalar.ts +90 -0
  131. package/src/box2.ts +168 -0
  132. package/src/box3.ts +372 -0
  133. package/src/circle2.ts +134 -0
  134. package/src/color.ts +177 -0
  135. package/src/easing.ts +49 -0
  136. package/src/euler.ts +239 -0
  137. package/src/f32-to-f16-bytes.ts +71 -0
  138. package/src/frustum.ts +240 -0
  139. package/src/index.ts +65 -0
  140. package/src/mat3.ts +286 -0
  141. package/src/mat4.ts +1334 -0
  142. package/src/noise.ts +78 -0
  143. package/src/quat.ts +847 -0
  144. package/src/ray.ts +575 -0
  145. package/src/ray2.ts +198 -0
  146. package/src/sphere.ts +138 -0
  147. package/src/types.ts +78 -0
  148. package/src/vec2.ts +229 -0
  149. package/src/vec3.ts +294 -0
  150. package/src/vec4.ts +235 -0
package/src/vec3.ts ADDED
@@ -0,0 +1,294 @@
1
+ // vec3.ts — 3D vector namespace (M2 / T-014, rewritten from the M1 baseline of 8 functions)
2
+ //
3
+ // 21-function surface (≥ 18 lower bound): vec2 base (without perp) + cross + distanceSq.
4
+ // create / clone / copy / set / equals / add / sub / scale / negate /
5
+ // dot / cross / lengthSq / length / distance / distanceSq /
6
+ // normalize / lerp / smoothDamp / catmullRom / min / max
7
+ //
8
+ // Cross-type transforms are provided via the reverse surfaces of mat4 / quat (K-1/K-2 tore down
9
+ // the Three.js-style promise; see plan-decisions §D-12): the three mat4 ns transform* functions
10
+ // + quat.transformVec3 supersede the historical promise to "hang cross-type methods on vec3 instances".
11
+ //
12
+ // Design locks:
13
+ // - branded Float32Array (types.ts SSOT); factory `as Vec3` casts are funneled.
14
+ // - The legacy local Vec3 / Vec3Like type aliases are removed; mat4 / quat now `import './types'` Vec3Like.
15
+ // - Out-param first + aliasing-safe (gl-matrix four ironclad rules, wiki/gl-matrix-overview).
16
+ // - normalize(0-vec) → 0-vec silent fall-back (D-P12 / AC-06).
17
+ // - mandatory `export function` (D-P10).
18
+ //
19
+ // Related: requirements §Surface vec3 lower bound 18 + AC-19 legacy 8-function removal (vec3 portion);
20
+ // plan-strategy §6 M2 + §1.1 vec3.ts;
21
+ // wiki/typescript-branded-types §7.2 factory template;
22
+ // wiki/gl-matrix-overview Out-param four ironclad rules + degenerate anchor.
23
+ //
24
+ // Compatibility: this file still re-exports `Vec3Like` from ./types so mat4 / quat's existing imports
25
+ // keep working (M2 does not force-update mat4 / quat import paths; that lands in M3).
26
+
27
+ import { EPS_NORMALIZE } from './_internal/epsilon';
28
+ import { catmullRomScalar, lerp as scalarLerp, smoothDecayFactor } from './_internal/scalar';
29
+ import type { Vec3, Vec3Like } from './types';
30
+
31
+ export type { Vec3, Vec3Like };
32
+
33
+ /** Create a Vec3 (zero vector by default). */
34
+ export function create(x = 0, y = 0, z = 0): Vec3 {
35
+ return Float32Array.of(x, y, z) as Vec3;
36
+ }
37
+
38
+ /** Allocate a new Vec3 copy. */
39
+ export function clone(a: Vec3Like): Vec3 {
40
+ return Float32Array.of(a[0] as number, a[1] as number, a[2] as number) as Vec3;
41
+ }
42
+
43
+ /** out = a. aliasing-safe (copy(v, v) is a no-op). */
44
+ export function copy(out: Vec3, a: Vec3Like): Vec3 {
45
+ out[0] = a[0] as number;
46
+ out[1] = a[1] as number;
47
+ out[2] = a[2] as number;
48
+ return out;
49
+ }
50
+
51
+ /** Write the components and return out. */
52
+ export function set(out: Vec3, x: number, y: number, z: number): Vec3 {
53
+ out[0] = x;
54
+ out[1] = y;
55
+ out[2] = z;
56
+ return out;
57
+ }
58
+
59
+ /**
60
+ * Approximate equality: every component differs by ≤ epsilon. NaN inputs always return false (IEEE 754).
61
+ */
62
+ export function equals(a: Vec3Like, b: Vec3Like, epsilon = 1e-6): boolean {
63
+ const ax = a[0] as number;
64
+ const ay = a[1] as number;
65
+ const az = a[2] as number;
66
+ const bx = b[0] as number;
67
+ const by = b[1] as number;
68
+ const bz = b[2] as number;
69
+ if (
70
+ Number.isNaN(ax) ||
71
+ Number.isNaN(ay) ||
72
+ Number.isNaN(az) ||
73
+ Number.isNaN(bx) ||
74
+ Number.isNaN(by) ||
75
+ Number.isNaN(bz)
76
+ ) {
77
+ return false;
78
+ }
79
+ return (
80
+ Math.abs(ax - bx) <= epsilon && Math.abs(ay - by) <= epsilon && Math.abs(az - bz) <= epsilon
81
+ );
82
+ }
83
+
84
+ /** out = a + b. aliasing-safe. */
85
+ export function add(out: Vec3, a: Vec3Like, b: Vec3Like): Vec3 {
86
+ const ax = a[0] as number;
87
+ const ay = a[1] as number;
88
+ const az = a[2] as number;
89
+ const bx = b[0] as number;
90
+ const by = b[1] as number;
91
+ const bz = b[2] as number;
92
+ out[0] = ax + bx;
93
+ out[1] = ay + by;
94
+ out[2] = az + bz;
95
+ return out;
96
+ }
97
+
98
+ /** out = a - b. aliasing-safe. */
99
+ export function sub(out: Vec3, a: Vec3Like, b: Vec3Like): Vec3 {
100
+ const ax = a[0] as number;
101
+ const ay = a[1] as number;
102
+ const az = a[2] as number;
103
+ const bx = b[0] as number;
104
+ const by = b[1] as number;
105
+ const bz = b[2] as number;
106
+ out[0] = ax - bx;
107
+ out[1] = ay - by;
108
+ out[2] = az - bz;
109
+ return out;
110
+ }
111
+
112
+ /** out = a * s. */
113
+ export function scale(out: Vec3, a: Vec3Like, s: number): Vec3 {
114
+ out[0] = (a[0] as number) * s;
115
+ out[1] = (a[1] as number) * s;
116
+ out[2] = (a[2] as number) * s;
117
+ return out;
118
+ }
119
+
120
+ /** out = -a. */
121
+ export function negate(out: Vec3, a: Vec3Like): Vec3 {
122
+ out[0] = 0 - (a[0] as number);
123
+ out[1] = 0 - (a[1] as number);
124
+ out[2] = 0 - (a[2] as number);
125
+ return out;
126
+ }
127
+
128
+ /** Dot product a · b. */
129
+ export function dot(a: Vec3Like, b: Vec3Like): number {
130
+ return (
131
+ (a[0] as number) * (b[0] as number) +
132
+ (a[1] as number) * (b[1] as number) +
133
+ (a[2] as number) * (b[2] as number)
134
+ );
135
+ }
136
+
137
+ /** out = a × b (cross product). aliasing-safe (reads all 6 components into locals first). */
138
+ export function cross(out: Vec3, a: Vec3Like, b: Vec3Like): Vec3 {
139
+ const ax = a[0] as number;
140
+ const ay = a[1] as number;
141
+ const az = a[2] as number;
142
+ const bx = b[0] as number;
143
+ const by = b[1] as number;
144
+ const bz = b[2] as number;
145
+ out[0] = ay * bz - az * by;
146
+ out[1] = az * bx - ax * bz;
147
+ out[2] = ax * by - ay * bx;
148
+ return out;
149
+ }
150
+
151
+ /** Squared length |a|² (avoids sqrt overhead). */
152
+ export function lengthSq(a: Vec3Like): number {
153
+ const x = a[0] as number;
154
+ const y = a[1] as number;
155
+ const z = a[2] as number;
156
+ return x * x + y * y + z * z;
157
+ }
158
+
159
+ /** Euclidean length |a|. */
160
+ export function length(a: Vec3Like): number {
161
+ const x = a[0] as number;
162
+ const y = a[1] as number;
163
+ const z = a[2] as number;
164
+ return Math.sqrt(x * x + y * y + z * z);
165
+ }
166
+
167
+ /** Distance between two points |a - b|. */
168
+ export function distance(a: Vec3Like, b: Vec3Like): number {
169
+ const dx = (a[0] as number) - (b[0] as number);
170
+ const dy = (a[1] as number) - (b[1] as number);
171
+ const dz = (a[2] as number) - (b[2] as number);
172
+ return Math.sqrt(dx * dx + dy * dy + dz * dz);
173
+ }
174
+
175
+ /** Squared distance between two points |a - b|² (avoids sqrt overhead). */
176
+ export function distanceSq(a: Vec3Like, b: Vec3Like): number {
177
+ const dx = (a[0] as number) - (b[0] as number);
178
+ const dy = (a[1] as number) - (b[1] as number);
179
+ const dz = (a[2] as number) - (b[2] as number);
180
+ return dx * dx + dy * dy + dz * dz;
181
+ }
182
+
183
+ /**
184
+ * out = a / |a| (unit-length).
185
+ *
186
+ * @degrade Zero vector (|a|² < EPS_NORMALIZE) silently falls back to the zero vector;
187
+ * no NaN, no throw (gl-matrix style, AC-06 / D-P12).
188
+ * @degrade NaN inputs → NaN outputs: when a component is NaN, lenSq=NaN; the `lenSq < EPS`
189
+ * branch is false → goes through 1/sqrt(NaN)=NaN → output is all NaN (IEEE-754 NaN propagation;
190
+ * still does not throw).
191
+ *
192
+ * @example
193
+ * ```ts
194
+ * vec3.normalize(out, [3, 0, 4]); // → (0.6, 0, 0.8)
195
+ * vec3.normalize(out, [0, 0, 0]); // → (0, 0, 0) zero vector falls back (AC-06 no throw)
196
+ * vec3.normalize(out, [NaN, 1, 0]); // → (NaN, NaN, NaN) IEEE-754 propagation
197
+ * ```
198
+ */
199
+ export function normalize(out: Vec3, a: Vec3Like): Vec3 {
200
+ const x = a[0] as number;
201
+ const y = a[1] as number;
202
+ const z = a[2] as number;
203
+ const lenSq = x * x + y * y + z * z;
204
+ if (lenSq < EPS_NORMALIZE) {
205
+ out[0] = 0;
206
+ out[1] = 0;
207
+ out[2] = 0;
208
+ return out;
209
+ }
210
+ const inv = 1 / Math.sqrt(lenSq);
211
+ out[0] = x * inv;
212
+ out[1] = y * inv;
213
+ out[2] = z * inv;
214
+ return out;
215
+ }
216
+
217
+ /** out = lerp(a, b, t). t is not clamped (extrapolation semantics). aliasing-safe. */
218
+ export function lerp(out: Vec3, a: Vec3Like, b: Vec3Like, t: number): Vec3 {
219
+ const ax = a[0] as number;
220
+ const ay = a[1] as number;
221
+ const az = a[2] as number;
222
+ const bx = b[0] as number;
223
+ const by = b[1] as number;
224
+ const bz = b[2] as number;
225
+ out[0] = scalarLerp(ax, bx, t);
226
+ out[1] = scalarLerp(ay, by, t);
227
+ out[2] = scalarLerp(az, bz, t);
228
+ return out;
229
+ }
230
+
231
+ /**
232
+ * out = frame-rate-INDEPENDENT exponential smoothing of `current` toward `target`:
233
+ * `lerp(current, target, 1 − exp(−decayRate · dt))`. Semantics match Bevy
234
+ * `StableInterpolate::smooth_nudge` / three.js `MathUtils.damp` — NOT Unity `Vector3.SmoothDamp`
235
+ * (which is a 2nd-order critically-damped spring needing a velocity ref; this is 1st-order decay).
236
+ *
237
+ * `decayRate` is the exponential decay constant (1/s), meant to stay fixed while `dt` is per-frame;
238
+ * a good anchor is `decayRate = ln(2)/halfLife`. `dt=0` → out=current (no move); large `decayRate·dt`
239
+ * → out≈target; `decayRate=0` → out=current. Frame-rate independent by construction: one step of `dt`
240
+ * equals two composed steps of `dt/2` — unlike the naive `lerp(current, target, rate·dt)`, which
241
+ * behaves differently per frame rate and overshoots when `rate·dt > 1`. aliasing-safe.
242
+ */
243
+ export function smoothDamp(
244
+ out: Vec3,
245
+ current: Vec3Like,
246
+ target: Vec3Like,
247
+ decayRate: number,
248
+ dt: number,
249
+ ): Vec3 {
250
+ return lerp(out, current, target, smoothDecayFactor(decayRate, dt));
251
+ }
252
+
253
+ /**
254
+ * out = Catmull-Rom spline point on the segment between `p1` and `p2`, with `p0` / `p3` the
255
+ * neighbor control points setting the endpoint tangents (tension 0.5 — the Bevy
256
+ * `CubicCardinalSpline::new_catmull_rom` / three.js `CatmullRomCurve3` default). Interpolates
257
+ * the control points: `t=0` → `p1`, `t=1` → `p2`. Unlike `lerp` (a straight segment), this is
258
+ * the smooth cubic through the points — use it for camera paths, animation ease paths, or
259
+ * procedural curve geometry. To sample a whole polyline, loop the segments with a sliding
260
+ * 4-point window `[pts[i-1], pts[i], pts[i+1], pts[i+2]]` (clamp/duplicate ends). `t` is not
261
+ * clamped (extrapolation follows the same cubic). aliasing-safe.
262
+ */
263
+ export function catmullRom(
264
+ out: Vec3,
265
+ p0: Vec3Like,
266
+ p1: Vec3Like,
267
+ p2: Vec3Like,
268
+ p3: Vec3Like,
269
+ t: number,
270
+ ): Vec3 {
271
+ const x = catmullRomScalar(p0[0] as number, p1[0] as number, p2[0] as number, p3[0] as number, t);
272
+ const y = catmullRomScalar(p0[1] as number, p1[1] as number, p2[1] as number, p3[1] as number, t);
273
+ const z = catmullRomScalar(p0[2] as number, p1[2] as number, p2[2] as number, p3[2] as number, t);
274
+ out[0] = x;
275
+ out[1] = y;
276
+ out[2] = z;
277
+ return out;
278
+ }
279
+
280
+ /** Component-wise min. */
281
+ export function min(out: Vec3, a: Vec3Like, b: Vec3Like): Vec3 {
282
+ out[0] = Math.min(a[0] as number, b[0] as number);
283
+ out[1] = Math.min(a[1] as number, b[1] as number);
284
+ out[2] = Math.min(a[2] as number, b[2] as number);
285
+ return out;
286
+ }
287
+
288
+ /** Component-wise max. */
289
+ export function max(out: Vec3, a: Vec3Like, b: Vec3Like): Vec3 {
290
+ out[0] = Math.max(a[0] as number, b[0] as number);
291
+ out[1] = Math.max(a[1] as number, b[1] as number);
292
+ out[2] = Math.max(a[2] as number, b[2] as number);
293
+ return out;
294
+ }
package/src/vec4.ts ADDED
@@ -0,0 +1,235 @@
1
+ // vec4.ts — 4D vector / homogeneous coordinate namespace (M2 / T-015)
2
+ //
3
+ // 18-function surface (≥ 14 lower bound; same shape as vec2/vec3, no cross / perp):
4
+ // create / clone / copy / set / equals / add / sub / scale / negate /
5
+ // dot / lengthSq / length / distance / normalize / lerp / smoothDamp / min / max
6
+ //
7
+ // Design locks:
8
+ // - branded Float32Array (types.ts SSOT); factory `as Vec4` casts are funneled (D-P15).
9
+ // - Out-param first + aliasing-safe (gl-matrix four ironclad rules).
10
+ // - normalize(0-vec) → 0-vec silent fall-back (D-P12 / AC-06).
11
+ // - mandatory `export function` (D-P10).
12
+ // - reuses lerp from _internal/scalar.ts (strategy D mixed; duplicate-code elimination).
13
+ // - Vec4 ≠ Quat (same length=4 brand mutual exclusion; types.ts SSOT keeps each brand independent).
14
+ //
15
+ // Related: requirements §Surface vec4 lower bound 14; plan-strategy §6 M2 + §1.1 vec4.ts LOC 240;
16
+ // wiki/typescript-branded-types §7.2 factory template;
17
+ // wiki/gl-matrix-overview Out-param four ironclad rules.
18
+
19
+ import { EPS_NORMALIZE } from './_internal/epsilon';
20
+ import { lengthSq4, normalize4, lerp as scalarLerp, smoothDecayFactor } from './_internal/scalar';
21
+ import type { Vec4, Vec4Like } from './types';
22
+
23
+ /** Create a Vec4 (zero vector by default). */
24
+ export function create(x = 0, y = 0, z = 0, w = 0): Vec4 {
25
+ return Float32Array.of(x, y, z, w) as Vec4;
26
+ }
27
+
28
+ /** Allocate a new Vec4 copy. */
29
+ export function clone(a: Vec4Like): Vec4 {
30
+ return Float32Array.of(a[0] as number, a[1] as number, a[2] as number, a[3] as number) as Vec4;
31
+ }
32
+
33
+ /** out = a. aliasing-safe (copy(v, v) is a no-op). */
34
+ export function copy(out: Vec4, a: Vec4Like): Vec4 {
35
+ out[0] = a[0] as number;
36
+ out[1] = a[1] as number;
37
+ out[2] = a[2] as number;
38
+ out[3] = a[3] as number;
39
+ return out;
40
+ }
41
+
42
+ /** Write the components and return out. */
43
+ export function set(out: Vec4, x: number, y: number, z: number, w: number): Vec4 {
44
+ out[0] = x;
45
+ out[1] = y;
46
+ out[2] = z;
47
+ out[3] = w;
48
+ return out;
49
+ }
50
+
51
+ /**
52
+ * Approximate equality: every component differs by ≤ epsilon. NaN inputs always return false (IEEE 754).
53
+ */
54
+ export function equals(a: Vec4Like, b: Vec4Like, epsilon = 1e-6): boolean {
55
+ const ax = a[0] as number;
56
+ const ay = a[1] as number;
57
+ const az = a[2] as number;
58
+ const aw = a[3] as number;
59
+ const bx = b[0] as number;
60
+ const by = b[1] as number;
61
+ const bz = b[2] as number;
62
+ const bw = b[3] as number;
63
+ if (
64
+ Number.isNaN(ax) ||
65
+ Number.isNaN(ay) ||
66
+ Number.isNaN(az) ||
67
+ Number.isNaN(aw) ||
68
+ Number.isNaN(bx) ||
69
+ Number.isNaN(by) ||
70
+ Number.isNaN(bz) ||
71
+ Number.isNaN(bw)
72
+ ) {
73
+ return false;
74
+ }
75
+ return (
76
+ Math.abs(ax - bx) <= epsilon &&
77
+ Math.abs(ay - by) <= epsilon &&
78
+ Math.abs(az - bz) <= epsilon &&
79
+ Math.abs(aw - bw) <= epsilon
80
+ );
81
+ }
82
+
83
+ /** out = a + b. aliasing-safe. */
84
+ export function add(out: Vec4, a: Vec4Like, b: Vec4Like): Vec4 {
85
+ const ax = a[0] as number;
86
+ const ay = a[1] as number;
87
+ const az = a[2] as number;
88
+ const aw = a[3] as number;
89
+ const bx = b[0] as number;
90
+ const by = b[1] as number;
91
+ const bz = b[2] as number;
92
+ const bw = b[3] as number;
93
+ out[0] = ax + bx;
94
+ out[1] = ay + by;
95
+ out[2] = az + bz;
96
+ out[3] = aw + bw;
97
+ return out;
98
+ }
99
+
100
+ /** out = a - b. aliasing-safe. */
101
+ export function sub(out: Vec4, a: Vec4Like, b: Vec4Like): Vec4 {
102
+ const ax = a[0] as number;
103
+ const ay = a[1] as number;
104
+ const az = a[2] as number;
105
+ const aw = a[3] as number;
106
+ const bx = b[0] as number;
107
+ const by = b[1] as number;
108
+ const bz = b[2] as number;
109
+ const bw = b[3] as number;
110
+ out[0] = ax - bx;
111
+ out[1] = ay - by;
112
+ out[2] = az - bz;
113
+ out[3] = aw - bw;
114
+ return out;
115
+ }
116
+
117
+ /** out = a * s. */
118
+ export function scale(out: Vec4, a: Vec4Like, s: number): Vec4 {
119
+ out[0] = (a[0] as number) * s;
120
+ out[1] = (a[1] as number) * s;
121
+ out[2] = (a[2] as number) * s;
122
+ out[3] = (a[3] as number) * s;
123
+ return out;
124
+ }
125
+
126
+ /** out = -a. The `0 - x` form avoids -0. */
127
+ export function negate(out: Vec4, a: Vec4Like): Vec4 {
128
+ out[0] = 0 - (a[0] as number);
129
+ out[1] = 0 - (a[1] as number);
130
+ out[2] = 0 - (a[2] as number);
131
+ out[3] = 0 - (a[3] as number);
132
+ return out;
133
+ }
134
+
135
+ /** Dot product a · b (4 components). */
136
+ export function dot(a: Vec4Like, b: Vec4Like): number {
137
+ return (
138
+ (a[0] as number) * (b[0] as number) +
139
+ (a[1] as number) * (b[1] as number) +
140
+ (a[2] as number) * (b[2] as number) +
141
+ (a[3] as number) * (b[3] as number)
142
+ );
143
+ }
144
+
145
+ /** Squared length |a|² (avoids sqrt overhead). */
146
+ export function lengthSq(a: Vec4Like): number {
147
+ return lengthSq4(a);
148
+ }
149
+
150
+ /** Euclidean length |a|. */
151
+ export function length(a: Vec4Like): number {
152
+ return Math.sqrt(lengthSq4(a));
153
+ }
154
+
155
+ /** Distance between two points |a - b|. */
156
+ export function distance(a: Vec4Like, b: Vec4Like): number {
157
+ const dx = (a[0] as number) - (b[0] as number);
158
+ const dy = (a[1] as number) - (b[1] as number);
159
+ const dz = (a[2] as number) - (b[2] as number);
160
+ const dw = (a[3] as number) - (b[3] as number);
161
+ return Math.sqrt(dx * dx + dy * dy + dz * dz + dw * dw);
162
+ }
163
+
164
+ /**
165
+ * out = a / |a| (unit-length).
166
+ *
167
+ * @degrade Zero vector (|a|² < EPS_NORMALIZE) silently falls back to the zero vector;
168
+ * no NaN, no throw (gl-matrix style, AC-06 / D-P12).
169
+ * @degrade NaN inputs → NaN outputs: when a component is NaN, lenSq=NaN; the `lenSq < EPS`
170
+ * branch is false → goes through 1/sqrt(NaN)=NaN → output is all NaN (IEEE-754 NaN propagation;
171
+ * still does not throw).
172
+ *
173
+ * @example
174
+ * ```ts
175
+ * vec4.normalize(out, [1, 2, 2, 0]); // → (1/3, 2/3, 2/3, 0)
176
+ * vec4.normalize(out, [0, 0, 0, 0]); // → (0, 0, 0, 0) zero vector falls back (AC-06 no throw)
177
+ * vec4.normalize(out, [NaN, 1, 0, 0]); // → all NaN, IEEE-754 propagation
178
+ * ```
179
+ */
180
+ export function normalize(out: Vec4, a: Vec4Like): Vec4 {
181
+ normalize4(out, a, EPS_NORMALIZE);
182
+ return out;
183
+ }
184
+
185
+ /** out = lerp(a, b, t). t is not clamped (extrapolation semantics). aliasing-safe. */
186
+ export function lerp(out: Vec4, a: Vec4Like, b: Vec4Like, t: number): Vec4 {
187
+ const ax = a[0] as number;
188
+ const ay = a[1] as number;
189
+ const az = a[2] as number;
190
+ const aw = a[3] as number;
191
+ const bx = b[0] as number;
192
+ const by = b[1] as number;
193
+ const bz = b[2] as number;
194
+ const bw = b[3] as number;
195
+ out[0] = scalarLerp(ax, bx, t);
196
+ out[1] = scalarLerp(ay, by, t);
197
+ out[2] = scalarLerp(az, bz, t);
198
+ out[3] = scalarLerp(aw, bw, t);
199
+ return out;
200
+ }
201
+
202
+ /**
203
+ * out = frame-rate-INDEPENDENT exponential smoothing of `current` toward `target`:
204
+ * `lerp(current, target, 1 − exp(−decayRate · dt))`. Semantics match Bevy
205
+ * `StableInterpolate::smooth_nudge` / three.js `MathUtils.damp` (1st-order decay, NOT Unity
206
+ * `SmoothDamp`'s 2nd-order spring). `dt=0` → out=current; large `decayRate·dt` → out≈target.
207
+ * Frame-rate independent by construction (one `dt` step = two composed `dt/2` steps). aliasing-safe.
208
+ * See vec3.smoothDamp for the full contract. */
209
+ export function smoothDamp(
210
+ out: Vec4,
211
+ current: Vec4Like,
212
+ target: Vec4Like,
213
+ decayRate: number,
214
+ dt: number,
215
+ ): Vec4 {
216
+ return lerp(out, current, target, smoothDecayFactor(decayRate, dt));
217
+ }
218
+
219
+ /** Component-wise min. */
220
+ export function min(out: Vec4, a: Vec4Like, b: Vec4Like): Vec4 {
221
+ out[0] = Math.min(a[0] as number, b[0] as number);
222
+ out[1] = Math.min(a[1] as number, b[1] as number);
223
+ out[2] = Math.min(a[2] as number, b[2] as number);
224
+ out[3] = Math.min(a[3] as number, b[3] as number);
225
+ return out;
226
+ }
227
+
228
+ /** Component-wise max. */
229
+ export function max(out: Vec4, a: Vec4Like, b: Vec4Like): Vec4 {
230
+ out[0] = Math.max(a[0] as number, b[0] as number);
231
+ out[1] = Math.max(a[1] as number, b[1] as number);
232
+ out[2] = Math.max(a[2] as number, b[2] as number);
233
+ out[3] = Math.max(a[3] as number, b[3] as number);
234
+ return out;
235
+ }