@series-inc/rundot-syncplay 5.26.0 → 5.28.0-beta.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.
- package/core/native/physics/physics.cpp +129 -3
- package/core/native/physics/physics.hpp +1 -1
- package/core/physics.d.ts +1 -0
- package/core/scripts/build-wasm-physics.mjs +1 -1
- package/core/src/physics/index.mjs +20 -9
- package/core/src/physics/physics-wasm-module.generated.mjs +4 -4
- package/dist/browser.d.ts +6 -4
- package/dist/browser.js +3 -2
- package/dist/creator.d.ts +6 -4
- package/dist/creator.js +3 -2
- package/dist/debug-wireframe.d.ts +22 -0
- package/dist/debug-wireframe.js +226 -0
- package/dist/index.d.ts +6 -4
- package/dist/index.js +3 -2
- package/dist/physics3d-voxel.d.ts +52 -1
- package/dist/physics3d-voxel.js +337 -0
- package/dist/physics3d.d.ts +57 -0
- package/dist/physics3d.js +295 -1
- package/package.json +7 -2
package/dist/physics3d-voxel.js
CHANGED
|
@@ -1,3 +1,141 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Amanatides & Woo 3D Digital Differential Analyzer (DDA) fast voxel grid raycaster.
|
|
3
|
+
* Marches through chunk cells in O(distance) directly over the occupancy bitset.
|
|
4
|
+
*/
|
|
5
|
+
export function raycastDeterministicVoxelChunk3D(chunk, query) {
|
|
6
|
+
const { dimX, dimY, dimZ, cellSize, occupancy } = chunk;
|
|
7
|
+
if (cellSize <= 0 || occupancy.length !== dimX * dimY * dimZ) {
|
|
8
|
+
return undefined;
|
|
9
|
+
}
|
|
10
|
+
const dirLen = Math.hypot(query.dx, query.dy, query.dz);
|
|
11
|
+
if (dirLen <= 1e-12) {
|
|
12
|
+
return undefined;
|
|
13
|
+
}
|
|
14
|
+
const dx = query.dx / dirLen;
|
|
15
|
+
const dy = query.dy / dirLen;
|
|
16
|
+
const dz = query.dz / dirLen;
|
|
17
|
+
const maxDistance = query.maxDistance;
|
|
18
|
+
if (maxDistance < 0) {
|
|
19
|
+
return undefined;
|
|
20
|
+
}
|
|
21
|
+
const ox = query.x / cellSize;
|
|
22
|
+
const oy = query.y / cellSize;
|
|
23
|
+
const oz = query.z / cellSize;
|
|
24
|
+
let tMin = 0;
|
|
25
|
+
let tMax = maxDistance / cellSize;
|
|
26
|
+
let enterNormal = { x: 0, y: 0, z: 0 };
|
|
27
|
+
const axes = [
|
|
28
|
+
{ o: ox, d: dx, min: 0, max: dimX, nx: 1, ny: 0, nz: 0 },
|
|
29
|
+
{ o: oy, d: dy, min: 0, max: dimY, nx: 0, ny: 1, nz: 0 },
|
|
30
|
+
{ o: oz, d: dz, min: 0, max: dimZ, nx: 0, ny: 0, nz: 1 },
|
|
31
|
+
];
|
|
32
|
+
for (const axis of axes) {
|
|
33
|
+
if (Math.abs(axis.d) < 1e-12) {
|
|
34
|
+
if (axis.o < axis.min || axis.o > axis.max) {
|
|
35
|
+
return undefined;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
let t1 = (axis.min - axis.o) / axis.d;
|
|
40
|
+
let t2 = (axis.max - axis.o) / axis.d;
|
|
41
|
+
let n1 = { x: -axis.nx, y: -axis.ny, z: -axis.nz };
|
|
42
|
+
if (t1 > t2) {
|
|
43
|
+
const temp = t1;
|
|
44
|
+
t1 = t2;
|
|
45
|
+
t2 = temp;
|
|
46
|
+
n1 = { x: axis.nx, y: axis.ny, z: axis.nz };
|
|
47
|
+
}
|
|
48
|
+
if (t1 > tMin) {
|
|
49
|
+
tMin = t1;
|
|
50
|
+
enterNormal = n1;
|
|
51
|
+
}
|
|
52
|
+
if (t2 < tMax) {
|
|
53
|
+
tMax = t2;
|
|
54
|
+
}
|
|
55
|
+
if (tMin > tMax) {
|
|
56
|
+
return undefined;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
if (ox >= 0 && ox < dimX && oy >= 0 && oy < dimY && oz >= 0 && oz < dimZ) {
|
|
61
|
+
tMin = 0;
|
|
62
|
+
}
|
|
63
|
+
const startX = ox + dx * tMin;
|
|
64
|
+
const startY = oy + dy * tMin;
|
|
65
|
+
const startZ = oz + dz * tMin;
|
|
66
|
+
let currentX = Math.floor(startX);
|
|
67
|
+
let currentY = Math.floor(startY);
|
|
68
|
+
let currentZ = Math.floor(startZ);
|
|
69
|
+
currentX = Math.max(0, Math.min(dimX - 1, currentX));
|
|
70
|
+
currentY = Math.max(0, Math.min(dimY - 1, currentY));
|
|
71
|
+
currentZ = Math.max(0, Math.min(dimZ - 1, currentZ));
|
|
72
|
+
const stepX = dx > 0 ? 1 : dx < 0 ? -1 : 0;
|
|
73
|
+
const stepY = dy > 0 ? 1 : dy < 0 ? -1 : 0;
|
|
74
|
+
const stepZ = dz > 0 ? 1 : dz < 0 ? -1 : 0;
|
|
75
|
+
const tDeltaX = stepX !== 0 ? Math.abs(1 / dx) : Infinity;
|
|
76
|
+
const tDeltaY = stepY !== 0 ? Math.abs(1 / dy) : Infinity;
|
|
77
|
+
const tDeltaZ = stepZ !== 0 ? Math.abs(1 / dz) : Infinity;
|
|
78
|
+
let tMaxX = stepX > 0 ? (currentX + 1 - ox) / dx : stepX < 0 ? (currentX - ox) / dx : Infinity;
|
|
79
|
+
let tMaxY = stepY > 0 ? (currentY + 1 - oy) / dy : stepY < 0 ? (currentY - oy) / dy : Infinity;
|
|
80
|
+
let tMaxZ = stepZ > 0 ? (currentZ + 1 - oz) / dz : stepZ < 0 ? (currentZ - oz) / dz : Infinity;
|
|
81
|
+
let currentNormal = enterNormal;
|
|
82
|
+
let hitDistanceGrid = tMin;
|
|
83
|
+
while (currentX >= 0 && currentX < dimX && currentY >= 0 && currentY < dimY && currentZ >= 0 && currentZ < dimZ) {
|
|
84
|
+
const cellIndex = currentX + dimX * (currentY + dimY * currentZ);
|
|
85
|
+
if (occupancy[cellIndex] === 1) {
|
|
86
|
+
const worldDistance = hitDistanceGrid * cellSize;
|
|
87
|
+
if (worldDistance > maxDistance) {
|
|
88
|
+
return undefined;
|
|
89
|
+
}
|
|
90
|
+
const hitPoint = {
|
|
91
|
+
x: query.x + dx * worldDistance,
|
|
92
|
+
y: query.y + dy * worldDistance,
|
|
93
|
+
z: query.z + dz * worldDistance,
|
|
94
|
+
};
|
|
95
|
+
return {
|
|
96
|
+
hit: true,
|
|
97
|
+
distance: worldDistance,
|
|
98
|
+
voxelX: currentX,
|
|
99
|
+
voxelY: currentY,
|
|
100
|
+
voxelZ: currentZ,
|
|
101
|
+
point: hitPoint,
|
|
102
|
+
normal: currentNormal,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
if (tMaxX < tMaxY) {
|
|
106
|
+
if (tMaxX < tMaxZ) {
|
|
107
|
+
hitDistanceGrid = tMaxX;
|
|
108
|
+
tMaxX += tDeltaX;
|
|
109
|
+
currentX += stepX;
|
|
110
|
+
currentNormal = { x: -stepX, y: 0, z: 0 };
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
113
|
+
hitDistanceGrid = tMaxZ;
|
|
114
|
+
tMaxZ += tDeltaZ;
|
|
115
|
+
currentZ += stepZ;
|
|
116
|
+
currentNormal = { x: 0, y: 0, z: -stepZ };
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
if (tMaxY < tMaxZ) {
|
|
121
|
+
hitDistanceGrid = tMaxY;
|
|
122
|
+
tMaxY += tDeltaY;
|
|
123
|
+
currentY += stepY;
|
|
124
|
+
currentNormal = { x: 0, y: -stepY, z: 0 };
|
|
125
|
+
}
|
|
126
|
+
else {
|
|
127
|
+
hitDistanceGrid = tMaxZ;
|
|
128
|
+
tMaxZ += tDeltaZ;
|
|
129
|
+
currentZ += stepZ;
|
|
130
|
+
currentNormal = { x: 0, y: 0, z: -stepZ };
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
if (hitDistanceGrid > tMax) {
|
|
134
|
+
break;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
return undefined;
|
|
138
|
+
}
|
|
1
139
|
/**
|
|
2
140
|
* Deterministic greedy box merge: scan z→y→x ascending and grow each unconsumed
|
|
3
141
|
* solid cell into the largest box along +x, then +y, then +z. A solid n³ chunk
|
|
@@ -89,3 +227,202 @@ export function cookDeterministicVoxelChunkCollider3D(chunk) {
|
|
|
89
227
|
}
|
|
90
228
|
return { type: 'compound', children };
|
|
91
229
|
}
|
|
230
|
+
/**
|
|
231
|
+
* Carves or fills a 3D axis-aligned cell region in a voxel chunk occupancy grid.
|
|
232
|
+
* Computes interval intersection with chunk dimensions, returning chunk unchanged
|
|
233
|
+
* if disjoint or no-op.
|
|
234
|
+
*/
|
|
235
|
+
export function carveDeterministicVoxelChunk3D(chunk, region) {
|
|
236
|
+
const { dimX, dimY, dimZ, cellSize, occupancy } = chunk;
|
|
237
|
+
const startX = Math.max(0, Math.min(region.minX, region.maxX));
|
|
238
|
+
const endX = Math.min(dimX - 1, Math.max(region.minX, region.maxX));
|
|
239
|
+
const startY = Math.max(0, Math.min(region.minY, region.maxY));
|
|
240
|
+
const endY = Math.min(dimY - 1, Math.max(region.minY, region.maxY));
|
|
241
|
+
const startZ = Math.max(0, Math.min(region.minZ, region.maxZ));
|
|
242
|
+
const endZ = Math.min(dimZ - 1, Math.max(region.minZ, region.maxZ));
|
|
243
|
+
if (startX > endX || startY > endY || startZ > endZ) {
|
|
244
|
+
return chunk;
|
|
245
|
+
}
|
|
246
|
+
const targetVal = region.operation === 'carve' ? 0 : 1;
|
|
247
|
+
let changed = false;
|
|
248
|
+
const nextOccupancy = [...occupancy];
|
|
249
|
+
for (let z = startZ; z <= endZ; z += 1) {
|
|
250
|
+
for (let y = startY; y <= endY; y += 1) {
|
|
251
|
+
for (let x = startX; x <= endX; x += 1) {
|
|
252
|
+
const index = x + dimX * (y + dimY * z);
|
|
253
|
+
if (nextOccupancy[index] !== targetVal) {
|
|
254
|
+
nextOccupancy[index] = targetVal;
|
|
255
|
+
changed = true;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
if (!changed) {
|
|
261
|
+
return chunk;
|
|
262
|
+
}
|
|
263
|
+
return {
|
|
264
|
+
dimX,
|
|
265
|
+
dimY,
|
|
266
|
+
dimZ,
|
|
267
|
+
cellSize,
|
|
268
|
+
occupancy: nextOccupancy,
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
function cookSubregionBoxes(chunk, minX, minY, minZ, maxX, maxY, maxZ) {
|
|
272
|
+
const { dimX, dimY, dimZ, cellSize, occupancy } = chunk;
|
|
273
|
+
const solid = (x, y, z) => occupancy[x + dimX * (y + dimY * z)] !== 0;
|
|
274
|
+
const consumed = new Uint8Array(dimX * dimY * dimZ);
|
|
275
|
+
const free = (x, y, z) => solid(x, y, z) && consumed[x + dimX * (y + dimY * z)] === 0;
|
|
276
|
+
const children = [];
|
|
277
|
+
for (let z = minZ; z <= maxZ; z += 1) {
|
|
278
|
+
for (let y = minY; y <= maxY; y += 1) {
|
|
279
|
+
for (let x = minX; x <= maxX; x += 1) {
|
|
280
|
+
if (!free(x, y, z)) {
|
|
281
|
+
continue;
|
|
282
|
+
}
|
|
283
|
+
let x1 = x;
|
|
284
|
+
while (x1 + 1 <= maxX && free(x1 + 1, y, z)) {
|
|
285
|
+
x1 += 1;
|
|
286
|
+
}
|
|
287
|
+
const rowFree = (yy, zz) => {
|
|
288
|
+
for (let xx = x; xx <= x1; xx += 1) {
|
|
289
|
+
if (!free(xx, yy, zz)) {
|
|
290
|
+
return false;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
return true;
|
|
294
|
+
};
|
|
295
|
+
let y1 = y;
|
|
296
|
+
while (y1 + 1 <= maxY && rowFree(y1 + 1, z)) {
|
|
297
|
+
y1 += 1;
|
|
298
|
+
}
|
|
299
|
+
const slabFree = (zz) => {
|
|
300
|
+
for (let yy = y; yy <= y1; yy += 1) {
|
|
301
|
+
if (!rowFree(yy, zz)) {
|
|
302
|
+
return false;
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
return true;
|
|
306
|
+
};
|
|
307
|
+
let z1 = z;
|
|
308
|
+
while (z1 + 1 <= maxZ && slabFree(z1 + 1)) {
|
|
309
|
+
z1 += 1;
|
|
310
|
+
}
|
|
311
|
+
for (let zz = z; zz <= z1; zz += 1) {
|
|
312
|
+
for (let yy = y; yy <= y1; yy += 1) {
|
|
313
|
+
for (let xx = x; xx <= x1; xx += 1) {
|
|
314
|
+
consumed[xx + dimX * (yy + dimY * zz)] = 1;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
children.push({
|
|
319
|
+
shape: {
|
|
320
|
+
type: 'box',
|
|
321
|
+
halfX: ((x1 - x + 1) * cellSize) / 2,
|
|
322
|
+
halfY: ((y1 - y + 1) * cellSize) / 2,
|
|
323
|
+
halfZ: ((z1 - z + 1) * cellSize) / 2,
|
|
324
|
+
},
|
|
325
|
+
offset: {
|
|
326
|
+
x: ((x + x1) / 2) * cellSize,
|
|
327
|
+
y: ((y + y1) / 2) * cellSize,
|
|
328
|
+
z: ((z + z1) / 2) * cellSize,
|
|
329
|
+
},
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
return children;
|
|
335
|
+
}
|
|
336
|
+
/**
|
|
337
|
+
* Re-cooks compound collider boxes intersecting a dirty AABB cell region,
|
|
338
|
+
* iteratively expanding the re-cook volume to cover any intersecting previously-merged
|
|
339
|
+
* boxes so uncarved solid voxels are never dropped. Preserves disjoint children
|
|
340
|
+
* without full-chunk re-cooking. Returns undefined if 0 voxels remain across the chunk.
|
|
341
|
+
*/
|
|
342
|
+
export function cookDeterministicVoxelSubregion3D(chunk, existingShape, dirtyRegion) {
|
|
343
|
+
const { dimX, dimY, dimZ, cellSize } = chunk;
|
|
344
|
+
let activeMinX = Math.max(0, Math.min(dirtyRegion.minX, dirtyRegion.maxX));
|
|
345
|
+
let activeMaxX = Math.min(dimX - 1, Math.max(dirtyRegion.minX, dirtyRegion.maxX));
|
|
346
|
+
let activeMinY = Math.max(0, Math.min(dirtyRegion.minY, dirtyRegion.maxY));
|
|
347
|
+
let activeMaxY = Math.min(dimY - 1, Math.max(dirtyRegion.minY, dirtyRegion.maxY));
|
|
348
|
+
let activeMinZ = Math.max(0, Math.min(dirtyRegion.minZ, dirtyRegion.maxZ));
|
|
349
|
+
let activeMaxZ = Math.min(dimZ - 1, Math.max(dirtyRegion.minZ, dirtyRegion.maxZ));
|
|
350
|
+
if (activeMinX > activeMaxX || activeMinY > activeMaxY || activeMinZ > activeMaxZ) {
|
|
351
|
+
return existingShape;
|
|
352
|
+
}
|
|
353
|
+
// Iterative fixed-point expansion to absorb all intersecting merged boxes
|
|
354
|
+
let expanded = true;
|
|
355
|
+
while (expanded) {
|
|
356
|
+
expanded = false;
|
|
357
|
+
for (const child of existingShape.children) {
|
|
358
|
+
if (child.shape.type !== 'box') {
|
|
359
|
+
continue;
|
|
360
|
+
}
|
|
361
|
+
const boxMinX = Math.round((child.offset.x - child.shape.halfX) / cellSize + 0.5);
|
|
362
|
+
const boxMaxX = Math.round((child.offset.x + child.shape.halfX) / cellSize - 0.5);
|
|
363
|
+
const boxMinY = Math.round((child.offset.y - child.shape.halfY) / cellSize + 0.5);
|
|
364
|
+
const boxMaxY = Math.round((child.offset.y + child.shape.halfY) / cellSize - 0.5);
|
|
365
|
+
const boxMinZ = Math.round((child.offset.z - child.shape.halfZ) / cellSize + 0.5);
|
|
366
|
+
const boxMaxZ = Math.round((child.offset.z + child.shape.halfZ) / cellSize - 0.5);
|
|
367
|
+
const overlaps = boxMinX <= activeMaxX && boxMaxX >= activeMinX
|
|
368
|
+
&& boxMinY <= activeMaxY && boxMaxY >= activeMinY
|
|
369
|
+
&& boxMinZ <= activeMaxZ && boxMaxZ >= activeMinZ;
|
|
370
|
+
if (overlaps) {
|
|
371
|
+
if (boxMinX < activeMinX) {
|
|
372
|
+
activeMinX = Math.max(0, boxMinX);
|
|
373
|
+
expanded = true;
|
|
374
|
+
}
|
|
375
|
+
if (boxMaxX > activeMaxX) {
|
|
376
|
+
activeMaxX = Math.min(dimX - 1, boxMaxX);
|
|
377
|
+
expanded = true;
|
|
378
|
+
}
|
|
379
|
+
if (boxMinY < activeMinY) {
|
|
380
|
+
activeMinY = Math.max(0, boxMinY);
|
|
381
|
+
expanded = true;
|
|
382
|
+
}
|
|
383
|
+
if (boxMaxY > activeMaxY) {
|
|
384
|
+
activeMaxY = Math.min(dimY - 1, boxMaxY);
|
|
385
|
+
expanded = true;
|
|
386
|
+
}
|
|
387
|
+
if (boxMinZ < activeMinZ) {
|
|
388
|
+
activeMinZ = Math.max(0, boxMinZ);
|
|
389
|
+
expanded = true;
|
|
390
|
+
}
|
|
391
|
+
if (boxMaxZ > activeMaxZ) {
|
|
392
|
+
activeMaxZ = Math.min(dimZ - 1, boxMaxZ);
|
|
393
|
+
expanded = true;
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
// Preserve disjoint children
|
|
399
|
+
const preservedChildren = [];
|
|
400
|
+
for (const child of existingShape.children) {
|
|
401
|
+
if (child.shape.type !== 'box') {
|
|
402
|
+
preservedChildren.push(child);
|
|
403
|
+
continue;
|
|
404
|
+
}
|
|
405
|
+
const boxMinX = Math.round((child.offset.x - child.shape.halfX) / cellSize + 0.5);
|
|
406
|
+
const boxMaxX = Math.round((child.offset.x + child.shape.halfX) / cellSize - 0.5);
|
|
407
|
+
const boxMinY = Math.round((child.offset.y - child.shape.halfY) / cellSize + 0.5);
|
|
408
|
+
const boxMaxY = Math.round((child.offset.y + child.shape.halfY) / cellSize - 0.5);
|
|
409
|
+
const boxMinZ = Math.round((child.offset.z - child.shape.halfZ) / cellSize + 0.5);
|
|
410
|
+
const boxMaxZ = Math.round((child.offset.z + child.shape.halfZ) / cellSize - 0.5);
|
|
411
|
+
const overlaps = boxMinX <= activeMaxX && boxMaxX >= activeMinX
|
|
412
|
+
&& boxMinY <= activeMaxY && boxMaxY >= activeMinY
|
|
413
|
+
&& boxMinZ <= activeMaxZ && boxMaxZ >= activeMinZ;
|
|
414
|
+
if (!overlaps) {
|
|
415
|
+
preservedChildren.push(child);
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
// Cook newly carved/filled subregion
|
|
419
|
+
const subCooked = cookSubregionBoxes(chunk, activeMinX, activeMinY, activeMinZ, activeMaxX, activeMaxY, activeMaxZ);
|
|
420
|
+
const finalChildren = [...preservedChildren, ...subCooked];
|
|
421
|
+
if (finalChildren.length === 0) {
|
|
422
|
+
return undefined;
|
|
423
|
+
}
|
|
424
|
+
return {
|
|
425
|
+
type: 'compound',
|
|
426
|
+
children: finalChildren,
|
|
427
|
+
};
|
|
428
|
+
}
|
package/dist/physics3d.d.ts
CHANGED
|
@@ -58,6 +58,24 @@ export type DeterministicPhysicsShape3D = DeterministicPhysicsPrimitiveShape3D |
|
|
|
58
58
|
readonly type: 'compound';
|
|
59
59
|
readonly children: readonly DeterministicPhysicsCompoundChild3D[];
|
|
60
60
|
};
|
|
61
|
+
/**
|
|
62
|
+
* Creates a centered axis-aligned box shape from full dimensional sizes (sizeX, sizeY, sizeZ).
|
|
63
|
+
*/
|
|
64
|
+
export declare function createBoxShapeFromExtents(sizeX: number, sizeY: number, sizeZ: number): Extract<DeterministicPhysicsShape3D, {
|
|
65
|
+
readonly type: 'box';
|
|
66
|
+
}>;
|
|
67
|
+
/**
|
|
68
|
+
* Creates a box shape from min and max bounding coordinates.
|
|
69
|
+
* Returns a simple box if centered at (0,0,0), or a single-child compound with offset if not centered.
|
|
70
|
+
*/
|
|
71
|
+
export declare function createBoxShapeFromMinMax(min: Point3D, max: Point3D): DeterministicPhysicsShape3D;
|
|
72
|
+
/**
|
|
73
|
+
* Creates an upright capsule shape anchored at its base (y = 0), matching character controller conventions.
|
|
74
|
+
* Encapsulates the offset into a single-child compound shape centered at y = halfHeight + radius.
|
|
75
|
+
*/
|
|
76
|
+
export declare function createCapsuleShapeFromBase(radius: number, totalHeight: number): Extract<DeterministicPhysicsShape3D, {
|
|
77
|
+
readonly type: 'compound';
|
|
78
|
+
}>;
|
|
61
79
|
export declare const deterministicPhysicsCallback3D: {
|
|
62
80
|
readonly collisionEnter: 1;
|
|
63
81
|
readonly collisionStay: 2;
|
|
@@ -202,6 +220,28 @@ export interface DeterministicPhysicsDetailedHit3D extends DeterministicPhysicsH
|
|
|
202
220
|
readonly point: Point3D;
|
|
203
221
|
readonly normal: Point3D;
|
|
204
222
|
readonly penetration: number;
|
|
223
|
+
readonly childIndex?: number;
|
|
224
|
+
}
|
|
225
|
+
export interface DeterministicKinematicPose3D {
|
|
226
|
+
readonly id: string;
|
|
227
|
+
readonly x: number;
|
|
228
|
+
readonly y: number;
|
|
229
|
+
readonly z: number;
|
|
230
|
+
readonly rx?: number;
|
|
231
|
+
readonly ry?: number;
|
|
232
|
+
readonly rz?: number;
|
|
233
|
+
readonly orientation?: DeterministicPhysicsQuaternion3D;
|
|
234
|
+
readonly vx?: number;
|
|
235
|
+
readonly vy?: number;
|
|
236
|
+
readonly vz?: number;
|
|
237
|
+
readonly angularVelX?: number;
|
|
238
|
+
readonly angularVelY?: number;
|
|
239
|
+
readonly angularVelZ?: number;
|
|
240
|
+
}
|
|
241
|
+
export interface DeterministicKinematicTransformBuffer3D {
|
|
242
|
+
readonly bodyIds: readonly string[];
|
|
243
|
+
/** Flat [x, y, z, qx, qy, qz, qw] buffer per body. Length must be bodyIds.length * 7. */
|
|
244
|
+
readonly transforms: Float64Array | Float32Array | readonly number[];
|
|
205
245
|
}
|
|
206
246
|
export interface DeterministicPhysicsScheduledQuery3D {
|
|
207
247
|
readonly id: string;
|
|
@@ -363,6 +403,23 @@ export interface DeterministicPhysicsWorldEdit3D {
|
|
|
363
403
|
* inside each edit list.
|
|
364
404
|
*/
|
|
365
405
|
export declare function applyDeterministicWorldEdit3D(world: DeterministicPhysicsWorld3D, edit: DeterministicPhysicsWorldEdit3D): DeterministicPhysicsWorld3D;
|
|
406
|
+
/**
|
|
407
|
+
* Fast batch update of kinematic body transforms and velocities without
|
|
408
|
+
* allocating Set instances or re-sorting world bodies. Wakes touching dynamic
|
|
409
|
+
* bodies across active contact pairs. If linear velocity (vx, vy, vz) is omitted
|
|
410
|
+
* and dtTicks (> 0) is supplied, linear velocity is derived as (newPos - oldPos) / dtTicks.
|
|
411
|
+
*/
|
|
412
|
+
export declare function setDeterministicKinematicPoses3D(world: DeterministicPhysicsWorld3D, poses: readonly DeterministicKinematicPose3D[], options?: {
|
|
413
|
+
readonly dtTicks?: number;
|
|
414
|
+
}): DeterministicPhysicsWorld3D;
|
|
415
|
+
/**
|
|
416
|
+
* Allocation-free flat buffer batch update of kinematic body transforms.
|
|
417
|
+
* Accepts flat [x, y, z, qx, qy, qz, qw] arrays with stride 7 per bodyId,
|
|
418
|
+
* eliminating caller-side object allocations on high-frequency animation loops.
|
|
419
|
+
*/
|
|
420
|
+
export declare function setDeterministicKinematicTransforms3D(world: DeterministicPhysicsWorld3D, buffer: DeterministicKinematicTransformBuffer3D, options?: {
|
|
421
|
+
readonly dtTicks?: number;
|
|
422
|
+
}): DeterministicPhysicsWorld3D;
|
|
366
423
|
/**
|
|
367
424
|
* Apply a deterministic impulse to a body (linear + angular via lever arm),
|
|
368
425
|
* waking the body and its contact island. Returns a new canonical world.
|