avbd-babylon 0.1.0 → 0.3.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/README.md +2793 -2562
- package/dist/avbd.d.ts +1 -0
- package/dist/avbd.global.js +232 -138
- package/dist/avbd.js +232 -138
- package/dist/gpuDevice.d.ts +4 -0
- package/dist/native.d.ts +1 -0
- package/dist/native.js +1 -1
- package/dist/nativeScenes.d.ts +3 -0
- package/dist/solverPipeline.d.ts +62 -0
- package/docs/BABYLON_API.md +8 -0
- package/docs/README.md +465 -459
- package/docs/RELEASE.md +176 -122
- package/package.json +124 -116
- package/tests/README.md +218 -168
package/docs/README.md
CHANGED
|
@@ -1,459 +1,465 @@
|
|
|
1
|
-
# Integrating AVBD into a Babylon.js project
|
|
2
|
-
|
|
3
|
-
Use `AvbdPhysics` for GPU collision detection and rigid-body simulation, with
|
|
4
|
-
Babylon meshes for the interactive view. The aggregate/body methods follow
|
|
5
|
-
familiar Babylon conventions: mass, friction, velocities, impulses and disposal.
|
|
6
|
-
AVBD computes body motion and contacts on the GPU; Babylon renders the results.
|
|
7
|
-
This adapter has its own world; it does not implement Babylon's full
|
|
8
|
-
Physics V2 plugin interface or install through `scene.enablePhysics()`.
|
|
9
|
-
|
|
10
|
-
**Jump to:**
|
|
11
|
-
|
|
12
|
-
- [Install the library](#install-the-library)
|
|
13
|
-
- [A complete small scene](#a-complete-small-scene)
|
|
14
|
-
- [Move, launch and reuse bodies](#move-launch-and-reuse-bodies)
|
|
15
|
-
- [Choose a rendering path](#choose-a-rendering-path)
|
|
16
|
-
- [Reproduce the collision and solver benchmarks](#reproduce-the-collision-and-solver-benchmarks)
|
|
17
|
-
- [Current shape and API support](#current-shape-and-api-support)
|
|
18
|
-
- [Choosing collision detection](#choosing-collision-detection)
|
|
19
|
-
|
|
20
|
-
## Install the library
|
|
21
|
-
|
|
22
|
-
The package is named `avbd-babylon`. For a local build, run `npm install` and
|
|
23
|
-
`npm run build` in this repository. From your
|
|
24
|
-
Babylon project, run `npm install /absolute/path/to/avbd` or install the archive
|
|
25
|
-
created by `npm pack`. Import from `avbd-babylon` using your normal bundler.
|
|
26
|
-
The ESM output already contains the solver and shader strings. No TypeScript
|
|
27
|
-
source compilation or WGSL loader is needed in your project. The package has
|
|
28
|
-
no Babylon runtime imports; it uses the mesh objects your project provides.
|
|
29
|
-
|
|
30
|
-
`npm run build` builds only the library. `npm run build:web` builds the lab and
|
|
31
|
-
browser tests. `npm start` builds the library and serves the lab together.
|
|
32
|
-
`npm run build:cdn` creates `dist/avbd.global.js`, which exposes `globalThis.AVBD`.
|
|
33
|
-
Host that file yourself and load it with a normal script tag; use
|
|
34
|
-
`const { AvbdPhysics, AvbdPhysicsAggregate, AvbdShapeType } = AVBD`.
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
);
|
|
79
|
-
|
|
80
|
-
const
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
);
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
);
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
fallingBox.body.setLinearVelocity(new Vector3(0, 0,
|
|
133
|
-
fallingBox.body.
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
physics.
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
The
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
}),
|
|
302
|
-
);
|
|
303
|
-
const
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
They
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
1
|
+
# Integrating AVBD into a Babylon.js project
|
|
2
|
+
|
|
3
|
+
Use `AvbdPhysics` for GPU collision detection and rigid-body simulation, with
|
|
4
|
+
Babylon meshes for the interactive view. The aggregate/body methods follow
|
|
5
|
+
familiar Babylon conventions: mass, friction, velocities, impulses and disposal.
|
|
6
|
+
AVBD computes body motion and contacts on the GPU; Babylon renders the results.
|
|
7
|
+
This adapter has its own world; it does not implement Babylon's full
|
|
8
|
+
Physics V2 plugin interface or install through `scene.enablePhysics()`.
|
|
9
|
+
|
|
10
|
+
**Jump to:**
|
|
11
|
+
|
|
12
|
+
- [Install the library](#install-the-library)
|
|
13
|
+
- [A complete small scene](#a-complete-small-scene)
|
|
14
|
+
- [Move, launch and reuse bodies](#move-launch-and-reuse-bodies)
|
|
15
|
+
- [Choose a rendering path](#choose-a-rendering-path)
|
|
16
|
+
- [Reproduce the collision and solver benchmarks](#reproduce-the-collision-and-solver-benchmarks)
|
|
17
|
+
- [Current shape and API support](#current-shape-and-api-support)
|
|
18
|
+
- [Choosing collision detection](#choosing-collision-detection)
|
|
19
|
+
|
|
20
|
+
## Install the library
|
|
21
|
+
|
|
22
|
+
The package is named `avbd-babylon`. For a local build, run `npm install` and
|
|
23
|
+
`npm run build` in this repository. From your
|
|
24
|
+
Babylon project, run `npm install /absolute/path/to/avbd` or install the archive
|
|
25
|
+
created by `npm pack`. Import from `avbd-babylon` using your normal bundler.
|
|
26
|
+
The ESM output already contains the solver and shader strings. No TypeScript
|
|
27
|
+
source compilation or WGSL loader is needed in your project. The package has
|
|
28
|
+
no Babylon runtime imports; it uses the mesh objects your project provides.
|
|
29
|
+
|
|
30
|
+
`npm run build` builds only the library. `npm run build:web` builds the lab and
|
|
31
|
+
browser tests. `npm start` builds the library and serves the lab together.
|
|
32
|
+
`npm run build:cdn` creates `dist/avbd.global.js`, which exposes `globalThis.AVBD`.
|
|
33
|
+
Host that file yourself and load it with a normal script tag; use
|
|
34
|
+
`const { AvbdPhysics, AvbdPhysicsAggregate, AvbdShapeType } = AVBD`.
|
|
35
|
+
|
|
36
|
+
WebGPU
|
|
37
|
+
needs a supported browser and localhost or HTTPS. The default world requests
|
|
38
|
+
9 storage bindings, a 512 MiB storage binding limit and a 1 GiB buffer limit;
|
|
39
|
+
unsupported adapters fail during creation. Babylon drawing can use either its
|
|
40
|
+
WebGL or WebGPU engine when using the small-scene mesh bridge.
|
|
41
|
+
|
|
42
|
+
For CPU physics, use the [native pipeline examples](../README.md#choose-a-solver-backend-once).
|
|
43
|
+
Select the backend once; 2D and 3D scene builders, body commands, joints, triggers,
|
|
44
|
+
restitution, collision masks and state reads keep the same API. The package
|
|
45
|
+
supports WebGPU and CPU. The repository demos retain an experimental WebGL2
|
|
46
|
+
solver for comparisons; its measured performance was too poor to include in
|
|
47
|
+
the package. See the [backend benchmark](../README.md#backend-differences-and-benchmarks).
|
|
48
|
+
|
|
49
|
+
## A complete small scene
|
|
50
|
+
|
|
51
|
+
This example expects `<canvas id="canvas"></canvas>` styled to fill the page.
|
|
52
|
+
Use your project's existing Babylon dependency.
|
|
53
|
+
|
|
54
|
+
```js
|
|
55
|
+
// src/main.js
|
|
56
|
+
import {
|
|
57
|
+
Engine,
|
|
58
|
+
Scene,
|
|
59
|
+
ArcRotateCamera,
|
|
60
|
+
HemisphericLight,
|
|
61
|
+
MeshBuilder,
|
|
62
|
+
Vector3,
|
|
63
|
+
} from "@babylonjs/core";
|
|
64
|
+
import { AvbdPhysics, AvbdPhysicsAggregate, AvbdShapeType } from "avbd-babylon";
|
|
65
|
+
|
|
66
|
+
const canvas = document.getElementById("canvas");
|
|
67
|
+
const engine = new Engine(canvas, true);
|
|
68
|
+
const scene = new Scene(engine);
|
|
69
|
+
const camera = new ArcRotateCamera(
|
|
70
|
+
"camera",
|
|
71
|
+
-Math.PI / 2,
|
|
72
|
+
Math.PI / 3,
|
|
73
|
+
12,
|
|
74
|
+
new Vector3(0, 2, 0),
|
|
75
|
+
scene,
|
|
76
|
+
);
|
|
77
|
+
camera.attachControl(canvas, true);
|
|
78
|
+
new HemisphericLight("light", new Vector3(0, 1, 0), scene);
|
|
79
|
+
|
|
80
|
+
const floor = MeshBuilder.CreateBox(
|
|
81
|
+
"floor",
|
|
82
|
+
{ width: 20, height: 1, depth: 20 },
|
|
83
|
+
scene,
|
|
84
|
+
);
|
|
85
|
+
floor.position.y = -0.5;
|
|
86
|
+
const box = MeshBuilder.CreateBox("box", { size: 1 }, scene);
|
|
87
|
+
box.position.y = 4;
|
|
88
|
+
|
|
89
|
+
const physics = await AvbdPhysics.create({
|
|
90
|
+
scene,
|
|
91
|
+
gravity: new Vector3(0, -9.81, 0),
|
|
92
|
+
timeStep: 1 / 60,
|
|
93
|
+
iterations: 10,
|
|
94
|
+
capacity: 64,
|
|
95
|
+
});
|
|
96
|
+
new AvbdPhysicsAggregate(floor, AvbdShapeType.BOX, { mass: 0 }, scene);
|
|
97
|
+
const fallingBox = new AvbdPhysicsAggregate(
|
|
98
|
+
box,
|
|
99
|
+
AvbdShapeType.BOX,
|
|
100
|
+
{ mass: 2, friction: 0.6 },
|
|
101
|
+
scene,
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
// The world attaches to the scene and allocates on its first simulation step.
|
|
105
|
+
// Create aggregates before the render loop starts.
|
|
106
|
+
fallingBox.body.setAngularVelocity(new Vector3(0, 1, 0.5));
|
|
107
|
+
|
|
108
|
+
engine.runRenderLoop(() => scene.render());
|
|
109
|
+
const resize = () => engine.resize();
|
|
110
|
+
window.addEventListener("resize", resize);
|
|
111
|
+
window.addEventListener(
|
|
112
|
+
"pagehide",
|
|
113
|
+
() => {
|
|
114
|
+
window.removeEventListener("resize", resize);
|
|
115
|
+
physics.dispose();
|
|
116
|
+
scene.dispose();
|
|
117
|
+
engine.dispose();
|
|
118
|
+
},
|
|
119
|
+
{ once: true },
|
|
120
|
+
);
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
The fixed-step observer allows up to three catch-up steps per rendered frame.
|
|
124
|
+
Mesh poses arrive asynchronously; do not await them in the render loop. `create({ scene })` attaches automatically and disposes with the scene. For a
|
|
125
|
+
custom simulation loop, pass `autoAttach: false` and call `step()` yourself.
|
|
126
|
+
`attachToScene()` is also available and does not attach a second observer when
|
|
127
|
+
called again for the same scene.
|
|
128
|
+
|
|
129
|
+
## Move, launch and reuse bodies
|
|
130
|
+
|
|
131
|
+
```js
|
|
132
|
+
fallingBox.body.setLinearVelocity(new Vector3(0, 0, 5));
|
|
133
|
+
fallingBox.body.applyImpulse(new Vector3(3, 0, 0), box.position);
|
|
134
|
+
fallingBox.body.applyAngularImpulse(new Vector3(0, 2, 0));
|
|
135
|
+
|
|
136
|
+
// Teleport retains existing velocities; reset them explicitly for a fresh launch.
|
|
137
|
+
fallingBox.body.teleport(new Vector3(0, 3, -5));
|
|
138
|
+
fallingBox.body.setLinearVelocity(new Vector3(0, 0, 12));
|
|
139
|
+
fallingBox.body.setAngularVelocity(Vector3.Zero());
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
Commands are combined and applied against the current GPU state. They do not
|
|
143
|
+
upload a stale CPU pose. Create projectile slots before initialization and reuse
|
|
144
|
+
them for spawning. `aggregate.dispose()` disables its collision participation;
|
|
145
|
+
the caller still owns the mesh. Dispose the world before disposing its scene.
|
|
146
|
+
|
|
147
|
+
## Choose a rendering path
|
|
148
|
+
|
|
149
|
+
| Scene size and needs | Rendering path | Cost to watch |
|
|
150
|
+
| ----------------------------------- | ---------------------------------------------------- | ----------------------------------------- |
|
|
151
|
+
| Small interactive scenes | Default Babylon mesh bridge | Pose readback and individual mesh updates |
|
|
152
|
+
| Repeated geometry, moderate counts | Instances or thin instances with a batched pose copy | CPU matrix updates and uploading matrices |
|
|
153
|
+
| Tens of thousands of moving objects | Draw directly from the live GPU body buffer | GPU rendering and collision/solver work |
|
|
154
|
+
|
|
155
|
+
Babylon [instances](https://doc.babylonjs.com/features/featuresDeepDive/mesh/copies/instances)
|
|
156
|
+
share source geometry and material. [Thin instances](https://doc.babylonjs.com/features/featuresDeepDive/mesh/copies/thinInstances)
|
|
157
|
+
store repeated transforms in buffers and avoid a separate scene object for every
|
|
158
|
+
visible copy. Group objects by geometry and material. Instancing reduces drawing
|
|
159
|
+
overhead; it does not reduce the number of physics contacts.
|
|
160
|
+
|
|
161
|
+
For a moderate number of identical, origin-centred unit boxes, this bridge copies
|
|
162
|
+
all poses once and updates a single thin-instance matrix buffer. Here `physics`
|
|
163
|
+
was created with `syncMeshes: false`, and `boxes` contains box aggregates created
|
|
164
|
+
with `sync: false`. Their original visual meshes should be hidden; `source` is
|
|
165
|
+
an independent unit-box mesh at the origin with identity rotation and scale.
|
|
166
|
+
|
|
167
|
+
```js
|
|
168
|
+
import { Matrix, Quaternion, Vector3 } from "@babylonjs/core";
|
|
169
|
+
|
|
170
|
+
physics.initialize();
|
|
171
|
+
const indices = boxes.map((aggregate) => aggregate.body.gpuIndex);
|
|
172
|
+
const matrices = new Float32Array(boxes.length * 16);
|
|
173
|
+
const matrix = Matrix.Identity();
|
|
174
|
+
const position = Vector3.Zero();
|
|
175
|
+
const rotation = Quaternion.Identity();
|
|
176
|
+
const scale = Vector3.One();
|
|
177
|
+
let pending = false;
|
|
178
|
+
|
|
179
|
+
source.thinInstanceSetBuffer("matrix", matrices, 16, false);
|
|
180
|
+
scene.onBeforeRenderObservable.add(() => {
|
|
181
|
+
if (pending) return;
|
|
182
|
+
pending = true;
|
|
183
|
+
physics
|
|
184
|
+
.readBodies()
|
|
185
|
+
.then((poses) => {
|
|
186
|
+
for (let i = 0; i < indices.length; i++) {
|
|
187
|
+
const offset = indices[i] * 40;
|
|
188
|
+
position.set(poses[offset], poses[offset + 1], poses[offset + 2]);
|
|
189
|
+
rotation.set(...poses.subarray(offset + 4, offset + 8));
|
|
190
|
+
Matrix.ComposeToRef(scale, rotation, position, matrix);
|
|
191
|
+
matrix.copyToArray(matrices, i * 16);
|
|
192
|
+
}
|
|
193
|
+
source.thinInstanceBufferUpdated("matrix");
|
|
194
|
+
source.thinInstanceRefreshBoundingInfo();
|
|
195
|
+
})
|
|
196
|
+
.catch(console.error)
|
|
197
|
+
.finally(() => {
|
|
198
|
+
pending = false;
|
|
199
|
+
});
|
|
200
|
+
});
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
This is a CPU bridge for moderate counts. It still copies the full body buffer,
|
|
204
|
+
calculates matrices and uploads them. Bounding updates also cost CPU time. For
|
|
205
|
+
known contained motion, a deliberately conservative bounding volume can avoid
|
|
206
|
+
rebuilding bounds each update; ensure it covers all moving instances.
|
|
207
|
+
For hulls or meshes with offset origins, reconstruct the mesh transform using
|
|
208
|
+
the aggregate's local centre and principal rotation as the adapter does; the
|
|
209
|
+
unit-box example does not handle those offsets.
|
|
210
|
+
|
|
211
|
+
For a large scene:
|
|
212
|
+
|
|
213
|
+
```js
|
|
214
|
+
const physics = await AvbdPhysics.create({
|
|
215
|
+
capacity: 100_001,
|
|
216
|
+
iterations: 4,
|
|
217
|
+
syncMeshes: false,
|
|
218
|
+
});
|
|
219
|
+
// Create all bodies before initialize(), with { sync: false }.
|
|
220
|
+
physics.initialize();
|
|
221
|
+
const bodyBuffer = physics.bodyBuffer;
|
|
222
|
+
const gpuIndex = aggregate.body.gpuIndex;
|
|
223
|
+
physics.step(); // One fixed step; submission does not await completion.
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
A custom WebGPU renderer can read `bodyBuffer` on the **same GPUDevice** and draw
|
|
227
|
+
instances from live poses. Buffers cannot be shared across devices. The adapter
|
|
228
|
+
does not automatically connect this storage buffer to Babylon thin instances,
|
|
229
|
+
and it does not access Babylon private engine fields. If your renderer exposes
|
|
230
|
+
a device you own, pass it to `AvbdPhysics.create({ device, ... })`. A caller-owned
|
|
231
|
+
device stays alive when the world is disposed.
|
|
232
|
+
|
|
233
|
+
Thin instances do not automatically create colliders. The aggregate API takes
|
|
234
|
+
one mesh per physics body, which has a startup cost for very large scenes.
|
|
235
|
+
For 100K bodies, the laboratory's native body/scene builders avoid creating
|
|
236
|
+
100K Babylon mesh objects; see [canonicalScenes.js](../demo/canonicalScenes.js).
|
|
237
|
+
Import `AvbdScene2D` or `AvbdScene3D` from `avbd-babylon/native` for the same
|
|
238
|
+
renderer-independent integration. Both expose live body handles, bulk commands,
|
|
239
|
+
packed velocity uploads, selected pose reads, triggers, masks, restitution,
|
|
240
|
+
sleeping and contact events. See the package README's
|
|
241
|
+
[native examples](../README.md#native-physics-with-any-renderer).
|
|
242
|
+
The direct-buffer renderer and small-scene aggregate bridge are separate
|
|
243
|
+
integration paths.
|
|
244
|
+
|
|
245
|
+
See [canonicalRenderer.js](../demo/canonicalRenderer.js) and
|
|
246
|
+
[canonicalRender.wgsl](../demo/canonicalRender.wgsl) for the direct rendering path,
|
|
247
|
+
and [mixedColliderDemo.js](../demo/mixedColliderDemo.js) for the Babylon mesh bridge.
|
|
248
|
+
The laboratory scene builders use z up; the Babylon adapter uses y up by
|
|
249
|
+
default. Match your renderer's coordinates to the world you create.
|
|
250
|
+
|
|
251
|
+
The 3D body stride is 40 floats / 160 bytes: position at 0, quaternion xyzw at 4,
|
|
252
|
+
size at 16, mass at 19, principal inertia at 20, linear velocity at 32 and angular
|
|
253
|
+
velocity at 36. Spatial sorting changes GPU order. Use `body.gpuIndex` after
|
|
254
|
+
initialization rather than assuming creation order equals buffer order.
|
|
255
|
+
|
|
256
|
+
Use shared geometry/materials and a small number of draw calls. For independent
|
|
257
|
+
solver measurements, omit drawing entirely. For whole-app comparisons, keep
|
|
258
|
+
resolution, antialiasing, shadows, materials and camera fixed. Enhanced colors,
|
|
259
|
+
lighting and 4× MSAA are interactive presentation options in this app;
|
|
260
|
+
`quality=benchmark` disables them. Keeping physics settings fixed makes a
|
|
261
|
+
rendering optimization easier to identify.
|
|
262
|
+
|
|
263
|
+
## Reproduce the collision and solver benchmarks
|
|
264
|
+
|
|
265
|
+
Start this repository with `npm start`, then open
|
|
266
|
+
[/tests/performance.html](http://127.0.0.1:8080/tests/performance.html).
|
|
267
|
+
Select a scene and play it before measuring. The preview closes during a run
|
|
268
|
+
and restarts afterward. “Run all scenes” measures the complete scene catalog, including
|
|
269
|
+
10K, 50K and 100K dense 2D piles.
|
|
270
|
+
|
|
271
|
+
For a repeatable solver-only run, use this JavaScript in the benchmark page's
|
|
272
|
+
developer console after it is ready. `__PERFORMANCE__` is the repository's test
|
|
273
|
+
harness, not part of the Babylon world API.
|
|
274
|
+
|
|
275
|
+
```js
|
|
276
|
+
const measurements = [];
|
|
277
|
+
await __BENCHMARK_VIEW__.suspendPreview();
|
|
278
|
+
try {
|
|
279
|
+
for (const scene of [
|
|
280
|
+
"2d-stress-pile-10k",
|
|
281
|
+
"2d-stress-pile-50k",
|
|
282
|
+
"2d-stress-pile-100k",
|
|
283
|
+
"showcase-box-columns-100k",
|
|
284
|
+
"showcase-brick-ring-110k",
|
|
285
|
+
]) {
|
|
286
|
+
for (let repeat = 0; repeat < 3; repeat++) {
|
|
287
|
+
const result = await __PERFORMANCE__.run(scene, "production", 60, 60);
|
|
288
|
+
measurements.push({ ...result, repeat });
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
console.table(
|
|
292
|
+
measurements.map((r) => ({
|
|
293
|
+
scene: r.scene,
|
|
294
|
+
objects: r.bodyCount,
|
|
295
|
+
contacts: r.peakContacts,
|
|
296
|
+
iterations: r.params.iterations,
|
|
297
|
+
collisionMs: r.summary.collision.mean,
|
|
298
|
+
movementMs: r.summary.solve.mean,
|
|
299
|
+
physicsMs: r.summary.total.mean,
|
|
300
|
+
slowerStepsMs: r.summary.total.p95,
|
|
301
|
+
})),
|
|
302
|
+
);
|
|
303
|
+
const data = { adapter: __PERFORMANCE__.info, measurements };
|
|
304
|
+
const url = URL.createObjectURL(
|
|
305
|
+
new Blob([JSON.stringify(data, null, 2)], {
|
|
306
|
+
type: "application/json",
|
|
307
|
+
}),
|
|
308
|
+
);
|
|
309
|
+
const download = document.createElement("a");
|
|
310
|
+
download.href = url;
|
|
311
|
+
download.download = "avbd-replication.json";
|
|
312
|
+
download.click();
|
|
313
|
+
setTimeout(() => URL.revokeObjectURL(url), 1000);
|
|
314
|
+
} finally {
|
|
315
|
+
await __BENCHMARK_VIEW__.preview.load(document.getElementById("scene").value);
|
|
316
|
+
}
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
Each run starts fresh, warms up for 60 steps and records 60 distinct GPU
|
|
320
|
+
timestamp samples. Every step checks contact capacity and scheduling conflicts.
|
|
321
|
+
The 2D stress scenes use touching boxes, gravity, friction and ten iterations,
|
|
322
|
+
without sleeping. The 3D showcase uses each scene's recorded settings. Compare
|
|
323
|
+
collision time with contact count; equal body counts across dimensions do not
|
|
324
|
+
guarantee equal collision work.
|
|
325
|
+
|
|
326
|
+
For controlled engineering profiles, use `scripts/profile-solver.mjs` or
|
|
327
|
+
`scripts/profile-paired.mjs`. Set `AVBD_PROFILE_SCENES`,
|
|
328
|
+
`AVBD_PROFILE_VARIANTS`, `AVBD_PROFILE_WARMUP`, `AVBD_PROFILE_SAMPLES` and
|
|
329
|
+
`AVBD_PROFILE_REPEATS` explicitly. Public benchmark controls use the production
|
|
330
|
+
automatic choice.
|
|
331
|
+
|
|
332
|
+
GPU work layout selection is automatic in `AppGpuSolver3D` and the Babylon
|
|
333
|
+
adapter. Body count and active joints select the contact calculation; contact
|
|
334
|
+
load selects thread sharing for very large scenes. The 100K columns preset
|
|
335
|
+
shares face-contact points across threads. Dense rigid scenes above 250K bodies
|
|
336
|
+
with at least 1.75 manifolds per body use eight threads per body. The same large
|
|
337
|
+
scenes cache sorting keys for contact lists of up to sixteen entries; longer
|
|
338
|
+
lists retain their general sorting path. Deformable shaders remain intact.
|
|
339
|
+
|
|
340
|
+
```js
|
|
341
|
+
const world = await AvbdPhysics.create({ scene }); // automatic by default
|
|
342
|
+
// Create aggregates before the render loop.
|
|
343
|
+
world.initialize();
|
|
344
|
+
console.log(world.solverDecision); // implementation and reason
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
Body and joint changes are reconsidered before the next step. Contact-load
|
|
348
|
+
adaptation reuses existing capacity-counter readbacks rather than adding a
|
|
349
|
+
per-frame pose copy. Positions, velocities, friction, rotation and iteration
|
|
350
|
+
counts are retained. The policy follows measurements on the test laptop;
|
|
351
|
+
results on other GPUs may differ. Advanced `setSolverMode()` overrides remain
|
|
352
|
+
available for controlled engineering measurements and compatibility. The
|
|
353
|
+
browser benchmark runs the production automatic choice.
|
|
354
|
+
|
|
355
|
+
Web Workers can keep scene construction and command submission from blocking
|
|
356
|
+
the page. They do not divide the already-parallel GPU kernels among CPU threads.
|
|
357
|
+
The current API runs on the caller's thread. A complete worker integration would
|
|
358
|
+
own the GPU solver and an OffscreenCanvas renderer together, send inputs and
|
|
359
|
+
small status messages, and draw directly from the GPU body buffer. Copying every
|
|
360
|
+
body's pose between threads each frame would defeat that design. Worker-based
|
|
361
|
+
Babylon drawing also requires a compatible Babylon engine setup and replacing
|
|
362
|
+
DOM-dependent controls with forwarded input messages. The root [README worker example](../README.md#worker-integration) shows the
|
|
363
|
+
OffscreenCanvas setup and message forwarding. Use the benchmark's `setup` and
|
|
364
|
+
`summary.submission` fields to
|
|
365
|
+
judge the CPU cost separately from GPU physics.
|
|
366
|
+
|
|
367
|
+
Both GPU solvers hold poses and velocities if a step runs
|
|
368
|
+
out of contact storage or has conflicting solve groups. The host can then grow
|
|
369
|
+
capacity without advancing an incomplete movement solve. Counters still report
|
|
370
|
+
the failure: a held step does not pass correctness checks. This safeguard does
|
|
371
|
+
not change valid-step equations or remove the need to reserve enough capacity.
|
|
372
|
+
|
|
373
|
+
`npm run test:web` also checks screenshots, controls, benchmark preview lifecycle,
|
|
374
|
+
navigation, and six-second dense 2D motion. Keep other GPU workloads closed.
|
|
375
|
+
|
|
376
|
+
Lower GPU milliseconds are better; higher app FPS is better. Physics timings
|
|
377
|
+
separate collision checks, contact setup, scheduling and movement calculations.
|
|
378
|
+
They exclude drawing, sleeping, CPU submission and copying results back.
|
|
379
|
+
Instrumented timing passes add overhead. The “CPU + wait” column includes GPU
|
|
380
|
+
work and readback, so it is not an isolated CPU cost. Report GPU model, driver,
|
|
381
|
+
scene, timestep, iterations, sample counts and contact counts with comparisons.
|
|
382
|
+
|
|
383
|
+
The premade reports were recorded on an **NVIDIA GeForce RTX 4070 Laptop GPU**.
|
|
384
|
+
They are saved examples; running the browser benchmark measures your own GPU.
|
|
385
|
+
|
|
386
|
+
The [solver report](http://127.0.0.1:8080/test-results/solver-performance.html)
|
|
387
|
+
includes the [AVBD paper](https://graphics.cs.utah.edu/research/projects/avbd/Augmented_VBD-SIGGRAPH25.pdf)
|
|
388
|
+
comparison. Its RTX 4090 / DirectX / LBVH results and this browser's
|
|
389
|
+
RTX 4070 Laptop / WebGPU / spatial-grid results have different hardware and
|
|
390
|
+
timing boundaries. They are workload context, not a same-hardware speed ratio.
|
|
391
|
+
|
|
392
|
+
## Current shape and API support
|
|
393
|
+
|
|
394
|
+
Boxes, spheres and capsules have native collision geometry. Capsules use exact
|
|
395
|
+
rounded segments along local Y. Cylinders and custom hulls use polygonal convex
|
|
396
|
+
hulls, limited to 32 vertices. Meshes must be root
|
|
397
|
+
meshes with positive scale; bake parent or negative transforms first. Colliders
|
|
398
|
+
capture geometry, mass properties and transforms at creation. Sphere dimensions
|
|
399
|
+
must be uniform.
|
|
400
|
+
|
|
401
|
+
The adapter supports restitution, friction, velocity and impulse commands,
|
|
402
|
+
teleports, collision masks, contact/trigger events, GPU raycasts, per-body sleep
|
|
403
|
+
eligibility, ball joints, welds, elastic or breakable springs, 3D hinges with
|
|
404
|
+
angle stops and torque-limited motors. Both dimensions expose GPU raycasts;
|
|
405
|
+
3D sphere casts and 2D circle casts include rounded corner tests. It has no
|
|
406
|
+
complete Physics V2 plugin or concave mesh collider.
|
|
407
|
+
Static bodies with velocity move kinematically. Portable builders include 3D
|
|
408
|
+
ropes, ragdolls and fabric. Automatic mesh synchronization is limited to 2048
|
|
409
|
+
bodies by default; large scenes should draw GPU poses directly.
|
|
410
|
+
For full method signatures and ownership rules, see [BABYLON_API.md](BABYLON_API.md).
|
|
411
|
+
|
|
412
|
+
## Choosing collision detection
|
|
413
|
+
|
|
414
|
+
`AvbdPhysics.create` accepts `broadphase: "auto"` (default), `"grid"` or
|
|
415
|
+
`"hploc"`. Automatic selection uses a GPU tree for large scenes with varied
|
|
416
|
+
collider sizes or many linked thin plates, such as the ragdoll net.
|
|
417
|
+
Small scenes and uniform block scenes retain the grid. Measure your own workload;
|
|
418
|
+
body count alone does not predict which method is faster.
|
|
419
|
+
|
|
420
|
+
```js
|
|
421
|
+
const physics = await AvbdPhysics.create({
|
|
422
|
+
scene,
|
|
423
|
+
capacity: 50000,
|
|
424
|
+
syncMeshes: false,
|
|
425
|
+
broadphase: "auto",
|
|
426
|
+
bvh: { rebuildInterval: 64 },
|
|
427
|
+
});
|
|
428
|
+
```
|
|
429
|
+
|
|
430
|
+
The tree refits every collider each step, including rotations and teleports.
|
|
431
|
+
Rebuilds improve its quality; refitting preserves coverage between rebuilds.
|
|
432
|
+
The same narrowphase, friction and rotational AVBD equations handle its pairs.
|
|
433
|
+
See [GPU tree design and limits](HPLOC.md) for implementation details.
|
|
434
|
+
|
|
435
|
+
For a controlled comparison, open `/tests/performance.html`. Its collision
|
|
436
|
+
selector compares the grid and tree without changing solving rounds. Both
|
|
437
|
+
methods are also available in the playable laboratory.
|
|
438
|
+
|
|
439
|
+
```js
|
|
440
|
+
try {
|
|
441
|
+
const pair = await __PERFORMANCE__.runPaired(
|
|
442
|
+
"3d-mixed-sizes-50k",
|
|
443
|
+
["grid", "hploc"],
|
|
444
|
+
60,
|
|
445
|
+
60,
|
|
446
|
+
);
|
|
447
|
+
console.table(
|
|
448
|
+
pair.results.map((r) => ({
|
|
449
|
+
collisions: r.broadphase,
|
|
450
|
+
collisionMs: r.summary.collision.mean,
|
|
451
|
+
physicsMs: r.summary.total.mean,
|
|
452
|
+
firstStepMs: r.startup.total,
|
|
453
|
+
})),
|
|
454
|
+
);
|
|
455
|
+
} finally {
|
|
456
|
+
await __BENCHMARK_VIEW__.preview.load(document.querySelector("#scene").value);
|
|
457
|
+
}
|
|
458
|
+
```
|
|
459
|
+
|
|
460
|
+
Use `grid-detail` / `hploc-detail` to split collision preparation, nearby-pair
|
|
461
|
+
search, exact contact checks, body solving and contact-force updates. These
|
|
462
|
+
extra timers add pass boundaries, so compare matching modes. Paired runs keep
|
|
463
|
+
two independent simulations in memory; their absolute times are separate from
|
|
464
|
+
the single-world paper comparison. Raw JSON retains startup, rebuild spikes,
|
|
465
|
+
all samples, buffer bytes, selection decisions and noisy timing flags.
|