@woosh/meep-engine 2.145.0 → 2.147.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (99) hide show
  1. package/package.json +1 -1
  2. package/src/core/geom/3d/shape/HeightMapShape3D.d.ts +33 -3
  3. package/src/core/geom/3d/shape/HeightMapShape3D.d.ts.map +1 -1
  4. package/src/core/geom/3d/shape/HeightMapShape3D.js +486 -451
  5. package/src/engine/control/first-person/DESIGN_COLLISION.md +365 -352
  6. package/src/engine/control/first-person/FirstPersonPlayerController.d.ts +1 -14
  7. package/src/engine/control/first-person/FirstPersonPlayerController.d.ts.map +1 -1
  8. package/src/engine/control/first-person/FirstPersonPlayerControllerConfig.d.ts +20 -8
  9. package/src/engine/control/first-person/FirstPersonPlayerControllerConfig.d.ts.map +1 -1
  10. package/src/engine/control/first-person/FirstPersonPlayerControllerConfig.js +552 -546
  11. package/src/engine/control/first-person/TODO.md +13 -11
  12. package/src/engine/control/first-person/abilities/LedgeGrab.d.ts +8 -3
  13. package/src/engine/control/first-person/abilities/LedgeGrab.d.ts.map +1 -1
  14. package/src/engine/control/first-person/abilities/LedgeGrab.js +213 -199
  15. package/src/engine/control/first-person/abilities/Mantle.d.ts.map +1 -1
  16. package/src/engine/control/first-person/abilities/Mantle.js +195 -188
  17. package/src/engine/control/first-person/abilities/WallJump.d.ts.map +1 -1
  18. package/src/engine/control/first-person/abilities/WallJump.js +11 -3
  19. package/src/engine/control/first-person/abilities/WallRun.d.ts.map +1 -1
  20. package/src/engine/control/first-person/abilities/WallRun.js +183 -163
  21. package/src/engine/control/first-person/collision/KinematicMover.d.ts.map +1 -1
  22. package/src/engine/control/first-person/collision/KinematicMover.js +634 -592
  23. package/src/engine/control/first-person/prototype_first_person_controller.js +1003 -901
  24. package/src/engine/control/first-person/sensors/FirstPersonSensors.d.ts +9 -0
  25. package/src/engine/control/first-person/sensors/FirstPersonSensors.d.ts.map +1 -1
  26. package/src/engine/control/first-person/sensors/FirstPersonSensors.js +87 -77
  27. package/src/engine/control/first-person/sensors/FirstPersonSensorsSystem.d.ts +8 -0
  28. package/src/engine/control/first-person/sensors/FirstPersonSensorsSystem.d.ts.map +1 -1
  29. package/src/engine/control/first-person/sensors/FirstPersonSensorsSystem.js +229 -196
  30. package/src/engine/ecs/EntityManager.d.ts +34 -11
  31. package/src/engine/ecs/EntityManager.d.ts.map +1 -1
  32. package/src/engine/ecs/EntityManager.js +71 -42
  33. package/src/engine/interpolation/BinaryInterpolationAdapter.d.ts.map +1 -0
  34. package/src/engine/interpolation/Interpoland.d.ts +48 -0
  35. package/src/engine/interpolation/Interpoland.d.ts.map +1 -0
  36. package/src/engine/interpolation/Interpoland.js +49 -0
  37. package/src/engine/interpolation/Interpolated.d.ts +101 -0
  38. package/src/engine/interpolation/Interpolated.d.ts.map +1 -0
  39. package/src/engine/interpolation/Interpolated.js +149 -0
  40. package/src/engine/{network/sim → interpolation}/InterpolationLog.d.ts +1 -1
  41. package/src/engine/interpolation/InterpolationLog.d.ts.map +1 -0
  42. package/src/engine/{network/sim → interpolation}/InterpolationLog.js +2 -2
  43. package/src/engine/interpolation/InterpolationSystem.d.ts +116 -0
  44. package/src/engine/interpolation/InterpolationSystem.d.ts.map +1 -0
  45. package/src/engine/interpolation/InterpolationSystem.js +233 -0
  46. package/src/engine/interpolation/PoseInterpolationAdapter.d.ts +17 -0
  47. package/src/engine/interpolation/PoseInterpolationAdapter.d.ts.map +1 -0
  48. package/src/engine/interpolation/PoseInterpolationAdapter.js +61 -0
  49. package/src/engine/interpolation/TransformPoseSerializationAdapter.d.ts +35 -0
  50. package/src/engine/interpolation/TransformPoseSerializationAdapter.d.ts.map +1 -0
  51. package/src/engine/interpolation/TransformPoseSerializationAdapter.js +57 -0
  52. package/src/engine/interpolation/pose_interpoland.d.ts +18 -0
  53. package/src/engine/interpolation/pose_interpoland.d.ts.map +1 -0
  54. package/src/engine/interpolation/pose_interpoland.js +27 -0
  55. package/src/engine/network/NetworkSession.d.ts +2 -2
  56. package/src/engine/network/NetworkSession.d.ts.map +1 -1
  57. package/src/engine/network/NetworkSession.js +2 -2
  58. package/src/engine/network/adapters/QuaternionInterpolationAdapter.d.ts +1 -1
  59. package/src/engine/network/adapters/QuaternionInterpolationAdapter.d.ts.map +1 -1
  60. package/src/engine/network/adapters/QuaternionInterpolationAdapter.js +1 -1
  61. package/src/engine/network/adapters/TransformInterpolationAdapter.d.ts +1 -1
  62. package/src/engine/network/adapters/TransformInterpolationAdapter.d.ts.map +1 -1
  63. package/src/engine/network/adapters/TransformInterpolationAdapter.js +1 -1
  64. package/src/engine/network/adapters/Vector3InterpolationAdapter.d.ts +1 -1
  65. package/src/engine/network/adapters/Vector3InterpolationAdapter.d.ts.map +1 -1
  66. package/src/engine/network/adapters/Vector3InterpolationAdapter.js +1 -1
  67. package/src/engine/physics/INTEPOLATION_SYSTEM_PLAN.md +287 -0
  68. package/src/engine/physics/PLAN.md +944 -809
  69. package/src/engine/physics/body/BodyStorage.d.ts +9 -0
  70. package/src/engine/physics/body/BodyStorage.d.ts.map +1 -1
  71. package/src/engine/physics/body/BodyStorage.js +23 -0
  72. package/src/engine/physics/broadphase/generate_pairs.d.ts.map +1 -1
  73. package/src/engine/physics/broadphase/generate_pairs.js +7 -0
  74. package/src/engine/physics/ccd/linear_sweep.d.ts +97 -0
  75. package/src/engine/physics/ccd/linear_sweep.d.ts.map +1 -0
  76. package/src/engine/physics/ccd/linear_sweep.js +238 -0
  77. package/src/engine/physics/ecs/PhysicsSystem.d.ts +82 -3
  78. package/src/engine/physics/ecs/PhysicsSystem.d.ts.map +1 -1
  79. package/src/engine/physics/ecs/PhysicsSystem.js +227 -8
  80. package/src/engine/physics/ecs/RigidBodyFlags.d.ts +6 -0
  81. package/src/engine/physics/ecs/RigidBodyFlags.d.ts.map +1 -1
  82. package/src/engine/physics/ecs/RigidBodyFlags.js +6 -0
  83. package/src/engine/physics/narrowphase/box_triangle_contact.js +814 -811
  84. package/src/engine/physics/narrowphase/compute_penetration.d.ts.map +1 -1
  85. package/src/engine/physics/narrowphase/compute_penetration.js +325 -323
  86. package/src/engine/physics/narrowphase/decomposition/heightmap_enumerate_triangles.d.ts +27 -8
  87. package/src/engine/physics/narrowphase/decomposition/heightmap_enumerate_triangles.d.ts.map +1 -1
  88. package/src/engine/physics/narrowphase/decomposition/heightmap_enumerate_triangles.js +235 -204
  89. package/src/engine/physics/narrowphase/narrowphase_step.d.ts.map +1 -1
  90. package/src/engine/physics/narrowphase/narrowphase_step.js +97 -13
  91. package/src/engine/physics/queries/overlap_shape.d.ts.map +1 -1
  92. package/src/engine/physics/queries/overlap_shape.js +185 -183
  93. package/src/engine/simulation/Ticker.d.ts +14 -0
  94. package/src/engine/simulation/Ticker.d.ts.map +1 -1
  95. package/src/engine/simulation/Ticker.js +136 -1
  96. package/src/engine/network/sim/BinaryInterpolationAdapter.d.ts.map +0 -1
  97. package/src/engine/network/sim/InterpolationLog.d.ts.map +0 -1
  98. /package/src/engine/{network/sim → interpolation}/BinaryInterpolationAdapter.d.ts +0 -0
  99. /package/src/engine/{network/sim → interpolation}/BinaryInterpolationAdapter.js +0 -0
@@ -8,10 +8,25 @@
8
8
  * [+6..+8] vC.xyz
9
9
  * [+9] feature_id (stable across frames; warm-start key)
10
10
  *
11
- * The feature_id encodes `(cell_y * (W - 1) + cell_x) * 2 + tri_idx`
12
- * where (cell_x, cell_y) is the grid cell and tri_idx ∈ {0, 1}. Two
13
- * triangles per cell, lower-left-diagonal split (matches the existing
14
- * {@link build_height_field_geometry} winding).
11
+ * ## Tessellation
12
+ *
13
+ * Each sampler cell is split into `N × N` sub-cells, where `N =
14
+ * shape.tessellation` (a non-negative integer, validated at construction).
15
+ * The sub-cell corners are sampled with the same Catmull-Rom filter the
16
+ * surface uses everywhere else, so finer `N` makes the faceted collision
17
+ * surface converge toward the smooth surface the renderer draws (which
18
+ * samples the identical cubic, just at its own `size × resolution` density).
19
+ * `N = 1` emits exactly one quad per sampler cell — the legacy behaviour;
20
+ * `N = 0` emits nothing. Cost is O(N²) per cell, so the caller owns the
21
+ * fidelity/cost trade-off.
22
+ *
23
+ * The collision grid therefore has `seg_u = (W − 1)·N` segments along u and
24
+ * `seg_v = (H − 1)·N` along v. The feature_id encodes
25
+ * `(sub_y · seg_u + sub_x) · 2 + tri_idx`, where (sub_x, sub_y) is the
26
+ * sub-cell and tri_idx ∈ {0, 1}: a unique, frame-stable key per sub-cell
27
+ * triangle (`seg_u · seg_v · 2` stays well under 2^53 for any realistic
28
+ * sampler × N). Two triangles per sub-cell, lower-left-diagonal split
29
+ * (matches the existing {@link build_height_field_geometry} winding).
15
30
  *
16
31
  * The query AABB is filtered against the footprint *and* against the
17
32
  * cell grid; triangles outside the query AABB along the height axis are
@@ -21,11 +36,15 @@
21
36
  * Bullet's btHeightfieldTerrainShape does.
22
37
  *
23
38
  * The caller is responsible for ensuring
24
- * `output.length - offset >= cells_in_query * 2 * TRIANGLE_FLOAT_STRIDE`
39
+ * `output.length - offset >= sub_cells_in_query * 2 * TRIANGLE_FLOAT_STRIDE`
25
40
  * — sizing the scratch buffer for the *worst-case* query AABB is the
26
- * right pattern (the broadphase already bounded the AABB to one body's
27
- * world envelope, so the cell count is bounded by the footprint
28
- * resolution).
41
+ * right pattern. The broadphase already bounded the AABB to one body's
42
+ * world envelope, so the sampler-cell count is bounded by the footprint
43
+ * resolution; note the sub-cell count (and thus the buffer requirement)
44
+ * scales by N² with {@link HeightMapShape3D#tessellation}. Writes past the
45
+ * end of `output` are silently dropped by the typed array, so an
46
+ * undersized buffer degrades to a missed contact on a far cell (recovered
47
+ * next broadphase), never a crash.
29
48
  *
30
49
  * @param {Float64Array} output
31
50
  * @param {number} offset float-index into output
@@ -1 +1 @@
1
- {"version":3,"file":"heightmap_enumerate_triangles.d.ts","sourceRoot":"","sources":["../../../../../../src/engine/physics/narrowphase/decomposition/heightmap_enumerate_triangles.js"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AACH,sDAXW,YAAY,UACZ,MAAM,uCAEN,MAAM,cACN,MAAM,cACN,MAAM,cACN,MAAM,cACN,MAAM,cACN,MAAM,GACJ,MAAM,CAmKlB"}
1
+ {"version":3,"file":"heightmap_enumerate_triangles.d.ts","sourceRoot":"","sources":["../../../../../../src/engine/physics/narrowphase/decomposition/heightmap_enumerate_triangles.js"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0DG;AACH,sDAXW,YAAY,UACZ,MAAM,uCAEN,MAAM,cACN,MAAM,cACN,MAAM,cACN,MAAM,cACN,MAAM,cACN,MAAM,GACJ,MAAM,CA+KlB"}
@@ -1,204 +1,235 @@
1
- import { TRIANGLE_FEATURE_ID_OFFSET, TRIANGLE_FLOAT_STRIDE } from "./triangle_buffer_layout.js";
2
-
3
- /**
4
- * Write the heightmap's triangles that may overlap the given body-local
5
- * query AABB into `output`, starting at float-index `offset`.
6
- *
7
- * Each triangle occupies {@link TRIANGLE_FLOAT_STRIDE} consecutive floats:
8
- * [+0..+2] vA.xyz (body-local frame)
9
- * [+3..+5] vB.xyz
10
- * [+6..+8] vC.xyz
11
- * [+9] feature_id (stable across frames; warm-start key)
12
- *
13
- * The feature_id encodes `(cell_y * (W - 1) + cell_x) * 2 + tri_idx`
14
- * where (cell_x, cell_y) is the grid cell and tri_idx ∈ {0, 1}. Two
15
- * triangles per cell, lower-left-diagonal split (matches the existing
16
- * {@link build_height_field_geometry} winding).
17
- *
18
- * The query AABB is filtered against the footprint *and* against the
19
- * cell grid; triangles outside the query AABB along the height axis are
20
- * NOT filtered out that test is left to the per-triangle narrowphase
21
- * (where the precise GJK distance is computed anyway). This keeps the
22
- * enumerator branch-free over the height field and aligned with what
23
- * Bullet's btHeightfieldTerrainShape does.
24
- *
25
- * The caller is responsible for ensuring
26
- * `output.length - offset >= cells_in_query * 2 * TRIANGLE_FLOAT_STRIDE`
27
- * sizing the scratch buffer for the *worst-case* query AABB is the
28
- * right pattern (the broadphase already bounded the AABB to one body's
29
- * world envelope, so the cell count is bounded by the footprint
30
- * resolution).
31
- *
32
- * @param {Float64Array} output
33
- * @param {number} offset float-index into output
34
- * @param {HeightMapShape3D} shape
35
- * @param {number} aabb_min_x query AABB in shape's body-local frame
36
- * @param {number} aabb_min_y
37
- * @param {number} aabb_min_z
38
- * @param {number} aabb_max_x
39
- * @param {number} aabb_max_y
40
- * @param {number} aabb_max_z
41
- * @returns {number} number of triangles written
42
- */
43
- export function heightmap_enumerate_triangles(
44
- output, offset, shape,
45
- aabb_min_x, aabb_min_y, aabb_min_z,
46
- aabb_max_x, aabb_max_y, aabb_max_z
47
- ) {
48
- const sampler = shape.sampler;
49
-
50
- if (sampler === null) {
51
- return 0;
52
- }
53
-
54
- const W = sampler.width;
55
- const H = sampler.height;
56
-
57
- if (W < 2 || H < 2) {
58
- // need at least one cell (one cell = 2 vertices per axis)
59
- return 0;
60
- }
61
-
62
- const size_x = shape.size[0];
63
- const size_z = shape.size[2];
64
-
65
- if (size_x <= 0 || size_z <= 0) {
66
- return 0;
67
- }
68
-
69
- // ── Project body-local AABB into heightmap-local (u, v, h) frame ─────
70
- // Basis rows = (u_axis, v_axis, n_axis) in body-local frame. The
71
- // body→heightmap rotation is therefore B (rows of basis), and the
72
- // Arvo trick gives us the projected AABB extents.
73
-
74
- shape._ensure_basis();
75
- const b = shape._basis;
76
-
77
- const c_x = 0.5 * (aabb_min_x + aabb_max_x);
78
- const c_y = 0.5 * (aabb_min_y + aabb_max_y);
79
- const c_z = 0.5 * (aabb_min_z + aabb_max_z);
80
-
81
- const h_x = 0.5 * (aabb_max_x - aabb_min_x);
82
- const h_y = 0.5 * (aabb_max_y - aabb_min_y);
83
- const h_z = 0.5 * (aabb_max_z - aabb_min_z);
84
-
85
- const u_c = b[0] * c_x + b[1] * c_y + b[2] * c_z;
86
- const v_c = b[3] * c_x + b[4] * c_y + b[5] * c_z;
87
- // height axis (b[6..8]) is intentionally not projected — see fn docstring
88
-
89
- const u_e = Math.abs(b[0]) * h_x + Math.abs(b[1]) * h_y + Math.abs(b[2]) * h_z;
90
- const v_e = Math.abs(b[3]) * h_x + Math.abs(b[4]) * h_y + Math.abs(b[5]) * h_z;
91
-
92
- const half_u = size_x * 0.5;
93
- const half_v = size_z * 0.5;
94
-
95
- // ── Intersect with footprint, derive cell range ──────────────────────
96
-
97
- const u_lo_q = u_c - u_e;
98
- const u_hi_q = u_c + u_e;
99
- const v_lo_q = v_c - v_e;
100
- const v_hi_q = v_c + v_e;
101
-
102
- if (u_lo_q > half_u || u_hi_q < -half_u) return 0;
103
- if (v_lo_q > half_v || v_hi_q < -half_v) return 0;
104
-
105
- const u_lo = u_lo_q > -half_u ? u_lo_q : -half_u;
106
- const u_hi = u_hi_q < +half_u ? u_hi_q : +half_u;
107
- const v_lo = v_lo_q > -half_v ? v_lo_q : -half_v;
108
- const v_hi = v_hi_q < +half_v ? v_hi_q : +half_v;
109
-
110
- const inv_cell_u = (W - 1) / size_x;
111
- const inv_cell_v = (H - 1) / size_z;
112
-
113
- let i_first = Math.floor((u_lo + half_u) * inv_cell_u);
114
- let i_last = Math.ceil ((u_hi + half_u) * inv_cell_u) - 1;
115
- let j_first = Math.floor((v_lo + half_v) * inv_cell_v);
116
- let j_last = Math.ceil ((v_hi + half_v) * inv_cell_v) - 1;
117
-
118
- if (i_first < 0) i_first = 0;
119
- if (j_first < 0) j_first = 0;
120
- if (i_last > W - 2) i_last = W - 2;
121
- if (j_last > H - 2) j_last = H - 2;
122
-
123
- if (i_first > i_last || j_first > j_last) return 0;
124
-
125
- // ── Emit triangles ───────────────────────────────────────────────────
126
- //
127
- // For each cell (i, j) we sample 4 corner heights and emit 2 triangles
128
- // matching the build_height_field_geometry winding:
129
- // A = (i, j ), B = (i+1, j )
130
- // D = (i, j+1), C = (i+1, j+1)
131
- // tri 0 = (A, D, B)
132
- // tri 1 = (D, C, B)
133
- //
134
- // body_point = u_axis * u_coord + v_axis * v_coord + n_axis * h_coord
135
- // where columns of the body-frame projection matrix are the basis rows
136
- // of B (since B is orthonormal, body→heightmap and heightmap→body
137
- // matrices are mutual transposes).
138
-
139
- const ux = b[0]; const uy = b[1]; const uz = b[2];
140
- const vx = b[3]; const vy = b[4]; const vz = b[5];
141
- const nx = b[6]; const ny = b[7]; const nz = b[8];
142
-
143
- const inv_W1 = 1 / (W - 1);
144
- const inv_H1 = 1 / (H - 1);
145
-
146
- let count = 0;
147
- let cursor = offset;
148
-
149
- for (let j = j_first; j <= j_last; j++) {
150
- const v01_lo = j * inv_H1;
151
- const v01_hi = (j + 1) * inv_H1;
152
- const v_lo_coord = -half_v + size_z * v01_lo;
153
- const v_hi_coord = -half_v + size_z * v01_hi;
154
-
155
- for (let i = i_first; i <= i_last; i++) {
156
- const u01_lo = i * inv_W1;
157
- const u01_hi = (i + 1) * inv_W1;
158
- const u_lo_coord = -half_u + size_x * u01_lo;
159
- const u_hi_coord = -half_u + size_x * u01_hi;
160
-
161
- const hA = sampler.sampleChannelCatmullRomUV(u01_lo, v01_lo, 0);
162
- const hB = sampler.sampleChannelCatmullRomUV(u01_hi, v01_lo, 0);
163
- const hC = sampler.sampleChannelCatmullRomUV(u01_hi, v01_hi, 0);
164
- const hD = sampler.sampleChannelCatmullRomUV(u01_lo, v01_hi, 0);
165
-
166
- const Ax = ux * u_lo_coord + vx * v_lo_coord + nx * hA;
167
- const Ay = uy * u_lo_coord + vy * v_lo_coord + ny * hA;
168
- const Az = uz * u_lo_coord + vz * v_lo_coord + nz * hA;
169
-
170
- const Bx = ux * u_hi_coord + vx * v_lo_coord + nx * hB;
171
- const By = uy * u_hi_coord + vy * v_lo_coord + ny * hB;
172
- const Bz = uz * u_hi_coord + vz * v_lo_coord + nz * hB;
173
-
174
- const Cx = ux * u_hi_coord + vx * v_hi_coord + nx * hC;
175
- const Cy = uy * u_hi_coord + vy * v_hi_coord + ny * hC;
176
- const Cz = uz * u_hi_coord + vz * v_hi_coord + nz * hC;
177
-
178
- const Dx = ux * u_lo_coord + vx * v_hi_coord + nx * hD;
179
- const Dy = uy * u_lo_coord + vy * v_hi_coord + ny * hD;
180
- const Dz = uz * u_lo_coord + vz * v_hi_coord + nz * hD;
181
-
182
- const cell_idx = j * (W - 1) + i;
183
- const fid_base = cell_idx * 2;
184
-
185
- // triangle 0: (A, D, B)
186
- output[cursor ] = Ax; output[cursor + 1] = Ay; output[cursor + 2] = Az;
187
- output[cursor + 3] = Dx; output[cursor + 4] = Dy; output[cursor + 5] = Dz;
188
- output[cursor + 6] = Bx; output[cursor + 7] = By; output[cursor + 8] = Bz;
189
- output[cursor + TRIANGLE_FEATURE_ID_OFFSET] = fid_base;
190
- cursor += TRIANGLE_FLOAT_STRIDE;
191
- count++;
192
-
193
- // triangle 1: (D, C, B)
194
- output[cursor ] = Dx; output[cursor + 1] = Dy; output[cursor + 2] = Dz;
195
- output[cursor + 3] = Cx; output[cursor + 4] = Cy; output[cursor + 5] = Cz;
196
- output[cursor + 6] = Bx; output[cursor + 7] = By; output[cursor + 8] = Bz;
197
- output[cursor + TRIANGLE_FEATURE_ID_OFFSET] = fid_base + 1;
198
- cursor += TRIANGLE_FLOAT_STRIDE;
199
- count++;
200
- }
201
- }
202
-
203
- return count;
204
- }
1
+ import { TRIANGLE_FEATURE_ID_OFFSET, TRIANGLE_FLOAT_STRIDE } from "./triangle_buffer_layout.js";
2
+
3
+ /**
4
+ * Write the heightmap's triangles that may overlap the given body-local
5
+ * query AABB into `output`, starting at float-index `offset`.
6
+ *
7
+ * Each triangle occupies {@link TRIANGLE_FLOAT_STRIDE} consecutive floats:
8
+ * [+0..+2] vA.xyz (body-local frame)
9
+ * [+3..+5] vB.xyz
10
+ * [+6..+8] vC.xyz
11
+ * [+9] feature_id (stable across frames; warm-start key)
12
+ *
13
+ * ## Tessellation
14
+ *
15
+ * Each sampler cell is split into `N × N` sub-cells, where `N =
16
+ * shape.tessellation` (a non-negative integer, validated at construction).
17
+ * The sub-cell corners are sampled with the same Catmull-Rom filter the
18
+ * surface uses everywhere else, so finer `N` makes the faceted collision
19
+ * surface converge toward the smooth surface the renderer draws (which
20
+ * samples the identical cubic, just at its own `size × resolution` density).
21
+ * `N = 1` emits exactly one quad per sampler cell the legacy behaviour;
22
+ * `N = 0` emits nothing. Cost is O(N²) per cell, so the caller owns the
23
+ * fidelity/cost trade-off.
24
+ *
25
+ * The collision grid therefore has `seg_u = (W − 1)·N` segments along u and
26
+ * `seg_v = (H 1)·N` along v. The feature_id encodes
27
+ * `(sub_y · seg_u + sub_x) · 2 + tri_idx`, where (sub_x, sub_y) is the
28
+ * sub-cell and tri_idx {0, 1}: a unique, frame-stable key per sub-cell
29
+ * triangle (`seg_u · seg_v · 2` stays well under 2^53 for any realistic
30
+ * sampler × N). Two triangles per sub-cell, lower-left-diagonal split
31
+ * (matches the existing {@link build_height_field_geometry} winding).
32
+ *
33
+ * The query AABB is filtered against the footprint *and* against the
34
+ * cell grid; triangles outside the query AABB along the height axis are
35
+ * NOT filtered out that test is left to the per-triangle narrowphase
36
+ * (where the precise GJK distance is computed anyway). This keeps the
37
+ * enumerator branch-free over the height field and aligned with what
38
+ * Bullet's btHeightfieldTerrainShape does.
39
+ *
40
+ * The caller is responsible for ensuring
41
+ * `output.length - offset >= sub_cells_in_query * 2 * TRIANGLE_FLOAT_STRIDE`
42
+ * — sizing the scratch buffer for the *worst-case* query AABB is the
43
+ * right pattern. The broadphase already bounded the AABB to one body's
44
+ * world envelope, so the sampler-cell count is bounded by the footprint
45
+ * resolution; note the sub-cell count (and thus the buffer requirement)
46
+ * scales by N² with {@link HeightMapShape3D#tessellation}. Writes past the
47
+ * end of `output` are silently dropped by the typed array, so an
48
+ * undersized buffer degrades to a missed contact on a far cell (recovered
49
+ * next broadphase), never a crash.
50
+ *
51
+ * @param {Float64Array} output
52
+ * @param {number} offset float-index into output
53
+ * @param {HeightMapShape3D} shape
54
+ * @param {number} aabb_min_x query AABB in shape's body-local frame
55
+ * @param {number} aabb_min_y
56
+ * @param {number} aabb_min_z
57
+ * @param {number} aabb_max_x
58
+ * @param {number} aabb_max_y
59
+ * @param {number} aabb_max_z
60
+ * @returns {number} number of triangles written
61
+ */
62
+ export function heightmap_enumerate_triangles(
63
+ output, offset, shape,
64
+ aabb_min_x, aabb_min_y, aabb_min_z,
65
+ aabb_max_x, aabb_max_y, aabb_max_z
66
+ ) {
67
+ const sampler = shape.sampler;
68
+
69
+ if (sampler === null) {
70
+ return 0;
71
+ }
72
+
73
+ const W = sampler.width;
74
+ const H = sampler.height;
75
+
76
+ if (W < 2 || H < 2) {
77
+ // need at least one cell (one cell = 2 vertices per axis)
78
+ return 0;
79
+ }
80
+
81
+ const size_x = shape.size[0];
82
+ const size_z = shape.size[2];
83
+
84
+ if (size_x <= 0 || size_z <= 0) {
85
+ return 0;
86
+ }
87
+
88
+ // Collision tessellation: split each sampler cell into N×N sub-cells so a
89
+ // coarse sampler's contact surface can approach render density. N is a
90
+ // non-negative integer (validated by HeightMapShape3D.from); N=1
91
+ // reproduces the legacy one-quad-per-sampler-cell grid exactly (seg_u =
92
+ // W-1, seg_v = H-1), and N=0 collapses the grid so the cell-range check
93
+ // below returns 0 the degenerate empty case, handled without a branch.
94
+ const N = shape.tessellation;
95
+
96
+ const seg_u = (W - 1) * N;
97
+ const seg_v = (H - 1) * N;
98
+
99
+ // ── Project body-local AABB into heightmap-local (u, v, h) frame ─────
100
+ // Basis rows = (u_axis, v_axis, n_axis) in body-local frame. The
101
+ // body→heightmap rotation is therefore B (rows of basis), and the
102
+ // Arvo trick gives us the projected AABB extents.
103
+
104
+ shape._ensure_basis();
105
+ const b = shape._basis;
106
+
107
+ const c_x = 0.5 * (aabb_min_x + aabb_max_x);
108
+ const c_y = 0.5 * (aabb_min_y + aabb_max_y);
109
+ const c_z = 0.5 * (aabb_min_z + aabb_max_z);
110
+
111
+ const h_x = 0.5 * (aabb_max_x - aabb_min_x);
112
+ const h_y = 0.5 * (aabb_max_y - aabb_min_y);
113
+ const h_z = 0.5 * (aabb_max_z - aabb_min_z);
114
+
115
+ const u_c = b[0] * c_x + b[1] * c_y + b[2] * c_z;
116
+ const v_c = b[3] * c_x + b[4] * c_y + b[5] * c_z;
117
+ // height axis (b[6..8]) is intentionally not projected — see fn docstring
118
+
119
+ const u_e = Math.abs(b[0]) * h_x + Math.abs(b[1]) * h_y + Math.abs(b[2]) * h_z;
120
+ const v_e = Math.abs(b[3]) * h_x + Math.abs(b[4]) * h_y + Math.abs(b[5]) * h_z;
121
+
122
+ const half_u = size_x * 0.5;
123
+ const half_v = size_z * 0.5;
124
+
125
+ // ── Intersect with footprint, derive cell range ──────────────────────
126
+
127
+ const u_lo_q = u_c - u_e;
128
+ const u_hi_q = u_c + u_e;
129
+ const v_lo_q = v_c - v_e;
130
+ const v_hi_q = v_c + v_e;
131
+
132
+ if (u_lo_q > half_u || u_hi_q < -half_u) return 0;
133
+ if (v_lo_q > half_v || v_hi_q < -half_v) return 0;
134
+
135
+ const u_lo = u_lo_q > -half_u ? u_lo_q : -half_u;
136
+ const u_hi = u_hi_q < +half_u ? u_hi_q : +half_u;
137
+ const v_lo = v_lo_q > -half_v ? v_lo_q : -half_v;
138
+ const v_hi = v_hi_q < +half_v ? v_hi_q : +half_v;
139
+
140
+ const inv_cell_u = seg_u / size_x;
141
+ const inv_cell_v = seg_v / size_z;
142
+
143
+ let i_first = Math.floor((u_lo + half_u) * inv_cell_u);
144
+ let i_last = Math.ceil ((u_hi + half_u) * inv_cell_u) - 1;
145
+ let j_first = Math.floor((v_lo + half_v) * inv_cell_v);
146
+ let j_last = Math.ceil ((v_hi + half_v) * inv_cell_v) - 1;
147
+
148
+ if (i_first < 0) i_first = 0;
149
+ if (j_first < 0) j_first = 0;
150
+ if (i_last > seg_u - 1) i_last = seg_u - 1;
151
+ if (j_last > seg_v - 1) j_last = seg_v - 1;
152
+
153
+ if (i_first > i_last || j_first > j_last) return 0;
154
+
155
+ // ── Emit triangles ───────────────────────────────────────────────────
156
+ //
157
+ // For each sub-cell (i, j) on the seg_u × seg_v grid we sample 4 corner
158
+ // heights (Catmull-Rom, at the sub-cell UVs) and emit 2 triangles
159
+ // matching the build_height_field_geometry winding:
160
+ // A = (i, j ), B = (i+1, j )
161
+ // D = (i, j+1), C = (i+1, j+1)
162
+ // tri 0 = (A, D, B)
163
+ // tri 1 = (D, C, B)
164
+ //
165
+ // body_point = u_axis * u_coord + v_axis * v_coord + n_axis * h_coord
166
+ // where columns of the body-frame projection matrix are the basis rows
167
+ // of B (since B is orthonormal, body→heightmap and heightmap→body
168
+ // matrices are mutual transposes).
169
+
170
+ const ux = b[0]; const uy = b[1]; const uz = b[2];
171
+ const vx = b[3]; const vy = b[4]; const vz = b[5];
172
+ const nx = b[6]; const ny = b[7]; const nz = b[8];
173
+
174
+ const inv_segU = 1 / seg_u;
175
+ const inv_segV = 1 / seg_v;
176
+
177
+ let count = 0;
178
+ let cursor = offset;
179
+
180
+ for (let j = j_first; j <= j_last; j++) {
181
+ const v01_lo = j * inv_segV;
182
+ const v01_hi = (j + 1) * inv_segV;
183
+ const v_lo_coord = -half_v + size_z * v01_lo;
184
+ const v_hi_coord = -half_v + size_z * v01_hi;
185
+
186
+ for (let i = i_first; i <= i_last; i++) {
187
+ const u01_lo = i * inv_segU;
188
+ const u01_hi = (i + 1) * inv_segU;
189
+ const u_lo_coord = -half_u + size_x * u01_lo;
190
+ const u_hi_coord = -half_u + size_x * u01_hi;
191
+
192
+ const hA = sampler.sampleChannelCatmullRomUV(u01_lo, v01_lo, 0);
193
+ const hB = sampler.sampleChannelCatmullRomUV(u01_hi, v01_lo, 0);
194
+ const hC = sampler.sampleChannelCatmullRomUV(u01_hi, v01_hi, 0);
195
+ const hD = sampler.sampleChannelCatmullRomUV(u01_lo, v01_hi, 0);
196
+
197
+ const Ax = ux * u_lo_coord + vx * v_lo_coord + nx * hA;
198
+ const Ay = uy * u_lo_coord + vy * v_lo_coord + ny * hA;
199
+ const Az = uz * u_lo_coord + vz * v_lo_coord + nz * hA;
200
+
201
+ const Bx = ux * u_hi_coord + vx * v_lo_coord + nx * hB;
202
+ const By = uy * u_hi_coord + vy * v_lo_coord + ny * hB;
203
+ const Bz = uz * u_hi_coord + vz * v_lo_coord + nz * hB;
204
+
205
+ const Cx = ux * u_hi_coord + vx * v_hi_coord + nx * hC;
206
+ const Cy = uy * u_hi_coord + vy * v_hi_coord + ny * hC;
207
+ const Cz = uz * u_hi_coord + vz * v_hi_coord + nz * hC;
208
+
209
+ const Dx = ux * u_lo_coord + vx * v_hi_coord + nx * hD;
210
+ const Dy = uy * u_lo_coord + vy * v_hi_coord + ny * hD;
211
+ const Dz = uz * u_lo_coord + vz * v_hi_coord + nz * hD;
212
+
213
+ const cell_idx = j * seg_u + i;
214
+ const fid_base = cell_idx * 2;
215
+
216
+ // triangle 0: (A, D, B)
217
+ output[cursor ] = Ax; output[cursor + 1] = Ay; output[cursor + 2] = Az;
218
+ output[cursor + 3] = Dx; output[cursor + 4] = Dy; output[cursor + 5] = Dz;
219
+ output[cursor + 6] = Bx; output[cursor + 7] = By; output[cursor + 8] = Bz;
220
+ output[cursor + TRIANGLE_FEATURE_ID_OFFSET] = fid_base;
221
+ cursor += TRIANGLE_FLOAT_STRIDE;
222
+ count++;
223
+
224
+ // triangle 1: (D, C, B)
225
+ output[cursor ] = Dx; output[cursor + 1] = Dy; output[cursor + 2] = Dz;
226
+ output[cursor + 3] = Cx; output[cursor + 4] = Cy; output[cursor + 5] = Cz;
227
+ output[cursor + 6] = Bx; output[cursor + 7] = By; output[cursor + 8] = Bz;
228
+ output[cursor + TRIANGLE_FEATURE_ID_OFFSET] = fid_base + 1;
229
+ cursor += TRIANGLE_FLOAT_STRIDE;
230
+ count++;
231
+ }
232
+ }
233
+
234
+ return count;
235
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"narrowphase_step.d.ts","sourceRoot":"","sources":["../../../../../src/engine/physics/narrowphase/narrowphase_step.js"],"names":[],"mappings":"AAwyCA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,qDAVW,YAAY,GAAC,MAAM,EAAE,iCAGrB;IAAC,CAAC,EAAC,MAAM,CAAC;IAAA,CAAC,EAAC,MAAM,CAAC;IAAA,CAAC,EAAC,MAAM,CAAA;CAAC,QAC5B;IAAC,CAAC,EAAC,MAAM,CAAC;IAAA,CAAC,EAAC,MAAM,CAAC;IAAA,CAAC,EAAC,MAAM,CAAC;IAAA,CAAC,EAAC,MAAM,CAAA;CAAC,iCAErC;IAAC,CAAC,EAAC,MAAM,CAAC;IAAA,CAAC,EAAC,MAAM,CAAC;IAAA,CAAC,EAAC,MAAM,CAAA;CAAC,QAC5B;IAAC,CAAC,EAAC,MAAM,CAAC;IAAA,CAAC,EAAC,MAAM,CAAC;IAAA,CAAC,EAAC,MAAM,CAAC;IAAA,CAAC,EAAC,MAAM,CAAA;CAAC,GACnC,MAAM,CAyClB;AAED;;;;;;;;;;;GAWG;AACH,uFALW,MAAM,MAAM;IAAC,QAAQ,WAAW;IAAC,SAAS,YAAW;CAAC,CAAC,CAAC,QA0JlE;AAED;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,uEAJW,MAAM,UACN,MAAM;IAAC,QAAQ,WAAW;IAAC,SAAS,YAAW;CAAC,CAAC,UACjD,MAAM;IAAC,QAAQ,WAAW;IAAC,SAAS,YAAW;CAAC,CAAC,QAiD3D"}
1
+ {"version":3,"file":"narrowphase_step.d.ts","sourceRoot":"","sources":["../../../../../src/engine/physics/narrowphase/narrowphase_step.js"],"names":[],"mappings":"AA2zCA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,qDAVW,YAAY,GAAC,MAAM,EAAE,iCAGrB;IAAC,CAAC,EAAC,MAAM,CAAC;IAAA,CAAC,EAAC,MAAM,CAAC;IAAA,CAAC,EAAC,MAAM,CAAA;CAAC,QAC5B;IAAC,CAAC,EAAC,MAAM,CAAC;IAAA,CAAC,EAAC,MAAM,CAAC;IAAA,CAAC,EAAC,MAAM,CAAC;IAAA,CAAC,EAAC,MAAM,CAAA;CAAC,iCAErC;IAAC,CAAC,EAAC,MAAM,CAAC;IAAA,CAAC,EAAC,MAAM,CAAC;IAAA,CAAC,EAAC,MAAM,CAAA;CAAC,QAC5B;IAAC,CAAC,EAAC,MAAM,CAAC;IAAA,CAAC,EAAC,MAAM,CAAC;IAAA,CAAC,EAAC,MAAM,CAAC;IAAA,CAAC,EAAC,MAAM,CAAA;CAAC,GACnC,MAAM,CAyClB;AAED;;;;;;;;;;;GAWG;AACH,uFALW,MAAM,MAAM;IAAC,QAAQ,WAAW;IAAC,SAAS,YAAW;CAAC,CAAC,CAAC,QA0JlE;AAED;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,uEAJW,MAAM,UACN,MAAM;IAAC,QAAQ,WAAW;IAAC,SAAS,YAAW;CAAC,CAAC,UACjD,MAAM;IAAC,QAAQ,WAAW;IAAC,SAAS,YAAW;CAAC,CAAC,QAkH3D"}