@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/dist/vec2.d.ts ADDED
@@ -0,0 +1,76 @@
1
+ import type { Vec2, Vec2Like } from './types';
2
+ /** Create a Vec2 (zero vector by default). */
3
+ export declare function create(x?: number, y?: number): Vec2;
4
+ /** Allocate a new Vec2 copy. Difference from copy: clone allocates, copy writes into an existing out. */
5
+ export declare function clone(a: Vec2Like): Vec2;
6
+ /** out = a. Returns out. aliasing-safe (copy(v, v) is a no-op). */
7
+ export declare function copy(out: Vec2, a: Vec2Like): Vec2;
8
+ /** Write the components and return out. */
9
+ export declare function set(out: Vec2, x: number, y: number): Vec2;
10
+ /**
11
+ * Approximate equality: every component differs by ≤ epsilon. NaN inputs always return false (matches IEEE 754).
12
+ */
13
+ export declare function equals(a: Vec2Like, b: Vec2Like, epsilon?: number): boolean;
14
+ /** out = a + b. aliasing-safe. */
15
+ export declare function add(out: Vec2, a: Vec2Like, b: Vec2Like): Vec2;
16
+ /** out = a - b. aliasing-safe. */
17
+ export declare function sub(out: Vec2, a: Vec2Like, b: Vec2Like): Vec2;
18
+ /** out = a * s. */
19
+ export declare function scale(out: Vec2, a: Vec2Like, s: number): Vec2;
20
+ /** out = -a. The `0 - x` form avoids -0 (test toBe(0) uses Object.is and distinguishes ±0). */
21
+ export declare function negate(out: Vec2, a: Vec2Like): Vec2;
22
+ /** Dot product a · b. */
23
+ export declare function dot(a: Vec2Like, b: Vec2Like): number;
24
+ /** Squared length |a|² (avoids sqrt overhead; useful on hot paths comparing distances). */
25
+ export declare function lengthSq(a: Vec2Like): number;
26
+ /** Euclidean length |a|. */
27
+ export declare function length(a: Vec2Like): number;
28
+ /** Distance between two points |a - b|. */
29
+ export declare function distance(a: Vec2Like, b: Vec2Like): number;
30
+ /**
31
+ * out = a / |a| (unit-length).
32
+ *
33
+ * @degrade Zero vector (|a|² < EPS_NORMALIZE) silently falls back to the zero vector;
34
+ * no NaN, no throw (gl-matrix style, AC-06 / D-P12).
35
+ * @degrade NaN inputs → NaN outputs: when a component is NaN, lenSq=NaN; the `lenSq < EPS`
36
+ * branch is false → goes through 1/sqrt(NaN)=NaN → output is all NaN (IEEE-754 NaN propagation;
37
+ * still does not throw).
38
+ *
39
+ * @example
40
+ * ```ts
41
+ * vec2.normalize(out, [3, 4]); // → (0.6, 0.8)
42
+ * vec2.normalize(out, [0, 0]); // → (0, 0) zero vector falls back (no NaN; AC-06 no throw)
43
+ * vec2.normalize(out, [NaN, 1]); // → (NaN, NaN) IEEE-754 propagation
44
+ * ```
45
+ */
46
+ export declare function normalize(out: Vec2, a: Vec2Like): Vec2;
47
+ /** out = lerp(a, b, t). t is not clamped (extrapolation semantics matches glam / wgpu-matrix). aliasing-safe. */
48
+ export declare function lerp(out: Vec2, a: Vec2Like, b: Vec2Like, t: number): Vec2;
49
+ /**
50
+ * out = frame-rate-INDEPENDENT exponential smoothing of `current` toward `target`:
51
+ * `lerp(current, target, 1 − exp(−decayRate · dt))`. Semantics match Bevy
52
+ * `StableInterpolate::smooth_nudge` / three.js `MathUtils.damp` (1st-order decay, NOT Unity
53
+ * `SmoothDamp`'s 2nd-order spring). `dt=0` → out=current; large `decayRate·dt` → out≈target.
54
+ * Frame-rate independent by construction (one `dt` step = two composed `dt/2` steps). aliasing-safe.
55
+ * See vec3.smoothDamp for the full contract. */
56
+ export declare function smoothDamp(out: Vec2, current: Vec2Like, target: Vec2Like, decayRate: number, dt: number): Vec2;
57
+ /**
58
+ * out = Catmull-Rom spline point on the segment between `p1` and `p2`, with `p0` / `p3` the
59
+ * neighbor control points setting the endpoint tangents (tension 0.5 — the Bevy
60
+ * `CubicCardinalSpline::new_catmull_rom` / three.js default). Interpolates the control points
61
+ * (`t=0` → `p1`, `t=1` → `p2`). See vec3.catmullRom for the full contract + whole-polyline
62
+ * sampling. aliasing-safe. */
63
+ export declare function catmullRom(out: Vec2, p0: Vec2Like, p1: Vec2Like, p2: Vec2Like, p3: Vec2Like, t: number): Vec2;
64
+ /** Component-wise min. */
65
+ export declare function min(out: Vec2, a: Vec2Like, b: Vec2Like): Vec2;
66
+ /** Component-wise max. */
67
+ export declare function max(out: Vec2, a: Vec2Like, b: Vec2Like): Vec2;
68
+ /**
69
+ * out = perp(a) — 2D counter-clockwise 90° rotation: (x, y) → (-y, x).
70
+ *
71
+ * Derives from cross(a, b) reducing to a scalar determinant in 2D; this API serves hot paths
72
+ * (collision / normals).
73
+ * aliasing-safe: reads a's components into locals before writing out.
74
+ */
75
+ export declare function perp(out: Vec2, a: Vec2Like): Vec2;
76
+ //# sourceMappingURL=vec2.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vec2.d.ts","sourceRoot":"","sources":["../src/vec2.ts"],"names":[],"mappings":"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"}
package/dist/vec3.d.ts ADDED
@@ -0,0 +1,82 @@
1
+ import type { Vec3, Vec3Like } from './types';
2
+ export type { Vec3, Vec3Like };
3
+ /** Create a Vec3 (zero vector by default). */
4
+ export declare function create(x?: number, y?: number, z?: number): Vec3;
5
+ /** Allocate a new Vec3 copy. */
6
+ export declare function clone(a: Vec3Like): Vec3;
7
+ /** out = a. aliasing-safe (copy(v, v) is a no-op). */
8
+ export declare function copy(out: Vec3, a: Vec3Like): Vec3;
9
+ /** Write the components and return out. */
10
+ export declare function set(out: Vec3, x: number, y: number, z: number): Vec3;
11
+ /**
12
+ * Approximate equality: every component differs by ≤ epsilon. NaN inputs always return false (IEEE 754).
13
+ */
14
+ export declare function equals(a: Vec3Like, b: Vec3Like, epsilon?: number): boolean;
15
+ /** out = a + b. aliasing-safe. */
16
+ export declare function add(out: Vec3, a: Vec3Like, b: Vec3Like): Vec3;
17
+ /** out = a - b. aliasing-safe. */
18
+ export declare function sub(out: Vec3, a: Vec3Like, b: Vec3Like): Vec3;
19
+ /** out = a * s. */
20
+ export declare function scale(out: Vec3, a: Vec3Like, s: number): Vec3;
21
+ /** out = -a. */
22
+ export declare function negate(out: Vec3, a: Vec3Like): Vec3;
23
+ /** Dot product a · b. */
24
+ export declare function dot(a: Vec3Like, b: Vec3Like): number;
25
+ /** out = a × b (cross product). aliasing-safe (reads all 6 components into locals first). */
26
+ export declare function cross(out: Vec3, a: Vec3Like, b: Vec3Like): Vec3;
27
+ /** Squared length |a|² (avoids sqrt overhead). */
28
+ export declare function lengthSq(a: Vec3Like): number;
29
+ /** Euclidean length |a|. */
30
+ export declare function length(a: Vec3Like): number;
31
+ /** Distance between two points |a - b|. */
32
+ export declare function distance(a: Vec3Like, b: Vec3Like): number;
33
+ /** Squared distance between two points |a - b|² (avoids sqrt overhead). */
34
+ export declare function distanceSq(a: Vec3Like, b: Vec3Like): number;
35
+ /**
36
+ * out = a / |a| (unit-length).
37
+ *
38
+ * @degrade Zero vector (|a|² < EPS_NORMALIZE) silently falls back to the zero vector;
39
+ * no NaN, no throw (gl-matrix style, AC-06 / D-P12).
40
+ * @degrade NaN inputs → NaN outputs: when a component is NaN, lenSq=NaN; the `lenSq < EPS`
41
+ * branch is false → goes through 1/sqrt(NaN)=NaN → output is all NaN (IEEE-754 NaN propagation;
42
+ * still does not throw).
43
+ *
44
+ * @example
45
+ * ```ts
46
+ * vec3.normalize(out, [3, 0, 4]); // → (0.6, 0, 0.8)
47
+ * vec3.normalize(out, [0, 0, 0]); // → (0, 0, 0) zero vector falls back (AC-06 no throw)
48
+ * vec3.normalize(out, [NaN, 1, 0]); // → (NaN, NaN, NaN) IEEE-754 propagation
49
+ * ```
50
+ */
51
+ export declare function normalize(out: Vec3, a: Vec3Like): Vec3;
52
+ /** out = lerp(a, b, t). t is not clamped (extrapolation semantics). aliasing-safe. */
53
+ export declare function lerp(out: Vec3, a: Vec3Like, b: Vec3Like, t: number): Vec3;
54
+ /**
55
+ * out = frame-rate-INDEPENDENT exponential smoothing of `current` toward `target`:
56
+ * `lerp(current, target, 1 − exp(−decayRate · dt))`. Semantics match Bevy
57
+ * `StableInterpolate::smooth_nudge` / three.js `MathUtils.damp` — NOT Unity `Vector3.SmoothDamp`
58
+ * (which is a 2nd-order critically-damped spring needing a velocity ref; this is 1st-order decay).
59
+ *
60
+ * `decayRate` is the exponential decay constant (1/s), meant to stay fixed while `dt` is per-frame;
61
+ * a good anchor is `decayRate = ln(2)/halfLife`. `dt=0` → out=current (no move); large `decayRate·dt`
62
+ * → out≈target; `decayRate=0` → out=current. Frame-rate independent by construction: one step of `dt`
63
+ * equals two composed steps of `dt/2` — unlike the naive `lerp(current, target, rate·dt)`, which
64
+ * behaves differently per frame rate and overshoots when `rate·dt > 1`. aliasing-safe.
65
+ */
66
+ export declare function smoothDamp(out: Vec3, current: Vec3Like, target: Vec3Like, decayRate: number, dt: number): Vec3;
67
+ /**
68
+ * out = Catmull-Rom spline point on the segment between `p1` and `p2`, with `p0` / `p3` the
69
+ * neighbor control points setting the endpoint tangents (tension 0.5 — the Bevy
70
+ * `CubicCardinalSpline::new_catmull_rom` / three.js `CatmullRomCurve3` default). Interpolates
71
+ * the control points: `t=0` → `p1`, `t=1` → `p2`. Unlike `lerp` (a straight segment), this is
72
+ * the smooth cubic through the points — use it for camera paths, animation ease paths, or
73
+ * procedural curve geometry. To sample a whole polyline, loop the segments with a sliding
74
+ * 4-point window `[pts[i-1], pts[i], pts[i+1], pts[i+2]]` (clamp/duplicate ends). `t` is not
75
+ * clamped (extrapolation follows the same cubic). aliasing-safe.
76
+ */
77
+ export declare function catmullRom(out: Vec3, p0: Vec3Like, p1: Vec3Like, p2: Vec3Like, p3: Vec3Like, t: number): Vec3;
78
+ /** Component-wise min. */
79
+ export declare function min(out: Vec3, a: Vec3Like, b: Vec3Like): Vec3;
80
+ /** Component-wise max. */
81
+ export declare function max(out: Vec3, a: Vec3Like, b: Vec3Like): Vec3;
82
+ //# sourceMappingURL=vec3.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vec3.d.ts","sourceRoot":"","sources":["../src/vec3.ts"],"names":[],"mappings":"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"}
package/dist/vec4.d.ts ADDED
@@ -0,0 +1,61 @@
1
+ import type { Vec4, Vec4Like } from './types';
2
+ /** Create a Vec4 (zero vector by default). */
3
+ export declare function create(x?: number, y?: number, z?: number, w?: number): Vec4;
4
+ /** Allocate a new Vec4 copy. */
5
+ export declare function clone(a: Vec4Like): Vec4;
6
+ /** out = a. aliasing-safe (copy(v, v) is a no-op). */
7
+ export declare function copy(out: Vec4, a: Vec4Like): Vec4;
8
+ /** Write the components and return out. */
9
+ export declare function set(out: Vec4, x: number, y: number, z: number, w: number): Vec4;
10
+ /**
11
+ * Approximate equality: every component differs by ≤ epsilon. NaN inputs always return false (IEEE 754).
12
+ */
13
+ export declare function equals(a: Vec4Like, b: Vec4Like, epsilon?: number): boolean;
14
+ /** out = a + b. aliasing-safe. */
15
+ export declare function add(out: Vec4, a: Vec4Like, b: Vec4Like): Vec4;
16
+ /** out = a - b. aliasing-safe. */
17
+ export declare function sub(out: Vec4, a: Vec4Like, b: Vec4Like): Vec4;
18
+ /** out = a * s. */
19
+ export declare function scale(out: Vec4, a: Vec4Like, s: number): Vec4;
20
+ /** out = -a. The `0 - x` form avoids -0. */
21
+ export declare function negate(out: Vec4, a: Vec4Like): Vec4;
22
+ /** Dot product a · b (4 components). */
23
+ export declare function dot(a: Vec4Like, b: Vec4Like): number;
24
+ /** Squared length |a|² (avoids sqrt overhead). */
25
+ export declare function lengthSq(a: Vec4Like): number;
26
+ /** Euclidean length |a|. */
27
+ export declare function length(a: Vec4Like): number;
28
+ /** Distance between two points |a - b|. */
29
+ export declare function distance(a: Vec4Like, b: Vec4Like): number;
30
+ /**
31
+ * out = a / |a| (unit-length).
32
+ *
33
+ * @degrade Zero vector (|a|² < EPS_NORMALIZE) silently falls back to the zero vector;
34
+ * no NaN, no throw (gl-matrix style, AC-06 / D-P12).
35
+ * @degrade NaN inputs → NaN outputs: when a component is NaN, lenSq=NaN; the `lenSq < EPS`
36
+ * branch is false → goes through 1/sqrt(NaN)=NaN → output is all NaN (IEEE-754 NaN propagation;
37
+ * still does not throw).
38
+ *
39
+ * @example
40
+ * ```ts
41
+ * vec4.normalize(out, [1, 2, 2, 0]); // → (1/3, 2/3, 2/3, 0)
42
+ * vec4.normalize(out, [0, 0, 0, 0]); // → (0, 0, 0, 0) zero vector falls back (AC-06 no throw)
43
+ * vec4.normalize(out, [NaN, 1, 0, 0]); // → all NaN, IEEE-754 propagation
44
+ * ```
45
+ */
46
+ export declare function normalize(out: Vec4, a: Vec4Like): Vec4;
47
+ /** out = lerp(a, b, t). t is not clamped (extrapolation semantics). aliasing-safe. */
48
+ export declare function lerp(out: Vec4, a: Vec4Like, b: Vec4Like, t: number): Vec4;
49
+ /**
50
+ * out = frame-rate-INDEPENDENT exponential smoothing of `current` toward `target`:
51
+ * `lerp(current, target, 1 − exp(−decayRate · dt))`. Semantics match Bevy
52
+ * `StableInterpolate::smooth_nudge` / three.js `MathUtils.damp` (1st-order decay, NOT Unity
53
+ * `SmoothDamp`'s 2nd-order spring). `dt=0` → out=current; large `decayRate·dt` → out≈target.
54
+ * Frame-rate independent by construction (one `dt` step = two composed `dt/2` steps). aliasing-safe.
55
+ * See vec3.smoothDamp for the full contract. */
56
+ export declare function smoothDamp(out: Vec4, current: Vec4Like, target: Vec4Like, decayRate: number, dt: number): Vec4;
57
+ /** Component-wise min. */
58
+ export declare function min(out: Vec4, a: Vec4Like, b: Vec4Like): Vec4;
59
+ /** Component-wise max. */
60
+ export declare function max(out: Vec4, a: Vec4Like, b: Vec4Like): Vec4;
61
+ //# sourceMappingURL=vec4.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vec4.d.ts","sourceRoot":"","sources":["../src/vec4.ts"],"names":[],"mappings":"AAoBA,OAAO,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAE9C,8CAA8C;AAC9C,wBAAgB,MAAM,CAAC,CAAC,SAAI,EAAE,CAAC,SAAI,EAAE,CAAC,SAAI,EAAE,CAAC,SAAI,GAAG,IAAI,CAEvD;AAED,gCAAgC;AAChC,wBAAgB,KAAK,CAAC,CAAC,EAAE,QAAQ,GAAG,IAAI,CAEvC;AAED,sDAAsD;AACtD,wBAAgB,IAAI,CAAC,GAAG,EAAE,IAAI,EAAE,CAAC,EAAE,QAAQ,GAAG,IAAI,CAMjD;AAED,2CAA2C;AAC3C,wBAAgB,GAAG,CAAC,GAAG,EAAE,IAAI,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,IAAI,CAM/E;AAED;;GAEG;AACH,wBAAgB,MAAM,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,QAAQ,EAAE,OAAO,SAAO,GAAG,OAAO,CA2BxE;AAED,kCAAkC;AAClC,wBAAgB,GAAG,CAAC,GAAG,EAAE,IAAI,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,QAAQ,GAAG,IAAI,CAc7D;AAED,kCAAkC;AAClC,wBAAgB,GAAG,CAAC,GAAG,EAAE,IAAI,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,QAAQ,GAAG,IAAI,CAc7D;AAED,mBAAmB;AACnB,wBAAgB,KAAK,CAAC,GAAG,EAAE,IAAI,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,MAAM,GAAG,IAAI,CAM7D;AAED,4CAA4C;AAC5C,wBAAgB,MAAM,CAAC,GAAG,EAAE,IAAI,EAAE,CAAC,EAAE,QAAQ,GAAG,IAAI,CAMnD;AAED,wCAAwC;AACxC,wBAAgB,GAAG,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,QAAQ,GAAG,MAAM,CAOpD;AAED,kDAAkD;AAClD,wBAAgB,QAAQ,CAAC,CAAC,EAAE,QAAQ,GAAG,MAAM,CAE5C;AAED,4BAA4B;AAC5B,wBAAgB,MAAM,CAAC,CAAC,EAAE,QAAQ,GAAG,MAAM,CAE1C;AAED,2CAA2C;AAC3C,wBAAgB,QAAQ,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,QAAQ,GAAG,MAAM,CAMzD;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,SAAS,CAAC,GAAG,EAAE,IAAI,EAAE,CAAC,EAAE,QAAQ,GAAG,IAAI,CAGtD;AAED,sFAAsF;AACtF,wBAAgB,IAAI,CAAC,GAAG,EAAE,IAAI,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,MAAM,GAAG,IAAI,CAczE;AAED;;;;;;gDAMgD;AAChD,wBAAgB,UAAU,CACxB,GAAG,EAAE,IAAI,EACT,OAAO,EAAE,QAAQ,EACjB,MAAM,EAAE,QAAQ,EAChB,SAAS,EAAE,MAAM,EACjB,EAAE,EAAE,MAAM,GACT,IAAI,CAEN;AAED,0BAA0B;AAC1B,wBAAgB,GAAG,CAAC,GAAG,EAAE,IAAI,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,QAAQ,GAAG,IAAI,CAM7D;AAED,0BAA0B;AAC1B,wBAAgB,GAAG,CAAC,GAAG,EAAE,IAAI,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,QAAQ,GAAG,IAAI,CAM7D"}
package/package.json ADDED
@@ -0,0 +1,57 @@
1
+ {
2
+ "name": "@forgeax/engine-math",
3
+ "version": "0.0.0-dev.8d955ade1c79",
4
+ "private": false,
5
+ "type": "module",
6
+ "license": "Apache-2.0",
7
+ "sideEffects": false,
8
+ "description": "Pure-function SoA-friendly Vec/Mat/Quat math utilities for forgeax-engine.",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.mjs"
13
+ },
14
+ "./package.json": "./package.json"
15
+ },
16
+ "main": "./dist/index.mjs",
17
+ "types": "./dist/index.d.ts",
18
+ "files": [
19
+ "dist",
20
+ "src",
21
+ "README.md",
22
+ "LICENSE"
23
+ ],
24
+ "forgeax": {
25
+ "metrics": {
26
+ "bundle-size": {
27
+ "enabled": true,
28
+ "path": "dist/index.mjs",
29
+ "compression": "gzip"
30
+ },
31
+ "fps": {
32
+ "enabled": false,
33
+ "reason": "library package, no runtime canvas; fps reported by hello-triangle / hello-cube apps"
34
+ },
35
+ "bench": {
36
+ "enabled": false,
37
+ "reason": "pure math micro-benchmarks rarely regress; removed from CI to save 15s runner-minutes"
38
+ },
39
+ "gate": {
40
+ "enabled": false,
41
+ "reason": "no package-level binary gate; smoke gate covered by hello-triangle / hello-cube apps"
42
+ },
43
+ "spike-report": {
44
+ "enabled": false,
45
+ "reason": "not a spike package; spike artefacts archived in feat-20260511-naga-rhi-wgpu-merge M5 (PR #34 + git history)"
46
+ }
47
+ }
48
+ },
49
+ "scripts": {
50
+ "build": "tsup",
51
+ "test": "vitest run",
52
+ "bench": "vitest bench --run",
53
+ "bench:json": "FORGEAX_BENCH=fast vitest bench --run --outputJson bench-result.json",
54
+ "lint:brand-cast": "node scripts/lint-brand-cast.mjs",
55
+ "lint:brand-runtime": "node scripts/lint-brand-runtime.mjs"
56
+ }
57
+ }
@@ -0,0 +1,149 @@
1
+ // _arbs.ts — fast-check arbitrary factories + ε-tolerance comparison helpers (M5 / T-031)
2
+ //
3
+ // Shared by the three property files vec3.property.test.ts / mat4.property.test.ts /
4
+ // quat.property.test.ts. The leading underscore prevents vitest's default glob
5
+ // `**/*.test.ts` from running this file as a test (source: wiki/fast-check-property-test §7.1).
6
+ //
7
+ // Design anchors:
8
+ // - safeFloat: noNaN + noDefaultInfinity, prevents NaN from making ε comparisons
9
+ // spuriously pass/fail (wiki §3.1 + §8 anti-pattern #1)
10
+ // - vec3Arb / vec4Arb: same shape as the Float32Array.of branded factories (packages/math/src/vec3.ts)
11
+ // - unitVec3Arb: spherical sampling, filter hit rate > 99% (len² > 1e-12 almost always)
12
+ // - unitQuatArb: 4D-sphere Marsaglia / generic normalization + filter, 4D unit Hopf
13
+ // - invertibleMat4Arb: composed from TRS rather than 16 random elements (wiki §3.3)
14
+ // - approxEq / vec3ApproxEq / mat4ApproxEq / quatApproxEq: ε-tolerance helpers,
15
+ // EPS chosen from wiki §4.4 recommendation table (annotated inline below)
16
+ //
17
+ // brand cast: this file lives under packages/math/src/__tests__/, which is inside
18
+ // lint-brand-cast.mjs's allow-list (the packages/math/src/ prefix is OK).
19
+ //
20
+ // Related: requirements §AC-11 property ≥ 12 / ≥ 5 categories / ≤ 4 per category;
21
+ // plan-strategy D-P5 numRuns default 100;
22
+ // wiki/fast-check-property-test §3 / §4 / §5 / §7.
23
+
24
+ import { fc } from '@fast-check/vitest';
25
+ import * as mat4 from '../mat4';
26
+ import type { Mat4, Quat, Vec3, Vec4 } from '../types';
27
+
28
+ // ---------- ε constants (wiki §4.4 recommendation table) ----------
29
+
30
+ /** Float32 single add/sub; ULP ≈ 1.19e-7, leaves an 8× margin. */
31
+ export const EPS_F32 = 1e-5;
32
+
33
+ /** Float32 mat4 × mat4: chained accumulation of 16 muls + 12 adds. */
34
+ export const EPS_MAT4_MUL = 1e-4;
35
+
36
+ /** Float32 mat4 × mat4 × mat4: associativity test accumulates 3 multiplications on each side. */
37
+ export const EPS_MAT4_MUL3 = 1e-3;
38
+
39
+ /** Quat slerp endpoint / single-step numeric path. */
40
+ export const EPS_QUAT = 1e-5;
41
+
42
+ // ---------- single-value generators ----------
43
+
44
+ /** General 32-bit safe scalar: noNaN + noDefaultInfinity, lies in [-1e3, 1e3]. */
45
+ export const safeFloat = (min = -1e3, max = 1e3): fc.Arbitrary<number> =>
46
+ fc.float({
47
+ noNaN: true,
48
+ noDefaultInfinity: true,
49
+ // fast-check 4.x: min/max must be 32-bit representable, otherwise it throws
50
+ min: Math.fround(min),
51
+ max: Math.fround(max),
52
+ });
53
+
54
+ /** Safe scalar for matrix elements: [-10, 10], prevents mat4×mat4 element values from inflating ε. */
55
+ export const matFloat = (): fc.Arbitrary<number> =>
56
+ fc.float({
57
+ noNaN: true,
58
+ noDefaultInfinity: true,
59
+ min: Math.fround(-10),
60
+ max: Math.fround(10),
61
+ });
62
+
63
+ /** Scale component: keeps away from 0 to avoid singularities ([1e-2, 10] ∪ [-10, -1e-2]). */
64
+ export const nonZeroScale = (): fc.Arbitrary<number> =>
65
+ fc.oneof(
66
+ fc.float({
67
+ noNaN: true,
68
+ noDefaultInfinity: true,
69
+ min: Math.fround(1e-2),
70
+ max: Math.fround(10),
71
+ }),
72
+ fc.float({
73
+ noNaN: true,
74
+ noDefaultInfinity: true,
75
+ min: Math.fround(-10),
76
+ max: Math.fround(-1e-2),
77
+ }),
78
+ );
79
+
80
+ // ---------- Float32Array vectors ----------
81
+
82
+ /** Generic vec3: three independent safeFloat components. */
83
+ export const vec3Arb = (): fc.Arbitrary<Vec3> =>
84
+ fc
85
+ .tuple(safeFloat(), safeFloat(), safeFloat())
86
+ .map(([x, y, z]) => Float32Array.of(x, y, z) as Vec3);
87
+
88
+ /** Generic vec4. */
89
+ export const vec4Arb = (): fc.Arbitrary<Vec4> =>
90
+ fc
91
+ .tuple(safeFloat(), safeFloat(), safeFloat(), safeFloat())
92
+ .map(([x, y, z, w]) => Float32Array.of(x, y, z, w) as Vec4);
93
+
94
+ /** Non-zero vec3 (len² > 1e-6 to avoid normalize fall-back). */
95
+ export const nonZeroVec3Arb = (): fc.Arbitrary<Vec3> =>
96
+ vec3Arb().map((v) => {
97
+ const x = v[0] as number;
98
+ const y = v[1] as number;
99
+ const z = v[2] as number;
100
+ const len2 = x * x + y * y + z * z;
101
+ return len2 < 1e-6 ? (Float32Array.of(1, 0, 0) as Vec3) : v;
102
+ });
103
+
104
+ /** Scale vector: each component nonZeroScale, used by TRS compose. */
105
+ export const scaleVec3Arb = (): fc.Arbitrary<Vec3> =>
106
+ fc
107
+ .tuple(nonZeroScale(), nonZeroScale(), nonZeroScale())
108
+ .map(([x, y, z]) => Float32Array.of(x, y, z) as Vec3);
109
+
110
+ /** Unit vec3: normalized non-zero vec3. */
111
+ export const unitVec3Arb = (): fc.Arbitrary<Vec3> =>
112
+ nonZeroVec3Arb().map((v) => {
113
+ const x = v[0] as number;
114
+ const y = v[1] as number;
115
+ const z = v[2] as number;
116
+ const inv = 1 / Math.hypot(x, y, z);
117
+ return Float32Array.of(x * inv, y * inv, z * inv) as Vec3;
118
+ });
119
+
120
+ // ---------- unit quaternion ----------
121
+
122
+ /** Unit quaternion: 4D-sphere sampling (filter hit rate ≈ 1 since random 4D points have len² > 1e-12). */
123
+ export const unitQuatArb = (): fc.Arbitrary<Quat> =>
124
+ fc
125
+ .tuple(safeFloat(-1, 1), safeFloat(-1, 1), safeFloat(-1, 1), safeFloat(-1, 1))
126
+ .map(([x, y, z, w]) => {
127
+ const len2 = x * x + y * y + z * z + w * w;
128
+ if (len2 < 1e-12) return Float32Array.of(0, 0, 0, 1) as Quat;
129
+ const inv = 1 / Math.sqrt(len2);
130
+ return Float32Array.of(x * inv, y * inv, z * inv, w * inv) as Quat;
131
+ });
132
+
133
+ // ---------- Mat4 ----------
134
+
135
+ /** Generic mat4: 16 independent elements ∈ [-10, 10], associativity / identity friendly. */
136
+ export const mat4Arb = (): fc.Arbitrary<Mat4> =>
137
+ fc
138
+ .array(matFloat(), { minLength: 16, maxLength: 16 })
139
+ .map((arr) => Float32Array.from(arr) as Mat4);
140
+
141
+ /**
142
+ * Invertible mat4: composed from TRS (translation × rotation × scale) so det ≠ 0 is guaranteed.
143
+ * Random 16 elements are almost always singular, invert returns identity → invertibility test always fails.
144
+ */
145
+ export const invertibleMat4Arb = (): fc.Arbitrary<Mat4> =>
146
+ fc.tuple(vec3Arb(), unitQuatArb(), scaleVec3Arb()).map(([t, r, s]) => {
147
+ const out = mat4.create();
148
+ return mat4.compose(out, t, r, s);
149
+ });
@@ -0,0 +1,118 @@
1
+ // _fixtures.ts — reversed-Z double-precision reference matrices + projection-correctness probe table (T-019, AC-05)
2
+ //
3
+ // Reference SSOT: `.forgeax-harness/knowledge-base/wiki/reversed-z-projection.md` §7.2 / §7.3 / §7.4.
4
+ // Numbers are computed in IEEE-754 float64 (cross-checked against Python's `math` stdlib);
5
+ // this library's Float32Array implementation matches within 1e-5 tolerance.
6
+ //
7
+ // Related: requirements §AC-05 reversed-Z fixture error ≤ 1e-5;
8
+ // research §Finding 6.2 double-precision reference fixture (copied verbatim);
9
+ // wiki/reversed-z-projection.md §7.2 finite + §7.3 infinite + §7.4 projection probes.
10
+
11
+ /** AC-05 fixed input: fovy=π/4, aspect=16/9, near=0.1, far=100. */
12
+ export const PERSPECTIVE_REVERSE_Z_FINITE_INPUT = {
13
+ fovy: Math.PI / 4,
14
+ aspect: 16 / 9,
15
+ near: 0.1,
16
+ far: 100,
17
+ } as const;
18
+
19
+ /**
20
+ * Finite reversed-Z expected matrix (column-major 16 elements, double-precision reference).
21
+ *
22
+ * Numeric derivation:
23
+ * m[0] = (1 / tan(π/8)) / (16/9) = 1.357995128834866
24
+ * m[5] = 1 / tan(π/8) = 2.414213562373095
25
+ * m[10] = near / (far - near) = 0.1 / 99.9 = 0.001001001001001001
26
+ * m[11] = -1
27
+ * m[14] = near * far / (far - near) = 0.10010010010010009
28
+ * m[15] = 0
29
+ */
30
+ export const PERSPECTIVE_REVERSE_Z_FINITE_EXPECTED: ReadonlyArray<number> = Object.freeze([
31
+ 1.357995128834866,
32
+ 0,
33
+ 0,
34
+ 0, // col 0
35
+ 0,
36
+ 2.414213562373095,
37
+ 0,
38
+ 0, // col 1
39
+ 0,
40
+ 0,
41
+ 0.001001001001001001,
42
+ -1, // col 2
43
+ 0,
44
+ 0,
45
+ 0.10010010010010009,
46
+ 0, // col 3
47
+ ]);
48
+
49
+ /**
50
+ * Infinite reversed-Z expected matrix (fovy=π/4, aspect=16/9, near=0.1, far=Infinity).
51
+ *
52
+ * lim(f→∞) m[10] = 0; lim(f→∞) m[14] = near = 0.1.
53
+ */
54
+ export const PERSPECTIVE_REVERSE_Z_INFINITE_EXPECTED: ReadonlyArray<number> = Object.freeze([
55
+ 1.357995128834866,
56
+ 0,
57
+ 0,
58
+ 0, // col 0
59
+ 0,
60
+ 2.414213562373095,
61
+ 0,
62
+ 0, // col 1
63
+ 0,
64
+ 0,
65
+ 0,
66
+ -1, // col 2
67
+ 0,
68
+ 0,
69
+ 0.1,
70
+ 0, // col 3
71
+ ]);
72
+
73
+ /**
74
+ * Projection-correctness check (reversed-Z finite, near=0.1, far=100):
75
+ * feed z_eye into mat4.perspectiveReverseZ and the table below should produce ndc_z.
76
+ *
77
+ * Derivation: p_clip = M @ [0, 0, z_eye, 1]^T; ndc_z = p_clip[2] / p_clip[3].
78
+ *
79
+ * From wiki §7.4 table (n=0.1, f=100):
80
+ * z_eye=-0.1 (near) → ndc_z = 1.0
81
+ * z_eye=-1.0 → ndc_z = 0.09909909909909909
82
+ * z_eye=-10.0 → ndc_z = 0.009009009009009009
83
+ * z_eye=-100.0 (far) → ndc_z = 0.0
84
+ */
85
+ export const REVERSE_Z_PROJECTION_PROBES_FINITE: ReadonlyArray<{ z_eye: number; ndc_z: number }> =
86
+ Object.freeze([
87
+ { z_eye: -0.1, ndc_z: 1.0 },
88
+ { z_eye: -1.0, ndc_z: 0.09909909909909909 },
89
+ { z_eye: -10.0, ndc_z: 0.009009009009009009 },
90
+ { z_eye: -100.0, ndc_z: 0.0 },
91
+ ]);
92
+
93
+ /**
94
+ * Projection-correctness check (reversed-Z infinite, near=0.1, far=Infinity):
95
+ *
96
+ * From wiki §7.4 table: infinite-far yields ndc_z = -near / z_eye = n / |z_eye| for any z_eye < 0.
97
+ * z_eye=-0.1 (near) → ndc_z = 1.0
98
+ * z_eye=-1.0 → ndc_z = 0.1
99
+ * z_eye=-10.0 → ndc_z = 0.01
100
+ * z_eye=-100.0 → ndc_z = 0.001
101
+ * z_eye=-10000.0 (far frustum out) → ndc_z = 1e-5
102
+ */
103
+ export const REVERSE_Z_PROJECTION_PROBES_INFINITE: ReadonlyArray<{
104
+ z_eye: number;
105
+ ndc_z: number;
106
+ }> = Object.freeze([
107
+ { z_eye: -0.1, ndc_z: 1.0 },
108
+ { z_eye: -1.0, ndc_z: 0.1 },
109
+ { z_eye: -10.0, ndc_z: 0.01 },
110
+ { z_eye: -100.0, ndc_z: 0.001 },
111
+ { z_eye: -10000.0, ndc_z: 1e-5 },
112
+ ]);
113
+
114
+ /**
115
+ * AC-05 tolerance: vitest `toBeCloseTo(expected, decimals)` with 5 decimals → ≤ 0.5e-5;
116
+ * float32 arithmetic stays within this tolerance vs the float64 reference.
117
+ */
118
+ export const REVERSE_Z_FIXTURE_TOLERANCE = 5;