@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
@@ -0,0 +1,138 @@
1
+ // quat.test-d.ts — branded return type + dimension-mix-up compile-time assertions (T-026)
2
+ //
3
+ // Locks quat.create / fromAxisAngle / fromEuler / slerp / nlerp / multiply / invert /
4
+ // conjugate / normalize / fromRotationMatrix / fromLookAt / fromUnitVectors to return Quat;
5
+ // dimension mix-ups (quat.multiply(out, vec3, vec3)) are ts-expect-error;
6
+ // EulerOrder union is locked to 6 exhaustive values; fromEuler(_, _, _, _, 'INVALID') is ts-error.
7
+ //
8
+ // Related: requirements §AC-12 type test + AC-02 brand mutual exclusion (≥ 3 quat contributions);
9
+ // plan-strategy §4.2 type test;
10
+ // wiki/typescript-branded-types §3.1 expectTypeOf template + §7.3 ts-expect-error.
11
+
12
+ import { describe, expectTypeOf, it } from 'vitest';
13
+ import { quat } from '../index';
14
+ import type { EulerOrder, Mat4, Quat, Vec3 } from '../types';
15
+
16
+ describe('quat — branded return-type locks', () => {
17
+ it('quat.create returns Quat', () => {
18
+ expectTypeOf(quat.create()).toEqualTypeOf<Quat>();
19
+ });
20
+
21
+ it('quat.identity returns Quat', () => {
22
+ expectTypeOf(quat.identity(quat.create())).toEqualTypeOf<Quat>();
23
+ });
24
+
25
+ it('quat.fromAxisAngle returns Quat', () => {
26
+ expectTypeOf(
27
+ quat.fromAxisAngle(quat.create(), [1, 0, 0] as unknown as Vec3, 1),
28
+ ).toEqualTypeOf<Quat>();
29
+ });
30
+
31
+ it('quat.fromEuler returns Quat', () => {
32
+ expectTypeOf(quat.fromEuler(quat.create(), 0, 0, 0, 'XYZ')).toEqualTypeOf<Quat>();
33
+ });
34
+
35
+ it('quat.multiply returns Quat', () => {
36
+ expectTypeOf(quat.multiply(quat.create(), quat.create(), quat.create())).toEqualTypeOf<Quat>();
37
+ });
38
+
39
+ it('quat.slerp returns Quat', () => {
40
+ expectTypeOf(
41
+ quat.slerp(quat.create(), quat.create(), quat.create(), 0.5),
42
+ ).toEqualTypeOf<Quat>();
43
+ });
44
+
45
+ it('quat.nlerp returns Quat', () => {
46
+ expectTypeOf(
47
+ quat.nlerp(quat.create(), quat.create(), quat.create(), 0.5),
48
+ ).toEqualTypeOf<Quat>();
49
+ });
50
+
51
+ it('quat.invert returns Quat', () => {
52
+ expectTypeOf(quat.invert(quat.create(), quat.create())).toEqualTypeOf<Quat>();
53
+ });
54
+
55
+ it('quat.conjugate returns Quat', () => {
56
+ expectTypeOf(quat.conjugate(quat.create(), quat.create())).toEqualTypeOf<Quat>();
57
+ });
58
+
59
+ it('quat.normalize returns Quat', () => {
60
+ expectTypeOf(quat.normalize(quat.create(), quat.create())).toEqualTypeOf<Quat>();
61
+ });
62
+
63
+ it('quat.fromLookAt returns Quat', () => {
64
+ expectTypeOf(
65
+ quat.fromLookAt(quat.create(), [0, 0, 5], [0, 0, 0], [0, 1, 0]),
66
+ ).toEqualTypeOf<Quat>();
67
+ });
68
+ });
69
+
70
+ describe('quat — cross-namespace misuse blocked at compile time (out brand lock)', () => {
71
+ it('quat.identity must reject Vec3 as out', () => {
72
+ const v: Vec3 = null as unknown as Vec3;
73
+ // @ts-expect-error — out must be Quat, not Vec3
74
+ quat.identity(v);
75
+ });
76
+
77
+ it('quat.multiply must reject Mat4 as out', () => {
78
+ const m: Mat4 = null as unknown as Mat4;
79
+ const q: Quat = quat.create();
80
+ // @ts-expect-error — out must be Quat, not Mat4
81
+ quat.multiply(m, q, q);
82
+ });
83
+
84
+ it('quat.slerp must reject Vec3 as out', () => {
85
+ const v: Vec3 = null as unknown as Vec3;
86
+ const q: Quat = quat.create();
87
+ // @ts-expect-error — out must be Quat, not Vec3
88
+ quat.slerp(v, q, q, 0.5);
89
+ });
90
+ });
91
+
92
+ describe('quat — EulerOrder union exhaustively locked to 6 values', () => {
93
+ it('EulerOrder contains the 6 entries XYZ / YXZ / ZXY / ZYX / YZX / XZY', () => {
94
+ expectTypeOf<EulerOrder>().toEqualTypeOf<'XYZ' | 'YXZ' | 'ZXY' | 'ZYX' | 'YZX' | 'XZY'>();
95
+ });
96
+
97
+ it('quat.fromEuler accepts every one of the 6 EulerOrder values', () => {
98
+ quat.fromEuler(quat.create(), 0, 0, 0, 'XYZ');
99
+ quat.fromEuler(quat.create(), 0, 0, 0, 'YXZ');
100
+ quat.fromEuler(quat.create(), 0, 0, 0, 'ZXY');
101
+ quat.fromEuler(quat.create(), 0, 0, 0, 'ZYX');
102
+ quat.fromEuler(quat.create(), 0, 0, 0, 'YZX');
103
+ quat.fromEuler(quat.create(), 0, 0, 0, 'XZY');
104
+ });
105
+
106
+ it('quat.fromEuler rejects string literals outside the union', () => {
107
+ // @ts-expect-error — 'INVALID' is not in the EulerOrder union
108
+ quat.fromEuler(quat.create(), 0, 0, 0, 'INVALID');
109
+ });
110
+ });
111
+
112
+ // M1 / t3 — quat.transformVec3 brand mutual exclusion + return type
113
+ //
114
+ // Related: requirements §6 AC-15 + §10.1 brand-safety row;
115
+ // research Finding 4 table: brand type mismatch → compile-time TS error;
116
+ // plan-strategy §4.3 key test points table row 6 + §7.1 brand safety.
117
+ describe('quat.transformVec3 — branded return type + brand mutual exclusion (M1 / t3)', () => {
118
+ it('quat.transformVec3 returns Vec3', () => {
119
+ const v: Vec3 = null as unknown as Vec3;
120
+ const q: Quat = quat.create();
121
+ expectTypeOf(quat.transformVec3(v, q, v)).toEqualTypeOf<Vec3>();
122
+ });
123
+
124
+ it('quat.transformVec3 must reject Quat as out (Vec3 ≠ Quat brand mutual exclusion)', () => {
125
+ const q: Quat = quat.create();
126
+ const v3: Vec3 = null as unknown as Vec3;
127
+ // @ts-expect-error — out must be Vec3, not Quat
128
+ quat.transformVec3(q, q, v3);
129
+ });
130
+
131
+ it('quat.transformVec3 must reject Mat4 as out (Vec3 ≠ Mat4 brand mutual exclusion)', () => {
132
+ const m4: Mat4 = null as unknown as Mat4;
133
+ const q: Quat = quat.create();
134
+ const v3: Vec3 = null as unknown as Vec3;
135
+ // @ts-expect-error — out must be Vec3, not Mat4
136
+ quat.transformVec3(m4, q, v3);
137
+ });
138
+ });
@@ -0,0 +1,105 @@
1
+ // ray.property.test.ts — fast-check round-trip property (feat-20260617-host-engine-contract-and-video-cutscene M2 w7)
2
+ //
3
+ // world -> screen -> world round-trip on a realistic view-projection (lookAt + perspective):
4
+ // screenToRay(worldToScreen(v)) yields a Ray that passes through v, for v inside the frustum.
5
+ //
6
+ // On-screen domain only: screenToRay clamps off-screen pixel coords back to the viewport edge
7
+ // (research Finding 6 / R-5), so off-screen points do not round-trip. We sample world points
8
+ // roughly inside the frustum the VP below describes, and assert onScreen before checking.
9
+ //
10
+ // EPS_MAT4_MUL3 = 1e-3 (two w-divides + a matrix invert; borrowed from mat4.property.test.ts:214-253).
11
+ // >= 100 samples (default numRuns).
12
+ //
13
+ // Related: requirements AC-02; plan-strategy §5.2 (R-5: arbitrary restricted to on-screen domain);
14
+ // research Finding 6 (screenToRay clamps screen coords).
15
+
16
+ import { fc, test } from '@fast-check/vitest';
17
+ import { describe, expect } from 'vitest';
18
+ import * as mat4 from '../mat4';
19
+ import * as ray from '../ray';
20
+ import type { Mat4, Vec2, Vec3 } from '../types';
21
+ import * as vec2 from '../vec2';
22
+ import { EPS_MAT4_MUL3 } from './_arbs';
23
+
24
+ const NUM_RUNS = Number.parseInt(
25
+ (globalThis as { process?: { env?: Record<string, string | undefined> } }).process?.env
26
+ ?.PROPERTY_NUM_RUNS ?? '100',
27
+ 10,
28
+ );
29
+
30
+ const CANVAS_W = 800;
31
+ const CANVAS_H = 600;
32
+ const FOV = Math.PI / 3;
33
+ const ASPECT = CANVAS_W / CANVAS_H;
34
+ const NEAR = 0.1;
35
+ const FAR = 100;
36
+
37
+ /** Build the reference forward-looking view-projection (camera at origin, looking down -z). */
38
+ function makeVP(): Mat4 {
39
+ const view = mat4.lookAt(mat4.create(), [0, 0, 0], [0, 0, -1], [0, 1, 0]);
40
+ const proj = mat4.perspective(mat4.create(), FOV, ASPECT, NEAR, FAR);
41
+ return mat4.multiply(mat4.create(), proj, view);
42
+ }
43
+
44
+ /** Distance from point p to the line defined by the ray (origin o, unit direction d). */
45
+ function pointToRayDistance(p: Vec3, r: ray.Ray): number {
46
+ const ox = r[0] as number;
47
+ const oy = r[1] as number;
48
+ const oz = r[2] as number;
49
+ const dx = r[3] as number;
50
+ const dy = r[4] as number;
51
+ const dz = r[5] as number;
52
+ const wx = (p[0] as number) - ox;
53
+ const wy = (p[1] as number) - oy;
54
+ const wz = (p[2] as number) - oz;
55
+ // perpendicular component = w - (w·d) d (d is unit length)
56
+ const t = wx * dx + wy * dy + wz * dz;
57
+ const px = wx - t * dx;
58
+ const py = wy - t * dy;
59
+ const pz = wz - t * dz;
60
+ return Math.sqrt(px * px + py * py + pz * pz);
61
+ }
62
+
63
+ describe('ray world->screen->world round-trip (AC-02)', () => {
64
+ test.prop(
65
+ {
66
+ // World points roughly inside the frustum the VP above describes (in front of camera).
67
+ v: fc
68
+ .tuple(
69
+ fc.float({ min: -2, max: 2, noNaN: true }),
70
+ fc.float({ min: -2, max: 2, noNaN: true }),
71
+ fc.float({ min: -8, max: -2, noNaN: true }),
72
+ )
73
+ .map(([x, y, z]) => Float32Array.of(x, y, z) as Vec3),
74
+ },
75
+ { numRuns: NUM_RUNS },
76
+ )('on-screen point lies on screenToRay(worldToScreen(v))', ({ v }) => {
77
+ const VP = makeVP();
78
+
79
+ const px = vec2.create() as Vec2;
80
+ const { onScreen, behind } = ray.worldToScreen(px, v, VP, CANVAS_W, CANVAS_H);
81
+
82
+ // Restrict the property to the on-screen domain: off-screen pixels are clamped by
83
+ // screenToRay and would not round-trip (R-5).
84
+ if (behind || !onScreen) return true;
85
+
86
+ const view = mat4.lookAt(mat4.create(), [0, 0, 0], [0, 0, -1], [0, 1, 0]);
87
+ const proj = mat4.perspective(mat4.create(), FOV, ASPECT, NEAR, FAR);
88
+ const r = ray.screenToRay(
89
+ ray.create(),
90
+ px[0] as number,
91
+ px[1] as number,
92
+ CANVAS_W,
93
+ CANVAS_H,
94
+ view,
95
+ proj,
96
+ 'perspective',
97
+ );
98
+
99
+ // v must lie on the recovered ray within the chained-mul tolerance.
100
+ return pointToRayDistance(v, r) < EPS_MAT4_MUL3;
101
+ });
102
+ });
103
+
104
+ void fc;
105
+ void expect;