avbd-babylon 0.2.0 → 0.4.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.
@@ -2,6 +2,18 @@ import type { Vector3, Quaternion } from "@babylonjs/core/Maths/math.vector.js";
2
2
  import type { AbstractMesh } from "@babylonjs/core/Meshes/abstractMesh.js";
3
3
  import type { Scene } from "@babylonjs/core/scene.js";
4
4
  import type { PhysicsShapeType } from "@babylonjs/core/Physics/v2/IPhysicsEnginePlugin.js";
5
+ import type {
6
+ AvbdSleepOptions,
7
+ AvbdHeightfieldOptions,
8
+ AvbdCookedTerrain,
9
+ AvbdTerrainMaterial,
10
+ } from "./nativeScenes.js";
11
+ export interface AvbdTerrain {
12
+ readonly bodies: AvbdPhysicsBody[];
13
+ readonly triangleCount: number;
14
+ readonly kind: "heightfield" | "static-mesh";
15
+ dispose(): void;
16
+ }
5
17
  export const AvbdShapeType: Readonly<{
6
18
  BOX: "box";
7
19
  SPHERE: "sphere";
@@ -31,9 +43,11 @@ export interface AvbdWorldOptions {
31
43
  maxSyncedBodies?: number;
32
44
  broadphase?: "auto" | "grid" | "hploc";
33
45
  solverMode?: AvbdSolverMode;
46
+ /** Automatically batch a closed jointless world of up to 32 bodies. */
47
+ smallGroups?: boolean;
34
48
  bvh?: { rebuildInterval?: number };
35
- /** Optional conservative GPU sleeping. Impacts and edits wake the world. */
36
- sleeping?: boolean | { speedThreshold?: number; timeThreshold?: number };
49
+ /** Optional GPU sleeping. Large worlds wake conservative connected groups on impact. */
50
+ sleeping?: boolean | AvbdSleepOptions;
37
51
  maxContactPairs?: number;
38
52
  maxContactEvents?: number;
39
53
  }
@@ -116,6 +130,17 @@ export class AvbdPhysicsConstraint {
116
130
  dispose(): void;
117
131
  }
118
132
  export class AvbdPhysics {
133
+ /** Register static collision pieces before initialize(); reserve body capacity. */
134
+ addHeightfield(options: AvbdHeightfieldOptions): AvbdTerrain;
135
+ /** Captures the mesh world transform; later mesh edits do not move terrain. */
136
+ addStaticMesh(
137
+ mesh: AbstractMesh,
138
+ options?: AvbdTerrainMaterial & { thickness?: number },
139
+ ): AvbdTerrain;
140
+ addTerrain(
141
+ cooked: AvbdCookedTerrain,
142
+ options?: AvbdTerrainMaterial,
143
+ ): AvbdTerrain;
119
144
  sphereCast(
120
145
  origin: AvbdVector,
121
146
  radius: number,
@@ -151,10 +176,9 @@ export class AvbdPhysics {
151
176
  readonly solverDecision: AvbdSolverDecision;
152
177
  setSolverMode(mode: AvbdSolverMode): AvbdSolverDecision;
153
178
  setGravity(gravity: AvbdVector): void;
154
- enableSleeping(options?: {
155
- speedThreshold?: number;
156
- timeThreshold?: number;
157
- }): this;
179
+ enableSleeping(options?: AvbdSleepOptions): this;
180
+ /** Wake all sleeping bodies and disable automatic sleeping. */
181
+ disableSleeping(): this;
158
182
  initialize(): void;
159
183
  step(): void;
160
184
  flushCommands(): void;
@@ -281,5 +305,6 @@ export class AvbdPhysicsBody {
281
305
  effectiveMass: number;
282
306
  sleeping: boolean;
283
307
  }>;
308
+ /** Mesh-origin position; preserves live rotation and velocities when omitted. */
284
309
  teleport(position: Vector3, rotation?: Quaternion): this;
285
310
  }
package/dist/native.d.ts CHANGED
@@ -1,2 +1,3 @@
1
1
  export * from "./nativeScenes.js";
2
2
  export * from "./gpuDevice.js";
3
+ export * from "./solverPipeline.js";
package/dist/native.js CHANGED
@@ -1 +1 @@
1
- export { AvbdScene2D, AvbdScene3D, createWebGPUDevice, prepareWebGPUDevice3D } from "./avbd.js";
1
+ export { AvbdScene2D, AvbdScene3D, createWebGPUDevice, prepareWebGPUDevice3D, createSolverPipeline, cookHeightfield, cookStaticMesh } from "./avbd.js";
@@ -1,3 +1,8 @@
1
+ import type {
2
+ AvbdSolverPipeline,
3
+ AvbdSolver2D,
4
+ AvbdSolver3D,
5
+ } from "./solverPipeline.js";
1
6
  export type AvbdTuple3 = readonly [number, number, number];
2
7
  export type AvbdTuple2 = readonly [number, number];
3
8
  export interface AvbdQueryOptions {
@@ -22,6 +27,55 @@ export interface AvbdQueryHit<V> {
22
27
  center: V;
23
28
  }
24
29
  export type AvbdQuaternion = readonly [number, number, number, number];
30
+ export interface AvbdTerrainMaterial {
31
+ friction?: number;
32
+ restitution?: number;
33
+ group?: number;
34
+ collidesWith?: number;
35
+ mass?: 0;
36
+ }
37
+ export interface AvbdHeightfieldOptions extends AvbdTerrainMaterial {
38
+ heights: ArrayLike<number>;
39
+ columns: number;
40
+ rows: number;
41
+ spacing?: number | AvbdTuple2;
42
+ origin?: AvbdTuple3;
43
+ upAxis?: 0 | 1 | 2;
44
+ /** Local height below every sample; defaults to the minimum height minus 2. */
45
+ baseHeight?: number;
46
+ /** Row-major cells; a nonzero entry leaves a hole. */
47
+ holes?: ArrayLike<number>;
48
+ }
49
+ export interface AvbdStaticMeshOptions extends AvbdTerrainMaterial {
50
+ /** World-space xyz triples. */
51
+ positions: ArrayLike<number>;
52
+ indices?: ArrayLike<number>;
53
+ /** Backing behind triangle winding, in world units. Defaults to 0.1. */
54
+ thickness?: number;
55
+ }
56
+ /** Serializable preprocessing output; can be cooked in a separate worker. */
57
+ export interface AvbdCookedTerrain {
58
+ kind: "heightfield" | "static-mesh";
59
+ triangleCount: number;
60
+ pieces: Array<{
61
+ shape: object;
62
+ position: AvbdTuple3;
63
+ rotation: AvbdQuaternion;
64
+ }>;
65
+ }
66
+ export interface AvbdNativeTerrain {
67
+ readonly bodies: AvbdRigid[];
68
+ readonly triangleCount: number;
69
+ readonly kind: "heightfield" | "static-mesh";
70
+ /** Disable collision participation; live indices remain stable. */
71
+ dispose(): void;
72
+ }
73
+ export function cookHeightfield(
74
+ options: AvbdHeightfieldOptions,
75
+ ): AvbdCookedTerrain;
76
+ export function cookStaticMesh(
77
+ options: AvbdStaticMeshOptions,
78
+ ): AvbdCookedTerrain;
25
79
  export interface AvbdNativeState3D {
26
80
  position: [number, number, number];
27
81
  rotation: [number, number, number, number];
@@ -186,6 +240,13 @@ export interface AvbdGpuParameters2D extends AvbdGpuParameters {
186
240
  vbd: boolean;
187
241
  vbdStiffness: number;
188
242
  }
243
+ /** 3D can retain conservative connected groups between explicit world edits. */
244
+ export interface AvbdSleepOptions {
245
+ speedThreshold?: number;
246
+ timeThreshold?: number;
247
+ /** 3D: automatic for large jointless desktop worlds; false selects whole-world waking. */
248
+ islands?: boolean;
249
+ }
189
250
  export interface AvbdGpuOptions {
190
251
  bodyBuffer?: GPUBuffer;
191
252
  bodyCapacity?: number;
@@ -193,6 +254,8 @@ export interface AvbdGpuOptions {
193
254
  spatialSort?: boolean;
194
255
  broadphase?: "auto" | "grid" | "hploc";
195
256
  solverMode?: "auto" | "standard" | "optimized" | "points";
257
+ /** 3D: automatically fuse closed jointless worlds of at most 32 bodies. */
258
+ smallGroups?: boolean;
196
259
  bvh?: { rebuildInterval?: number };
197
260
  capacity?: {
198
261
  contacts?: number;
@@ -201,9 +264,10 @@ export interface AvbdGpuOptions {
201
264
  colors?: number;
202
265
  joints?: number;
203
266
  };
267
+ /** Reserve coloring capacity when sleeping bodies wake. */
204
268
  minimumColors?: number;
205
269
  minimumColorRounds?: number;
206
- sleeping?: boolean | { speedThreshold?: number; timeThreshold?: number };
270
+ sleeping?: boolean | AvbdSleepOptions;
207
271
  }
208
272
  export interface AvbdGpuSolver {
209
273
  readonly bodyBuffer: GPUBuffer;
@@ -273,10 +337,7 @@ export interface AvbdGpuSolver3D extends AvbdGpuSolver {
273
337
  rotation?: AvbdQuaternion,
274
338
  ): this;
275
339
  readBodyState(index: number): Promise<AvbdNativeState3D>;
276
- enableSleeping(options?: {
277
- speedThreshold?: number;
278
- timeThreshold?: number;
279
- }): this;
340
+ enableSleeping(options?: AvbdSleepOptions): this;
280
341
  disableSleeping(): this;
281
342
  setSleepEnabled(index: number, enabled: boolean): this;
282
343
  watchContacts(options?: AvbdNativeContactOptions): this;
@@ -508,6 +569,12 @@ export interface AvbdNativeConstraint {
508
569
  destroy(): void;
509
570
  }
510
571
  export class AvbdScene3D {
572
+ addHeightfield(options: AvbdHeightfieldOptions): AvbdNativeTerrain;
573
+ addStaticMesh(options: AvbdStaticMeshOptions): AvbdNativeTerrain;
574
+ addTerrain(
575
+ cooked: AvbdCookedTerrain,
576
+ options?: AvbdTerrainMaterial,
577
+ ): AvbdNativeTerrain;
511
578
  addMotor(
512
579
  a: AvbdRigid | null,
513
580
  b: AvbdRigid,
@@ -620,6 +687,10 @@ export class AvbdScene3D {
620
687
  },
621
688
  ): AvbdNativeConstraint;
622
689
  createSolver(device: GPUDevice, options?: AvbdGpuOptions): AvbdGpuSolver3D;
690
+ createSolver(
691
+ pipeline: AvbdSolverPipeline,
692
+ options?: AvbdGpuOptions,
693
+ ): AvbdSolver3D;
623
694
  }
624
695
  export interface AvbdNativeAngularOptions {
625
696
  axisA?: AvbdTuple3;
@@ -729,7 +800,12 @@ export class AvbdScene2D {
729
800
  bodyBuffer?: GPUBuffer;
730
801
  bodyCapacity?: number;
731
802
  colorRounds?: number;
803
+ minimumColors?: number;
732
804
  sleeping?: boolean | { speedThreshold?: number; timeThreshold?: number };
733
805
  },
734
806
  ): AvbdGpuSolver2D;
807
+ createSolver(
808
+ pipeline: AvbdSolverPipeline,
809
+ options?: AvbdGpuOptions,
810
+ ): AvbdSolver2D;
735
811
  }
@@ -0,0 +1,62 @@
1
+ import type {
2
+ AvbdScene2D,
3
+ AvbdScene3D,
4
+ AvbdGpuOptions,
5
+ AvbdGpuSolver,
6
+ AvbdGpuSolver2D,
7
+ AvbdGpuSolver3D,
8
+ AvbdAdvanceOptions,
9
+ } from "./nativeScenes.js";
10
+ import type { createWebGPUDevice } from "./gpuDevice.js";
11
+
12
+ export type AvbdSolverBackend = "webgpu" | "cpu";
13
+ /** Shared world settings. GPU scheduling fields apply only to GPU execution. */
14
+ export type AvbdSolverOptions = AvbdGpuOptions;
15
+ /** Drawing buffers belong to the selected backend; simulation operations are shared. */
16
+ type SharedMethods<T, Self> = {
17
+ [K in keyof T]: T[K] extends (...args: infer A) => infer R
18
+ ? (...args: A) => R extends AvbdGpuSolver ? Self : R
19
+ : T[K];
20
+ };
21
+ export interface AvbdSolver2D extends SharedMethods<
22
+ Omit<AvbdGpuSolver2D, "bodyBuffer" | "shapeBuffer" | "attachToScene">,
23
+ AvbdSolver2D
24
+ > {
25
+ readonly backend: AvbdSolverBackend;
26
+ readonly bodyBuffer?: GPUBuffer;
27
+ readonly shapeBuffer?: GPUBuffer;
28
+ attachToScene(
29
+ scene: Parameters<AvbdGpuSolver2D["attachToScene"]>[0],
30
+ options?: AvbdAdvanceOptions & {
31
+ afterStep?: (solver: AvbdSolver2D) => void;
32
+ },
33
+ ): this;
34
+ }
35
+ export interface AvbdSolver3D extends SharedMethods<
36
+ Omit<AvbdGpuSolver3D, "bodyBuffer" | "attachToScene">,
37
+ AvbdSolver3D
38
+ > {
39
+ readonly backend: AvbdSolverBackend;
40
+ readonly bodyBuffer?: GPUBuffer;
41
+ attachToScene(
42
+ scene: Parameters<AvbdGpuSolver3D["attachToScene"]>[0],
43
+ options?: AvbdAdvanceOptions & {
44
+ afterStep?: (solver: AvbdSolver3D) => void;
45
+ },
46
+ ): this;
47
+ }
48
+ export interface AvbdSolverPipeline {
49
+ readonly backend: AvbdSolverBackend;
50
+ readonly device?: GPUDevice;
51
+ readonly adapter?: GPUAdapter;
52
+ createSolver(scene: AvbdScene2D, options?: AvbdSolverOptions): AvbdSolver2D;
53
+ createSolver(scene: AvbdScene3D, options?: AvbdSolverOptions): AvbdSolver3D;
54
+ /** Destroys its worlds and its own device. A supplied device remains caller-owned. */
55
+ destroy(): void;
56
+ }
57
+ export function createSolverPipeline(
58
+ options?: NonNullable<Parameters<typeof createWebGPUDevice>[0]> & {
59
+ backend?: AvbdSolverBackend;
60
+ device?: GPUDevice;
61
+ },
62
+ ): Promise<AvbdSolverPipeline>;
@@ -0,0 +1,161 @@
1
+ /** Nonblocking GPU pass timestamps; null when timestamp-query is unavailable. */
2
+ export class GpuTimer {
3
+ constructor(device: GPUDevice);
4
+ readonly latestMs: number | null;
5
+ readonly samples: number;
6
+ begin(): object | null;
7
+ descriptor(slot: object | null): {
8
+ timestampWrites?: GPUComputePassTimestampWrites;
9
+ };
10
+ encode(encoder: GPUCommandEncoder, slot: object | null): void;
11
+ resolve(slot: object | null): Promise<void> | undefined;
12
+ settled(): Promise<unknown[]>;
13
+ destroy(): void;
14
+ }
15
+ /** Reuse released buffers with compatible usage, without CPU pose transfers. */
16
+ export class GPUBufferRecycler {
17
+ constructor(device: GPUDevice, options?: { maxFreeBytes?: number });
18
+ readonly freeBytes: number;
19
+ acquire(
20
+ byteSize: number,
21
+ usage: GPUBufferUsageFlags,
22
+ label?: string,
23
+ ): GPUBuffer;
24
+ release(
25
+ buffer: GPUBuffer,
26
+ byteSize: number,
27
+ usage: GPUBufferUsageFlags,
28
+ ): void;
29
+ trim(maxFreeBytes?: number): void;
30
+ destroy(): void;
31
+ }
32
+ export const PARTICLE_BYTE_STRIDE: 48;
33
+ export interface SurfaceParticleStep {
34
+ dt: number;
35
+ count?: number;
36
+ speed?: number;
37
+ life?: number;
38
+ bulletRadius?: number;
39
+ spawnRadius?: number;
40
+ jitter?: number;
41
+ frame?: number;
42
+ }
43
+ /** Spherical emitter aimed at the origin. Particles hit triangle surfaces and
44
+ * respawn; they ignore each other. Five rays approximate the particle sweep. */
45
+ export class GpuSurfaceParticles {
46
+ constructor(
47
+ device: GPUDevice,
48
+ options: { capacity: number; triangleCount: number; readback?: boolean },
49
+ );
50
+ readonly device: GPUDevice;
51
+ readonly capacity: number;
52
+ readonly triangleCount: number;
53
+ /** 48 bytes: position/life vec4f, velocity/radius vec4f, seed vec4u. */
54
+ readonly particleBuffer: GPUBuffer;
55
+ readonly triangleBuffer: GPUBuffer;
56
+ readonly counterBuffer: GPUBuffer;
57
+ readonly timer: GpuTimer;
58
+ /** 12 float32 values per triangle: [ax,ay,az,0,bx,by,bz,0,cx,cy,cz,0]. */
59
+ writeTriangles(vertices: Float32Array): void;
60
+ /** Returns the latest asynchronous counters, or null with readback disabled. */
61
+ step(options: SurfaceParticleStep): { hits: number; alive: number } | null;
62
+ clear(): void;
63
+ destroy(): void;
64
+ }
65
+ export const sampledContactShader: string;
66
+ /** Gather a bounded GPU contact overlay, without reading the full contact set. */
67
+ export class GpuContactSample {
68
+ constructor(device: GPUDevice, options?: { count?: number });
69
+ readonly count: number;
70
+ readonly buffer: GPUBuffer;
71
+ encode(
72
+ encoder: GPUCommandEncoder,
73
+ resources: {
74
+ bodyBuffer: GPUBuffer;
75
+ /** Native 3D camera uniform: mat4 vp, vec4 eye, vec4 light/viewport. */
76
+ cameraBuffer: GPUBuffer;
77
+ contactStorage: {
78
+ manifolds: GPUBuffer;
79
+ contacts: GPUBuffer;
80
+ counters: GPUBuffer;
81
+ };
82
+ },
83
+ ): void;
84
+ dispose(): void;
85
+ }
86
+ /** Optional one-time native constraint-graph seed; live GPU coloring still checks contacts. */
87
+ export function seedConstraintColoring(
88
+ gpu: {
89
+ device: GPUDevice;
90
+ bodyCapacity: number;
91
+ colorBuffer: GPUBuffer;
92
+ colorCap: number;
93
+ gpuIndex(index: number): number;
94
+ },
95
+ world: {
96
+ bodies: { mass: number }[];
97
+ forces: { bodyA: object | null; bodyB: object | null }[];
98
+ },
99
+ minimum?: number,
100
+ ): void;
101
+ export const INPUT_WORDS: 9;
102
+ /** Custom compute/copy work in the solver's existing command encoder. Callbacks
103
+ * are synchronous, end their own passes, and must not submit or finish the encoder.
104
+ * Dispose the registration before destroying its buffers. CPU solvers are rejected. */
105
+ export function addGpuStepPass<
106
+ T extends { bodyBuffer: GPUBuffer; step(): void },
107
+ >(
108
+ solver: T,
109
+ hooks: {
110
+ before?: (encoder: GPUCommandEncoder, solver: T) => void;
111
+ after?: (encoder: GPUCommandEncoder, solver: T) => void;
112
+ },
113
+ ): () => void;
114
+ /** DOM-free sizing and event adapter around a caller-owned canvas. */
115
+ export class WorkerCanvas {
116
+ constructor(
117
+ canvas: OffscreenCanvas | HTMLCanvasElement,
118
+ width: number,
119
+ height: number,
120
+ );
121
+ readonly offscreenCanvas: OffscreenCanvas | HTMLCanvasElement;
122
+ readonly clientWidth: number;
123
+ readonly clientHeight: number;
124
+ readonly revision: number;
125
+ width: number;
126
+ height: number;
127
+ resize(width: number, height: number): void;
128
+ getContext(type: "webgpu"): GPUCanvasContext | null;
129
+ getContext(type: string, options?: object): unknown;
130
+ getBoundingClientRect(): {
131
+ left: number;
132
+ top: number;
133
+ width: number;
134
+ height: number;
135
+ };
136
+ addEventListener(type: string, callback: (event: any) => void): void;
137
+ removeEventListener(type: string, callback: (event: any) => void): void;
138
+ setPointerCapture(): void;
139
+ dispatch(type: string, data: object): void;
140
+ }
141
+ export interface SharedPointerState {
142
+ clientX: number;
143
+ clientY: number;
144
+ pointerId?: number;
145
+ buttons?: number;
146
+ altKey?: boolean;
147
+ shiftKey?: boolean;
148
+ ctrlKey?: boolean;
149
+ }
150
+ /** Returns null without cross-origin isolation / SharedArrayBuffer support. */
151
+ export function createSharedInput(): Int32Array<SharedArrayBuffer> | null;
152
+ /** One producer; continuous state only. Send discrete events as ordered messages. */
153
+ export function writeSharedInput(
154
+ view: Int32Array<SharedArrayBuffer>,
155
+ event: SharedPointerState,
156
+ wheel?: number,
157
+ ): void;
158
+ export function readSharedInput(
159
+ view: Int32Array<SharedArrayBuffer>,
160
+ previous?: number,
161
+ ): { sequence: number; event: SharedPointerState; wheel: number } | null;
@@ -0,0 +1 @@
1
+ export { GpuTimer, GPUBufferRecycler, GpuSurfaceParticles, PARTICLE_BYTE_STRIDE, GpuContactSample, sampledContactShader, seedConstraintColoring, addGpuStepPass, WorkerCanvas, INPUT_WORDS, createSharedInput, writeSharedInput, readSharedInput } from "./avbd.js";
@@ -1,5 +1,21 @@
1
1
  # Babylon.js integration
2
2
 
3
+ Static terrain can be added with `physics.addHeightfield(options)` or
4
+ `physics.addStaticMesh(mesh, { thickness, friction, group, collidesWith })`.
5
+ The mesh helper captures current world-space triangles, including parent transforms.
6
+ Both return `{ bodies, triangleCount, kind, dispose() }`. Add terrain before
7
+ `initialize()` or the first render, and reserve body capacity for its collision
8
+ pieces. These pieces do not synchronize meshes each frame. `dispose()` disables
9
+ their collisions while leaving the caller's mesh intact.
10
+
11
+ Heightfields use two solid prisms per cell. Mesh terrain gives each triangle
12
+ finite backing behind its winding; concavities and overhangs remain open. This
13
+ uses the existing convex contact and grid/HPLOC pipelines. Internal tile edges
14
+ can affect sliding, and a rendering mesh should usually have a simpler collision
15
+ mesh. Terrain is static and does not track subsequent mesh transformations.
16
+ See [terrain examples](../README.md#static-terrain-hills-caves-and-overhangs)
17
+ for the native API, holes, coordinate conventions and worker cooking helpers.
18
+
3
19
  Start with the [integration README](README.md) for a complete scene, bundler setup,
4
20
  instance rendering examples and reproducible collision/solver benchmarks.
5
21
 
@@ -79,8 +95,10 @@ impacts below 1 m/s do not bounce. Use `body.setRestitution()` for live changes.
79
95
  `isTrigger:true` detects dynamic-body overlaps without contact forces;
80
96
  `body.setTrigger()` changes the mode. Events include `isTrigger`, and masks
81
97
  apply to sensors too. Static–static pairs are not scanned. CCD,
82
- arbitrary concave meshes and the full Physics V2 plugin
83
- interface are not implemented. Static bodies given velocity move kinematically.
98
+ dynamic concave meshes and the full Physics V2 plugin
99
+ interface are not implemented. Static triangle meshes and heightfields use
100
+ `addStaticMesh()` and `addHeightfield()` terrain cooking, including caves and
101
+ overhangs. Static bodies given velocity move kinematically.
84
102
 
85
103
  `addHinge(a,b,{axisA,axisB,anchorA,anchorB,minAngle,maxAngle,motor})` constrains
86
104
  rotation to one axis, with optional angle stops and a torque-limited motor.
@@ -129,18 +147,29 @@ reserve storage; `readContactEvents()` reports dropped events or a full pair
129
147
  table. End events do not retain an old position. Callbacks run asynchronously.
130
148
 
131
149
  `sleeping: true` or `{ speedThreshold: 0.03, timeThreshold: 0.5 }` enables an
132
- optional GPU policy. Supported quiet bodies sleep; impacts, moving joint
133
- endpoints and explicit edits wake the world conservatively. Unsupported bodies
150
+ optional GPU policy. Supported quiet bodies sleep. Large jointless desktop 3D worlds
151
+ automatically wake conservative connected groups on impact, keeping unrelated
152
+ piles asleep; the shared static floor does not join them. `islands:true` opts
153
+ into group waking in smaller worlds, and `islands:false` selects whole-world
154
+ waking. Explicit edits wake the world and reset group history. Unsupported bodies
134
155
  under gravity stay awake. `wakeAll()`, `body.wakeUp()` and `readSleepStats()` are
135
156
  available. Suspended jointed objects are not guaranteed to sleep. Defaults and
136
157
  paper-comparison benchmarks keep sleeping disabled.
137
158
 
159
+ Closed jointless 3D worlds with at most 32 bodies automatically batch movement
160
+ iterations in one GPU workgroup. Body poses stay in shared memory, with the
161
+ same contact order, reductions and equations. Adding a joint or exceeding the
162
+ body limit selects the ordinary pipeline. Qualified mobile compatibility paths
163
+ retain their existing execution policy. `smallGroups:false` disables batching
164
+ for controlled comparisons.
165
+
138
166
  `body.setLinearVelocity()`, `setAngularVelocity()`, `applyImpulse()`,
139
167
  `applyAngularImpulse()` and `teleport(position, quaternion)` enqueue compact
140
168
  GPU commands. Commands for the same body are combined before submission;
141
169
  impulses use the current GPU mass, inertia and position. They never replace a
142
170
  body with a stale CPU pose. `teleport` retains velocity unless a velocity setter
143
- is also called. New aggregates can be added within reserved capacity after
171
+ is also called. Omitting its quaternion preserves the live GPU rotation and
172
+ places the mesh origin at the requested position. New aggregates can be added within reserved capacity after
144
173
  initialization; disposed body slots are not reused. For repeated spawning,
145
174
  reserve projectile bodies and teleport them. `applyForce()` and `applyTorque()`
146
175
  apply forces for one fixed step, using impulses on live GPU state.