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.
- package/README.md +864 -116
- package/dist/avbd.d.ts +2 -0
- package/dist/avbd.global.js +617 -153
- package/dist/avbd.js +617 -153
- package/dist/babylonAvbd.d.ts +31 -6
- package/dist/native.d.ts +1 -0
- package/dist/native.js +1 -1
- package/dist/nativeScenes.d.ts +81 -5
- package/dist/solverPipeline.d.ts +62 -0
- package/dist/utilities.d.ts +161 -0
- package/dist/utilities.js +1 -0
- package/docs/BABYLON_API.md +34 -5
- package/docs/HPLOC.md +119 -115
- package/docs/README.md +58 -14
- package/docs/RELEASE.md +26 -2
- package/package.json +38 -4
- package/tests/README.md +188 -25
package/README.md
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
# AVBD for Babylon.js
|
|
2
2
|
|
|
3
|
+
[](https://www.npmjs.com/package/avbd-babylon)
|
|
4
|
+
[](https://www.npmjs.com/package/avbd-babylon)
|
|
5
|
+

|
|
6
|
+
|
|
3
7
|
**TRY ME!!!** [**babylon-avbd.netlify.app**](https://babylon-avbd.netlify.app/)
|
|
4
8
|
|
|
5
9
|
`avbd-babylon` runs AVBD rigid-body physics and collision detection on WebGPU,
|
|
@@ -11,6 +15,8 @@ the resulting poses; `AvbdPhysics` owns the physics world and its GPU solver.
|
|
|
11
15
|
|
|
12
16
|
**Jump to:**
|
|
13
17
|
|
|
18
|
+
- [Benchmark sample code](#benchmark-sample-code)
|
|
19
|
+
- [Demo source and initialization](#demo-source-and-initialization)
|
|
14
20
|
- [Install](#install)
|
|
15
21
|
- [What this package adds](#what-this-package-adds)
|
|
16
22
|
- [Feature coverage](#feature-coverage)
|
|
@@ -26,6 +32,8 @@ the resulting poses; `AvbdPhysics` owns the physics world and its GPU solver.
|
|
|
26
32
|
- [Large pile with thin instances](#large-pile-with-thin-instances)
|
|
27
33
|
- [Direct GPU rendering](#direct-gpu-rendering)
|
|
28
34
|
- [Native physics with any renderer](#native-physics-with-any-renderer)
|
|
35
|
+
- [Choose a solver backend once](#choose-a-solver-backend-once)
|
|
36
|
+
- [Backend differences and benchmarks](#backend-differences-and-benchmarks)
|
|
29
37
|
- [Native 3D world](#native-3d-world)
|
|
30
38
|
- [Native 2D world](#native-2d-world)
|
|
31
39
|
- [Bulk edits and GPU dispatches](#bulk-edits-and-gpu-dispatches)
|
|
@@ -43,6 +51,11 @@ the resulting poses; `AvbdPhysics` owns the physics world and its GPU solver.
|
|
|
43
51
|
- [Slingshot siege demo](#slingshot-siege-demo)
|
|
44
52
|
- [Manual stepping and cleanup](#manual-stepping-and-cleanup)
|
|
45
53
|
- [Worker integration](#worker-integration)
|
|
54
|
+
- [Static terrain: hills, caves and overhangs](#static-terrain-hills-caves-and-overhangs)
|
|
55
|
+
- [Ships, water and destruction](#ships-water-and-destruction)
|
|
56
|
+
- [Worker and shared-memory benchmarks](#worker-and-shared-memory-benchmarks)
|
|
57
|
+
- [Reusable GPU and worker utilities](#reusable-gpu-and-worker-utilities)
|
|
58
|
+
- [D20 particle capacity](#d20-particle-capacity)
|
|
46
59
|
- [Benchmark replication](#benchmark-replication)
|
|
47
60
|
- [Babylon renderer comparison](#babylon-renderer-comparison)
|
|
48
61
|
- [Script-tag usage](#script-tag-usage)
|
|
@@ -50,6 +63,54 @@ the resulting poses; `AvbdPhysics` owns the physics world and its GPU solver.
|
|
|
50
63
|
- [Validation and attribution](#validation-and-attribution)
|
|
51
64
|
- [First release checks](docs/RELEASE.md)
|
|
52
65
|
|
|
66
|
+
## Benchmark sample code
|
|
67
|
+
|
|
68
|
+
**Start here:** [Benchmark replication](#benchmark-replication) contains copyable
|
|
69
|
+
JavaScript examples. Run `npm start`, then open the
|
|
70
|
+
[browser benchmark page](tests/performance.html) to run the scenes yourself.
|
|
71
|
+
|
|
72
|
+
| What you want to reproduce | Sample code and runnable source |
|
|
73
|
+
| --------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
74
|
+
| Physics timings, including the AVBD paper scenes | [Copyable physics example](#benchmark-replication), [scene measurement code](tests/performance.js), [package timing examples](tests/package-performance.js), [package runner](scripts/benchmark-package.mjs) |
|
|
75
|
+
| Scene setup: piles, walls, joints, cloth and stress tests | [Demo scene registry and builders](demo/lab/scenes.js), [browser benchmark controller](tests/performance.js) |
|
|
76
|
+
| Babylon rendering versus direct GPU rendering | [Rendering examples and commands](#babylon-renderer-comparison), [measurement code](tests/renderer-performance.js), [runner](scripts/benchmark-renderers.mjs) |
|
|
77
|
+
| Main thread versus dedicated worker and shared memory | [Copyable worker example and commands](#worker-and-shared-memory-benchmarks), [worker page](tests/workers.html), [worker runner](scripts/benchmark-workers.mjs) |
|
|
78
|
+
| Batched body edits, pose reads and queries | [Bulk-edit examples](#bulk-edits-and-gpu-dispatches), [measurement code](tests/native-edit-performance.js), [runner](scripts/check-native-performance.mjs) |
|
|
79
|
+
| CPU compatibility versus WebGPU | [Settings and results](#backend-differences-and-benchmarks), [measurement code](tests/compatibility-performance.js), [runner](scripts/benchmark-backends.mjs) |
|
|
80
|
+
| Sleeping and waking in large connected scenes | [Sleeping example](#queries-callbacks-and-sleeping), [connected-wall runner](scripts/benchmark-connected-sleep.mjs) |
|
|
81
|
+
| Ships, buoyancy and water | [Scene explanation](#ships-water-and-destruction), [scene builder](demo/naval/scene.js), [runner](scripts/benchmark-naval.mjs) |
|
|
82
|
+
| Grid versus HPLOC++ collisions | [Collision guide](docs/HPLOC.md), [paired scene measurements](tests/performance.js), [tree refit runner](scripts/benchmark-hploc-refit.mjs) |
|
|
83
|
+
| D20 particle collisions | [Capacity benchmark](#d20-particle-capacity), [runner](scripts/benchmark-particles.mjs), [reusable GPU particles](src/gpu/surfaceParticles.js), [collision shader](src/gpu/surfaceParticles.wgsl) |
|
|
84
|
+
| Movement solver and GPU scheduling | [Movement runner](scripts/benchmark-movement-solve.mjs), [color-bucket runner](scripts/benchmark-color-buckets.mjs), [small-group runner](scripts/benchmark-small-groups.mjs) |
|
|
85
|
+
| Uploads and command batching | [GPU upload runner](scripts/benchmark-webgpu-uploads.mjs), [command encoding runner](scripts/benchmark-command-encoding.mjs) |
|
|
86
|
+
|
|
87
|
+
This README includes package-usage examples and benchmark console snippets;
|
|
88
|
+
each benchmark snippet names the page where it runs. Files under `tests/`
|
|
89
|
+
implement the browser measurements; files under `scripts/` run those measurements
|
|
90
|
+
and save results. [tests/README.md](tests/README.md) lists the validation and
|
|
91
|
+
benchmark commands. Saved reports and screenshots live in `test-results/`.
|
|
92
|
+
|
|
93
|
+
## Demo source and initialization
|
|
94
|
+
|
|
95
|
+
**[Demo source guide](demo/README.md)** lists each demo's configuration, builder,
|
|
96
|
+
controls and shaders. Each demo has its own folder: for example,
|
|
97
|
+
[Galton board](demo/galton-board/scene.js), [ships](demo/naval/scene.js),
|
|
98
|
+
[slingshot](demo/slingshot/scene.js), [terrain](demo/terrain/scene.js) and
|
|
99
|
+
[tearable cloth](demo/tearable-cloth/scene.js).
|
|
100
|
+
|
|
101
|
+
Open `scene.js` to change bodies, constraints and solver settings; `index.js`
|
|
102
|
+
initializes demos with their own controls. The [lab registry](demo/lab/scenes.js)
|
|
103
|
+
selects scenes, while [the shared runtime](demo/shared/runtime/simulationSession.js)
|
|
104
|
+
initializes their solver and renderer for workers. Related size or feature presets
|
|
105
|
+
share a `scenes.js` builder. Rendering and execution helpers live in `demo/shared/`.
|
|
106
|
+
|
|
107
|
+
Build the bodies and constraints together, create one solver, and reuse its GPU
|
|
108
|
+
buffers. Initial shapes and sensors select the required shaders before compilation;
|
|
109
|
+
disabled collision groups are excluded from the initial contact-capacity estimate.
|
|
110
|
+
The [startup check](scripts/check-demo-startup.mjs) initializes every demo in a
|
|
111
|
+
paused worker with `--all` and records setup times separately from simulation
|
|
112
|
+
performance. Shader caches, device checks and scene size affect those times.
|
|
113
|
+
|
|
53
114
|
## Install
|
|
54
115
|
|
|
55
116
|
With a published release and your project's ESM bundler:
|
|
@@ -67,12 +128,14 @@ Babylon 9 is an optional peer dependency; integration examples are checked with
|
|
|
67
128
|
9.13.0. Installing AVBD alone does not install Babylon. For TypeScript Babylon
|
|
68
129
|
projects, install `@babylonjs/core` alongside AVBD as shown above.
|
|
69
130
|
For portable 2D/3D physics without Babylon, import from `avbd-babylon/native`.
|
|
70
|
-
That entry includes `AvbdScene2D`, `AvbdScene3D` and `createWebGPUDevice`, with
|
|
131
|
+
That entry includes `AvbdScene2D`, `AvbdScene3D`, `createSolverPipeline` and `createWebGPUDevice`, with
|
|
71
132
|
independent declarations. Current TypeScript DOM libraries include the WebGPU
|
|
72
133
|
types; older compilers may need `@webgpu/types` in the project's `types` setting.
|
|
73
134
|
Your application does not need a WGSL loader or the repository's TypeScript sources.
|
|
74
135
|
WebGPU requires a supported browser on localhost or HTTPS. Small-scene drawing
|
|
75
|
-
can use Babylon's WebGL or WebGPU engine
|
|
136
|
+
can use Babylon's WebGL or WebGPU engine. WebGPU is the default physics backend;
|
|
137
|
+
native worlds also offer CPU compatibility. The repository demos include an
|
|
138
|
+
experimental WebGL2 solver, which is excluded from the npm package.
|
|
76
139
|
|
|
77
140
|
The scene browser runs a small 3D floor-contact check once per GPU device before
|
|
78
141
|
opening a 3D GPU scene. A box, sphere and capsule must settle on a thin floor.
|
|
@@ -93,6 +156,10 @@ The [physical-phone floor check](test-results/connected-phone-floor.json) verifi
|
|
|
93
156
|
boxes, spheres and capsules in Android Brave. `npm run test:connected-phone`
|
|
94
157
|
checks 2D/3D collisions, joints, bounce, triggers, masks, sleeping and queries
|
|
95
158
|
on an authorized, unlocked Android phone running the local demo.
|
|
159
|
+
The current [Galaxy S23 GPU checks](test-results/connected-phone-contacts.json)
|
|
160
|
+
cover both 2D and 3D in Brave. The [phone scene checks](test-results/connected-phone-demos.json)
|
|
161
|
+
include hinges, sensors, terrain, cloth and 40,000-marble flow, with screenshots
|
|
162
|
+
and short measurements. Run these with `npm run test:connected-demos`.
|
|
96
163
|
|
|
97
164
|
`AvbdPhysics.create()` prepares its device automatically. Native 3D applications
|
|
98
165
|
can qualify a new device when creating it, or prepare a renderer-owned device
|
|
@@ -137,8 +204,22 @@ portable 2D/3D APIs and a Babylon integration:
|
|
|
137
204
|
equations, timestep and iteration count. Direct GPU pose buffers let a
|
|
138
205
|
compatible renderer draw many bodies without downloading every pose. When
|
|
139
206
|
CPU poses are needed, callers can request only the bodies they use.
|
|
207
|
+
Large 2D and 3D worlds automatically use bitsets to group bodies for solving,
|
|
208
|
+
reducing GPU scheduling work while preserving body order.
|
|
209
|
+
Large rigid 3D scenes also update a contact's points in parallel. The shared
|
|
210
|
+
contact-point layout stores only the needed entries of symmetric body matrices,
|
|
211
|
+
reducing movement-solver work with the same contacts and equations.
|
|
212
|
+
It also groups frequently read pose fields during solving, then restores the
|
|
213
|
+
public body-buffer format before rendering, queries or body edits.
|
|
214
|
+
Closed jointless 3D worlds of up to 32 bodies batch all movement iterations in
|
|
215
|
+
one workgroup with shared body poses and the same reductions and equations.
|
|
216
|
+
CPU command setup reuses settings and offset storage, uploads only changed
|
|
217
|
+
step/sleep/bounce settings and skips repeated buffer bindings within a compute
|
|
218
|
+
pass.
|
|
140
219
|
- **Sleeping and live editing.** Per-body sleep permission, support checks and
|
|
141
|
-
impact checks allow resting objects to sleep and wake again.
|
|
220
|
+
impact checks allow resting objects to sleep and wake again. Large jointless desktop
|
|
221
|
+
3D worlds retain conservative connected groups so impacts can wake a wall
|
|
222
|
+
without waking unrelated walls on the same floor. Velocities,
|
|
142
223
|
impulses, teleports, filters and motor targets can change during simulation.
|
|
143
224
|
Welds capture their rest rotation from live GPU poses, and released
|
|
144
225
|
constraint slots can be reused.
|
|
@@ -153,7 +234,7 @@ portable 2D/3D APIs and a Babylon integration:
|
|
|
153
234
|
behavior, collider pair coverage, queries, constraints, sleeping and failure
|
|
154
235
|
recovery. Collision overflow or conflicting parallel groups hold a step and
|
|
155
236
|
report the problem instead of silently dropping work. Browser tests cover all
|
|
156
|
-
|
|
237
|
+
69 scenes, controls, screenshots and stage timings; replication examples
|
|
157
238
|
distinguish physics, drawing and the paper's measurements.
|
|
158
239
|
|
|
159
240
|
The underlying solver and retained upstream code are credited in
|
|
@@ -168,50 +249,90 @@ It is an alternative physics integration, **not a drop-in Havok/Physics V2
|
|
|
168
249
|
plugin**: use `AvbdPhysicsAggregate`, rather than Babylon's `PhysicsAggregate`
|
|
169
250
|
and `scene.enablePhysics()`.
|
|
170
251
|
|
|
171
|
-
| Feature | 3D GPU
|
|
172
|
-
| ------------------------------------------------------ |
|
|
173
|
-
| Static and dynamic bodies, rotation, inertia, friction | Yes
|
|
174
|
-
| Boxes | Yes
|
|
175
|
-
| Spheres / circles | Analytic
|
|
176
|
-
| Capsules | Analytic
|
|
177
|
-
| Finite lines / infinite planes | Use static colliders
|
|
178
|
-
| Cylinders | Convex hulls
|
|
179
|
-
| Custom convex hulls | Yes
|
|
180
|
-
|
|
|
181
|
-
| Body velocity, forces, impulses and teleporting | Yes
|
|
182
|
-
| Batched body commands and packed velocity edits
|
|
183
|
-
| Automatic Babylon stepping and disposal | Yes
|
|
184
|
-
| Manual fixed steps / worker execution | Yes
|
|
185
|
-
| Mobile browsers with WebGPU | Yes
|
|
186
|
-
| Automatic grid / HPLOC++ choice | Yes
|
|
187
|
-
| GPU work sharing | Yes
|
|
188
|
-
| Mesh / instance rendering | Yes
|
|
189
|
-
| Direct GPU pose drawing | Yes
|
|
190
|
-
| Ball joints / planar hinges | Yes
|
|
191
|
-
| World anchors | Yes
|
|
192
|
-
| Welds and breakable joints | Yes
|
|
193
|
-
| Springs, linked ropes and ragdolls | Yes
|
|
194
|
-
| Hinges | Axis + angle limits
|
|
195
|
-
| Torque-limited motors | Yes
|
|
196
|
-
| Elastic / tearable fabric | Yes
|
|
197
|
-
| Per-body sleeping / waking | Yes
|
|
198
|
-
| Restitution / bounce | Yes
|
|
199
|
-
| Collision masks | Yes
|
|
200
|
-
| Contact / trigger events | Yes
|
|
201
|
-
| Non-solving triggers | Yes
|
|
202
|
-
| Raycasts | Yes
|
|
203
|
-
| Sphere / circle casts | Yes
|
|
204
|
-
| Continuous collision detection | No
|
|
205
|
-
| Full Babylon Physics V2 / Havok plugin | No
|
|
252
|
+
| Feature | 3D GPU | 2D GPU | Babylon / package usage |
|
|
253
|
+
| ------------------------------------------------------ | --------------------- | --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
254
|
+
| Static and dynamic bodies, rotation, inertia, friction | Yes | Yes | Aggregates in 3D; native 2D boxes, circles and capsules. |
|
|
255
|
+
| Boxes | Yes | Yes | Native collision shapes. |
|
|
256
|
+
| Spheres / circles | Analytic | Analytic | 3D `addSphere()`; 2D `addCircle()`. Exact rounded surfaces. |
|
|
257
|
+
| Capsules | Analytic | Analytic | 3D local Y, 2D local X. Full height / length includes both caps. |
|
|
258
|
+
| Finite lines / infinite planes | Use static colliders | Yes | 2D `addSegment()` is two-sided; `addPlane()` defines a solid half-space. |
|
|
259
|
+
| Cylinders | Convex hulls | No | Polygonal 3D approximations from mesh vertices. |
|
|
260
|
+
| Custom convex hulls | Yes | Yes | `addHull()` in both dimensions. Up to 32 boundary vertices. |
|
|
261
|
+
| Static heightfields and concave mesh terrain | Tiled convex contacts | Use static segments / hulls | Native and Babylon `addHeightfield()` / `addStaticMesh()`. Each triangle becomes a small static prism, preserving openings and overhangs. |
|
|
262
|
+
| Body velocity, forces, impulses and teleporting | Yes | Yes | Native `gpu.body()` handles or indexed solver methods in both dimensions; live GPU poses. |
|
|
263
|
+
| Batched body commands and packed velocity edits | Yes | Yes | `editBodies()` groups motion and property edits; packed linear/angular updates use one dispatch. |
|
|
264
|
+
| Automatic Babylon stepping and disposal | Yes | Yes | `AvbdPhysics` or native `gpu.attachToScene(scene)` in either dimension. |
|
|
265
|
+
| Manual fixed steps / worker execution | Yes | Yes | No renderer or DOM dependency in either solver. |
|
|
266
|
+
| Mobile browsers with WebGPU | Yes | Yes | Verified on Android Brave. Babylon's async factory and the scene browser prepare 3D automatically; native 3D apps use `validate3D:true` or `prepareWebGPUDevice3D()`. Requires WebGPU and HTTPS or localhost. |
|
|
267
|
+
| Automatic grid / HPLOC++ choice | Yes | Grid | 3D auto selection uses body sizes and workload. |
|
|
268
|
+
| GPU work sharing | Yes | Yes | Retains the configured physics and iteration counts. |
|
|
269
|
+
| Mesh / instance rendering | Yes | Yes | Use batch drawing for large scenes; portable builders do not create meshes. |
|
|
270
|
+
| Direct GPU pose drawing | Yes | Yes | Renderer and solver must share a GPUDevice. Body layouts differ by dimension. |
|
|
271
|
+
| Ball joints / planar hinges | Yes | Yes | 3D `addJoint()`; 2D joint with free angular row. |
|
|
272
|
+
| World anchors | Yes | Yes | Null body A in 3D; body index `-1` in 2D. |
|
|
273
|
+
| Welds and breakable joints | Yes | Yes | 3D `addWeld()`; 2D joint with all three rows locked and optional `breakForce`. |
|
|
274
|
+
| Springs, linked ropes and ragdolls | Yes | Yes | 3D helpers; 2D bodies linked with joints/springs. |
|
|
275
|
+
| Hinges | Axis + angle limits | Planar + angle limits | `addHinge()` in both dimensions; optional angle bounds relative to creation. |
|
|
276
|
+
| Torque-limited motors | Yes | Yes | Both dimensions expose motor handles and live `gpu.setMotor()` controls. |
|
|
277
|
+
| Elastic / tearable fabric | Yes | Build spring networks | 3D `addFabric()` has point collisions and tensile fracture. The demo preserves material after tearing. |
|
|
278
|
+
| Per-body sleeping / waking | Yes | Yes | World `sleeping`, body `allowSleep`, live `setSleepEnabled()`; supported bodies rest, impacts wake them. |
|
|
279
|
+
| Restitution / bounce | Yes | Yes | Coefficient 0..1. Zero adds no bounce passes. |
|
|
280
|
+
| Collision masks | Yes | Yes | Both bodies must allow the pair. Includes sensors. |
|
|
281
|
+
| Contact / trigger events | Yes | Yes | Babylon `onContact()`; native `gpu.watchContacts()` + `readContactEvents()` in both dimensions. |
|
|
282
|
+
| Non-solving triggers | Yes | Yes | `isTrigger:true`; no contact forces. Static–static pairs are not scanned. |
|
|
283
|
+
| Raycasts | Yes | Yes | `physics.raycast()` or portable `gpu.raycast()`; masks and ignored bodies. |
|
|
284
|
+
| Sphere / circle casts | Yes | Yes | 3D `sphereCast()`; 2D `circleCast()`; exact rounded corner tests. |
|
|
285
|
+
| Continuous collision detection | No | No | Discrete contacts; timestep matters for fast objects. |
|
|
286
|
+
| Full Babylon Physics V2 / Havok plugin | No | No | Use AVBD aggregates, rather than `scene.enablePhysics()`. |
|
|
206
287
|
|
|
207
288
|
See the [API reference](docs/BABYLON_API.md) and [GPU validation guide](tests/README.md)
|
|
208
289
|
for supported methods, numerical checks and limitations.
|
|
209
290
|
|
|
210
291
|
## Playable feature demos
|
|
211
292
|
|
|
293
|
+
Also try [ships, waves and sinking](index.html?demo=naval),
|
|
294
|
+
[hills, caves and overhangs](index.html?demo=canonical&scene=3d-static-terrain&backend=gpu)
|
|
295
|
+
and the [interactive worker comparison](tests/workers.html).
|
|
296
|
+
|
|
297
|
+
Three more physics playgrounds offer thousands of independently simulated bodies:
|
|
298
|
+
|
|
299
|
+
| Playground | What you can do |
|
|
300
|
+
| ----------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
301
|
+
| [Tornado town](index.html?demo=canonical&scene=3d-tornado-town&backend=gpu) | Send swirling wind through twenty houses made from 4,400 breakable pieces. Stop the wind and let debris settle. The visible funnel marks an analytic wind field. |
|
|
302
|
+
| [Domino spiral](index.html?demo=canonical&scene=3d-domino-spiral&backend=gpu) | Start a chain reaction through 2,048 tiles, or drag tiles to make your own branches. |
|
|
303
|
+
| [Galton board](index.html?demo=canonical&scene=3d-marble-cascade&backend=gpu) | Open the visible hopper to release 40,000 marbles through twenty staggered rows of pegs into thirty-three bins in a shallow board. Gravity and collisions control the discharge and spread. Watch the live bin chart, shake the board or reset it. |
|
|
304
|
+
|
|
305
|
+
These scenes support direct WebGPU and Babylon thin-instance drawing, dedicated
|
|
306
|
+
workers and optional shared input. The force-field demo adds one GPU pass to the
|
|
307
|
+
existing physics command buffer; positions stay on the GPU. CPU compatibility
|
|
308
|
+
also retains the actions, with the expected cost of simulating thousands of bodies.
|
|
309
|
+
The main scene browser and the naval demo default to a dedicated worker when
|
|
310
|
+
OffscreenCanvas is available. Physics and drawing run in the worker while the
|
|
311
|
+
scene selector and controls stay on the page. Choose Main thread, or add
|
|
312
|
+
`scope=main` to the scene URL, to compare execution. Specialized scene editors
|
|
313
|
+
are available through the scene editing link in the sidebar.
|
|
314
|
+
|
|
315
|
+
Every AVBD example has a Sleeping toggle for comparing the same scene with and
|
|
316
|
+
without sleeping. Large rigid scenes enable it by default. Add `sleeping=1` or
|
|
317
|
+
`sleeping=0` to choose explicitly; moving water and wind still keep affected
|
|
318
|
+
bodies awake. The half-million wall demos keep the original two-ball benchmark
|
|
319
|
+
until you click **Bowl every row**, which launches balls into all sixteen lanes.
|
|
320
|
+
|
|
321
|
+
The Galton board runs fixed physics steps against elapsed time, catching up
|
|
322
|
+
across frames when needed. Catch-up is bounded when the GPU is overloaded.
|
|
323
|
+
Click or tap a peg to retract it; its coral cap restores it. **Restore all pegs**
|
|
324
|
+
returns the original layout without releasing the hopper or resuming a paused
|
|
325
|
+
simulation. Peg movements and collision-filter changes are batched GPU edits.
|
|
326
|
+
[Playground checks](test-results/playgrounds.json) cover standing structures,
|
|
327
|
+
chain propagation, the 40,000-marble distribution and worker rendering. Run `npm run test:playgrounds`.
|
|
328
|
+
|
|
329
|
+

|
|
330
|
+

|
|
331
|
+

|
|
332
|
+
|
|
212
333
|
Run `npm start` and open the links below. Every scene uses GPU physics. The
|
|
213
334
|
[scene browser and benchmark](tests/performance.html)
|
|
214
|
-
includes all
|
|
335
|
+
includes all presets and lets you play them before measuring performance.
|
|
215
336
|
The [scene check report](test-results/report.html) contains
|
|
216
337
|
screenshots and results after `npm run test:web`.
|
|
217
338
|
|
|
@@ -935,8 +1056,8 @@ export async function createDirectGpuExample(canvas, count = 4096) {
|
|
|
935
1056
|
|
|
936
1057
|
The helper renders centered **boxes** in GPU order, including the floor. Hulls,
|
|
937
1058
|
capsules and other graphics need their own geometry and any center/principal-frame
|
|
938
|
-
conversion. The [demo renderer](demo/canonicalRenderer.js) shows those paths.
|
|
939
|
-
2D uses a different packed layout; see [the 2D renderer](demo/gpuRenderer2D.js).
|
|
1059
|
+
conversion. The [demo renderer](demo/shared/rendering/canonicalRenderer.js) shows those paths.
|
|
1060
|
+
2D uses a different packed layout; see [the 2D renderer](demo/shared/rendering/gpuRenderer2D.js).
|
|
940
1061
|
|
|
941
1062
|
For an existing Babylon world, pass `() => physics.getRenderBinding(device)`
|
|
942
1063
|
to `createGpuBoxRenderer()`. Configure that world with `syncMeshes: false` and
|
|
@@ -957,9 +1078,9 @@ const gatePosition = poses.subarray(40, 43);
|
|
|
957
1078
|
|
|
958
1079
|
## Native physics with any renderer
|
|
959
1080
|
|
|
960
|
-
Import `avbd-babylon/native` for
|
|
1081
|
+
Import `avbd-babylon/native` for physics without Babylon. These worlds
|
|
961
1082
|
also work in module workers. Create bodies and constraints in a scene builder,
|
|
962
|
-
then create its
|
|
1083
|
+
then create its solver once. WebGPU rendering and simulation can share a GPUDevice.
|
|
963
1084
|
The canonical web demos and solver benchmarks use these native solvers by default.
|
|
964
1085
|
|
|
965
1086
|
Both dimensions provide `gpu.body()`, `editBodies()`, `advance()`,
|
|
@@ -968,19 +1089,189 @@ shape queries. Native joints, welds, springs, hinges, motors, ropes, ragdolls an
|
|
|
968
1089
|
fabric use the builders shown in the following sections. They do not require
|
|
969
1090
|
Babylon meshes.
|
|
970
1091
|
|
|
1092
|
+
### Choose a solver backend once
|
|
1093
|
+
|
|
1094
|
+
`createSolverPipeline()` initializes the execution backend. Its default is
|
|
1095
|
+
`webgpu`; choose `cpu` to run without a graphics device. Both use the same scene
|
|
1096
|
+
builders, body handles, ordered bulk edits, joints, triggers, bounce, collision
|
|
1097
|
+
masks, queries, sleeping and fixed-step calls. A pipeline can own several worlds.
|
|
1098
|
+
Changing backend means creating a new pipeline and rebuilding the world.
|
|
1099
|
+
Initialization requests supported limits for convex hulls and large scenes;
|
|
1100
|
+
buffers are allocated only when a world needs them. The backends return the
|
|
1101
|
+
same promise-based state and query results. GPU scheduling options such as
|
|
1102
|
+
`broadphase` select the WebGPU collision search.
|
|
1103
|
+
Timestep and iterations apply to both backends.
|
|
1104
|
+
|
|
1105
|
+
```js
|
|
1106
|
+
import {
|
|
1107
|
+
AvbdScene2D,
|
|
1108
|
+
AvbdScene3D,
|
|
1109
|
+
createSolverPipeline,
|
|
1110
|
+
} from "avbd-babylon/native";
|
|
1111
|
+
|
|
1112
|
+
const pipeline = await createSolverPipeline({ backend: "webgpu" }); // or "cpu"
|
|
1113
|
+
|
|
1114
|
+
const scene3D = new AvbdScene3D({ iterations: 5 });
|
|
1115
|
+
scene3D.addBox([20, 1, 20], { density: 0, position: [0, -0.5, 0] });
|
|
1116
|
+
const ball = scene3D.addSphere(0.5, { position: [0, 3, 0], restitution: 0.6 });
|
|
1117
|
+
const world3D = scene3D.createSolver(pipeline);
|
|
1118
|
+
|
|
1119
|
+
const scene2D = new AvbdScene2D({ iterations: 5 });
|
|
1120
|
+
scene2D.addPlane([0, 1]);
|
|
1121
|
+
const circle = scene2D.addCircle(0.5, { position: [0, 3], restitution: 0.6 });
|
|
1122
|
+
const world2D = scene2D.createSolver(pipeline);
|
|
1123
|
+
|
|
1124
|
+
// The selected backend does not change these calls.
|
|
1125
|
+
world3D.editBodies([{ body: ball, impulse: [2, 0, 0] }]);
|
|
1126
|
+
world2D.editBodies([{ body: circle, impulse: [2, 0] }]);
|
|
1127
|
+
world3D.body(ball).setTrigger(false).setCollisionGroups(1, 1);
|
|
1128
|
+
world2D.body(circle).setTrigger(false).setCollisionGroups(1, 1);
|
|
1129
|
+
world3D.advance(1 / 60);
|
|
1130
|
+
world2D.advance(1 / 60);
|
|
1131
|
+
const state3D = await world3D.body(ball).readState();
|
|
1132
|
+
const state2D = await world2D.body(circle).readState();
|
|
1133
|
+
|
|
1134
|
+
// At application shutdown; destroys the worlds and a pipeline-owned GPU device.
|
|
1135
|
+
pipeline.destroy();
|
|
1136
|
+
```
|
|
1137
|
+
|
|
1138
|
+
Rendering resources belong to the backend. Direct WebGPU drawing uses
|
|
1139
|
+
`pipeline.device` and the world's `bodyBuffer`; CPU worlds provide poses through
|
|
1140
|
+
the same `readSelectedBodies()` calls instead. Supply `{ device }` when sharing
|
|
1141
|
+
an existing WebGPU renderer's device; the pipeline then leaves that device alive
|
|
1142
|
+
on disposal. Existing `scene.createSolver(device)` calls remain supported.
|
|
1143
|
+
|
|
1144
|
+
The browser benchmark includes an **experimental WebGL2
|
|
1145
|
+
solver for repository comparisons**. Playable demos offer WebGPU and CPU. WebGL2 is excluded from the npm package and
|
|
1146
|
+
cannot be selected through the published `createSolverPipeline()` API. Its
|
|
1147
|
+
measured performance was too poor for a production backend; see the
|
|
1148
|
+
[side-by-side results](#backend-differences-and-benchmarks).
|
|
1149
|
+
|
|
1150
|
+
The [slingshot siege](https://babylon-avbd.netlify.app/?demo=slingshot) also has
|
|
1151
|
+
a physics selector. CPU and WebGPU modes include the same ropes, motors, breakable joints,
|
|
1152
|
+
collision masks, sensors and bounce, with all three fort sizes available.
|
|
1153
|
+
CPU mode draws with Canvas 2D and does not require WebGPU.
|
|
1154
|
+
|
|
1155
|
+
| Experimental WebGL2 benchmark preview with Babylon WebGL drawing | Experimental WebGL2 cloth benchmark preview |
|
|
1156
|
+
| ----------------------------------------------------------------- | ------------------------------------------------------------------------------- |
|
|
1157
|
+
|  |  |
|
|
1158
|
+
|
|
1159
|
+
[Slingshot compatibility checks](test-results/cpu-slingshot.json) cover launching,
|
|
1160
|
+
boosting and adding another ball on CPU and WebGPU.
|
|
1161
|
+
|
|
1162
|
+
The [shared API checks](test-results/backend-contract.json) run the same 2D/3D
|
|
1163
|
+
operations on the published CPU and WebGPU backends. Separate [experimental WebGL2 checks](test-results/webgl-contract.json) include cloth points, live constraint frames, sensors, masks, restitution and sleeping. The [CPU scene checks](test-results/cpu-demos.json)
|
|
1164
|
+
also cover the original scene collection, renderer switching and startup without
|
|
1165
|
+
WebGPU.
|
|
1166
|
+
|
|
1167
|
+
| CPU 3D with Babylon WebGL drawing | CPU 2D with Canvas drawing |
|
|
1168
|
+
| --------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------ |
|
|
1169
|
+
|  |  |
|
|
1170
|
+
|
|
1171
|
+
| CPU rope | CPU chainmail |
|
|
1172
|
+
| ------------------------------------------------------------- | ---------------------------------------------------------------------------------------------- |
|
|
1173
|
+
|  |  |
|
|
1174
|
+
|
|
1175
|
+
[CPU feature checks](test-results/cpu-features.json) include these scenes, cloth,
|
|
1176
|
+
hinges, sensors, collision masks, mixed shapes and demolition.
|
|
1177
|
+
|
|
1178
|
+
### Backend differences and benchmarks
|
|
1179
|
+
|
|
1180
|
+
The published native 2D/3D API supports **WebGPU and CPU**. The WebGL2 column
|
|
1181
|
+
describes the repository experiment, which remains playable in the demos and
|
|
1182
|
+
is **excluded from the npm package**. The same scene commands are used in these
|
|
1183
|
+
comparisons; execution, rendering and memory costs differ.
|
|
1184
|
+
|
|
1185
|
+
| Capability | WebGPU (default) | WebGL2 (experimental benchmark only) | CPU |
|
|
1186
|
+
| ----------------------------------------------------------------- | -------------------------------------- | --------------------------------------------------------------- | ------------------------------------------------ |
|
|
1187
|
+
| Included in the npm package | Yes | No | Yes |
|
|
1188
|
+
| 2D/3D colliders, joints, welds, springs, cloth, motors and limits | Supported | Supported | Supported |
|
|
1189
|
+
| Bounce, triggers, masks, sleeping and batched body commands | Supported | Supported | Supported |
|
|
1190
|
+
| Simulation collision detection and AVBD solving | GPU compute | GPU texture/draw passes | JavaScript on the CPU |
|
|
1191
|
+
| Direct drawing from public GPU pose buffers | Yes; renderer shares the GPU device | Poses must be downloaded | Renderer reads CPU poses |
|
|
1192
|
+
| Selected body reads | GPU gathers requested bodies | GPU gathers requested bodies | Copies requested CPU records |
|
|
1193
|
+
| Ray and circle/sphere casts | GPU queries | Downloads current poses, then CPU queries | CPU queries |
|
|
1194
|
+
| Contact events | GPU results downloaded when enabled | GPU results downloaded when enabled | CPU results |
|
|
1195
|
+
| Babylon aggregate API (`AvbdPhysics`, `AvbdPhysicsAggregate`) | Supported | Use native worlds and synchronize renderer poses | Use native worlds and synchronize renderer poses |
|
|
1196
|
+
| Native worlds with Babylon or another renderer | Supported | Supported | Supported |
|
|
1197
|
+
| Module workers | WebGPU device in worker | Offscreen WebGL2 compute context in worker | JavaScript worker |
|
|
1198
|
+
| Large-scene limits | GPU buffer limits and available memory | More memory per candidate; default 2 GiB contact-texture budget | Browser memory and CPU time |
|
|
1199
|
+
|
|
1200
|
+
WebGPU is the best starting point for large scenes and direct GPU rendering.
|
|
1201
|
+
CPU can be quicker for a handful of objects because it avoids GPU submission
|
|
1202
|
+
costs. The WebGL2 experiment needs `EXT_color_buffer_float` and uses more memory
|
|
1203
|
+
than WebGPU.
|
|
1204
|
+
The 100K and half-million benchmarks can exceed WebGL2's default budget. Increasing
|
|
1205
|
+
`maxTextureBytes` only helps when the device has enough memory.
|
|
1206
|
+
|
|
1207
|
+
<!-- backend-comparison-results -->
|
|
1208
|
+
|
|
1209
|
+
**Measured on an NVIDIA GeForce RTX 4070 Laptop GPU and Intel Core Ultra 7 155H CPU**,
|
|
1210
|
+
using headless Chrome 149 on Windows, October 10, 2026. Each scene uses
|
|
1211
|
+
a 1/60-second timestep and 10 solver iterations. There are 60 warmup steps
|
|
1212
|
+
and 60 measured steps per run, with three fresh-world repeats: **180 samples
|
|
1213
|
+
per scene and backend**. Drawing and sleeping are disabled.
|
|
1214
|
+
|
|
1215
|
+
**Mean physics time per step — lower is faster:**
|
|
1216
|
+
|
|
1217
|
+
| Scene | Objects, including fixed bodies | Iterations | WebGPU: GPU ms | Experimental WebGL2: GPU ms | CPU: JavaScript ms |
|
|
1218
|
+
| -------------------------------------------------------------------------- | ------------------------------: | ---------: | -------------: | --------------------------: | -----------------: |
|
|
1219
|
+
| [2D stack](index.html?demo=canonical&scene=2d-stack&backend=gpu) | 21 | 10 | 1.810 | 38.241 | 0.299 |
|
|
1220
|
+
| [2D joint grid](index.html?demo=canonical&scene=2d-joint-grid&backend=gpu) | 625 | 10 | 3.614 | 51.396 | 4.323 |
|
|
1221
|
+
| [3D stack](index.html?demo=canonical&scene=3d-stack&backend=gpu) | 11 | 10 | 2.862 | 25.716 | 0.794 |
|
|
1222
|
+
| [3D pyramid](index.html?demo=canonical&scene=3d-pyramid&backend=gpu) | 137 | 10 | 4.056 | 57.057 | 14.776 |
|
|
1223
|
+
|
|
1224
|
+
GPU columns measure the GPU interval for collisions, scheduling and solving.
|
|
1225
|
+
The CPU column measures JavaScript executing the complete physics step.
|
|
1226
|
+
Startup, drawing and waiting for timing results are excluded. These numbers
|
|
1227
|
+
are physics timings; a rendered frame also needs CPU submission and drawing.
|
|
1228
|
+
|
|
1229
|
+
**Additional JavaScript time to submit GPU work, in ms per step:**
|
|
1230
|
+
|
|
1231
|
+
| Scene | WebGPU submission | Experimental WebGL2 submission |
|
|
1232
|
+
| ------------- | ----------------: | -----------------------------: |
|
|
1233
|
+
| 2D stack | 0.164 | 41.617 |
|
|
1234
|
+
| 2D joint grid | 0.197 | 55.267 |
|
|
1235
|
+
| 3D stack | 0.207 | 27.073 |
|
|
1236
|
+
| 3D pyramid | 0.666 | 54.471 |
|
|
1237
|
+
|
|
1238
|
+
Submission can overlap GPU execution, so these two tables should not be added
|
|
1239
|
+
together or converted directly into FPS. The raw report also records the
|
|
1240
|
+
time spent waiting for each measured step and the startup cost.
|
|
1241
|
+
|
|
1242
|
+
CPU wins the two small-stack cases here. WebGPU is quicker for the 625-object
|
|
1243
|
+
joint grid and the 137-object pyramid. The crossover depends on contact density
|
|
1244
|
+
and joints as well as object count. WebGL2 is the slowest of the three in these
|
|
1245
|
+
runs; its many texture/draw passes carry substantial CPU submission overhead.
|
|
1246
|
+
Its performance was too poor for inclusion in the main package. WebGL2 remains
|
|
1247
|
+
available in the experimental benchmark; WebGPU is the default and
|
|
1248
|
+
CPU is the packaged compatibility option.
|
|
1249
|
+
|
|
1250
|
+
<!-- /backend-comparison-results -->
|
|
1251
|
+
|
|
1252
|
+
Reproduce this comparison with `npm run benchmark:backends`, or open the
|
|
1253
|
+
[browser benchmark](tests/performance.html) and choose each physics backend.
|
|
1254
|
+
The script rotates backend order between repeats, builds a fresh world for each
|
|
1255
|
+
run, verifies matching object counts and solver settings, and rejects collision
|
|
1256
|
+
overflow, conflicting solve groups and invalid poses. It saves the raw samples
|
|
1257
|
+
in [backend-comparison.json](test-results/backend-comparison.json).
|
|
1258
|
+
|
|
971
1259
|
### Native 3D world
|
|
972
1260
|
|
|
973
1261
|
This builds a box pile, reusable capsule projectile, trigger volume and motorized
|
|
974
1262
|
hinge. The default gravity axis is Y. Keep builder body references for bulk
|
|
975
|
-
edits; `
|
|
1263
|
+
edits; `solver.body(reference)` creates a convenient handle only when requested.
|
|
976
1264
|
|
|
977
1265
|
```js
|
|
978
1266
|
// src/portable-3d.js
|
|
979
|
-
import { AvbdScene3D,
|
|
980
|
-
|
|
981
|
-
export async function createPortable3D({
|
|
982
|
-
|
|
983
|
-
|
|
1267
|
+
import { AvbdScene3D, createSolverPipeline } from "avbd-babylon/native";
|
|
1268
|
+
|
|
1269
|
+
export async function createPortable3D({
|
|
1270
|
+
backend = "webgpu",
|
|
1271
|
+
device,
|
|
1272
|
+
count = 4096,
|
|
1273
|
+
} = {}) {
|
|
1274
|
+
const pipeline = await createSolverPipeline({ backend, device });
|
|
984
1275
|
const world = new AvbdScene3D({ gravity: -9.81, iterations: 5 });
|
|
985
1276
|
world.addBox([100, 1, 100], { mass: 0, position: [0, -0.5, 0] });
|
|
986
1277
|
const bodies = [];
|
|
@@ -1008,37 +1299,37 @@ export async function createPortable3D({ device, count = 4096 } = {}) {
|
|
|
1008
1299
|
});
|
|
1009
1300
|
const gate = world.addBox([4, 0.3, 0.3], { position: [0, 12, 0] });
|
|
1010
1301
|
const hinge = world.addHinge(null, gate, {
|
|
1302
|
+
anchorA: [0, 12, 0],
|
|
1011
1303
|
axisA: [0, 0, 1],
|
|
1012
1304
|
axisB: [0, 0, 1],
|
|
1013
1305
|
minAngle: -0.7,
|
|
1014
1306
|
maxAngle: 0.7,
|
|
1015
1307
|
motor: { speed: 1, maxTorque: 10 },
|
|
1016
1308
|
});
|
|
1017
|
-
const
|
|
1309
|
+
const solver = world.createSolver(pipeline, {
|
|
1018
1310
|
broadphase: "auto",
|
|
1019
1311
|
sleeping: true,
|
|
1020
1312
|
});
|
|
1021
1313
|
return {
|
|
1022
|
-
device,
|
|
1023
|
-
|
|
1314
|
+
device: pipeline.device,
|
|
1315
|
+
solver,
|
|
1024
1316
|
bodies,
|
|
1025
1317
|
hinge,
|
|
1026
|
-
projectile:
|
|
1027
|
-
sensor:
|
|
1318
|
+
projectile: solver.body(projectile),
|
|
1319
|
+
sensor: solver.body(sensor),
|
|
1028
1320
|
dispose() {
|
|
1029
|
-
|
|
1030
|
-
if (ownsDevice) device.destroy();
|
|
1321
|
+
pipeline.destroy();
|
|
1031
1322
|
},
|
|
1032
1323
|
};
|
|
1033
1324
|
}
|
|
1034
1325
|
```
|
|
1035
1326
|
|
|
1036
|
-
`projectile.applyImpulse([20, 0, 0])` uses its current
|
|
1327
|
+
`projectile.applyImpulse([20, 0, 0])` uses its current centre. A world-point
|
|
1037
1328
|
argument also adds the appropriate rotation. `teleport(position, quaternion?)`
|
|
1038
1329
|
preserves velocity and clears old contact warm starts for that body. Position
|
|
1039
1330
|
means centre of mass; custom hulls use their principal frame. Numeric arguments
|
|
1040
|
-
to `
|
|
1041
|
-
account for
|
|
1331
|
+
to `solver.body()` are **live solver slots**; use builder references or
|
|
1332
|
+
`bodyIndex()` to account for any initial spatial sorting.
|
|
1042
1333
|
|
|
1043
1334
|
### Native 2D world
|
|
1044
1335
|
|
|
@@ -1049,11 +1340,14 @@ a spring and a breakable weld.
|
|
|
1049
1340
|
|
|
1050
1341
|
```js
|
|
1051
1342
|
// src/portable-2d.js
|
|
1052
|
-
import { AvbdScene2D,
|
|
1053
|
-
|
|
1054
|
-
export async function createPortable2D({
|
|
1055
|
-
|
|
1056
|
-
|
|
1343
|
+
import { AvbdScene2D, createSolverPipeline } from "avbd-babylon/native";
|
|
1344
|
+
|
|
1345
|
+
export async function createPortable2D({
|
|
1346
|
+
backend = "webgpu",
|
|
1347
|
+
device,
|
|
1348
|
+
count = 4096,
|
|
1349
|
+
} = {}) {
|
|
1350
|
+
const pipeline = await createSolverPipeline({ backend, device });
|
|
1057
1351
|
const world = new AvbdScene2D({ gravity: -9.81, iterations: 5 });
|
|
1058
1352
|
world.addPlane([0, 1]);
|
|
1059
1353
|
world.addSegment([-20, 4], [-16, 4]);
|
|
@@ -1095,17 +1389,16 @@ export async function createPortable2D({ device, count = 4096 } = {}) {
|
|
|
1095
1389
|
stiffness: [Infinity, Infinity, Infinity],
|
|
1096
1390
|
breakForce: 100,
|
|
1097
1391
|
});
|
|
1098
|
-
const
|
|
1392
|
+
const solver = world.createSolver(pipeline, { sleeping: true });
|
|
1099
1393
|
return {
|
|
1100
|
-
device,
|
|
1101
|
-
|
|
1394
|
+
device: pipeline.device,
|
|
1395
|
+
solver,
|
|
1102
1396
|
bodies,
|
|
1103
1397
|
hinge,
|
|
1104
|
-
projectile:
|
|
1105
|
-
sensor:
|
|
1398
|
+
projectile: solver.body(projectile),
|
|
1399
|
+
sensor: solver.body(sensor),
|
|
1106
1400
|
dispose() {
|
|
1107
|
-
|
|
1108
|
-
if (ownsDevice) device.destroy();
|
|
1401
|
+
pipeline.destroy();
|
|
1109
1402
|
},
|
|
1110
1403
|
};
|
|
1111
1404
|
}
|
|
@@ -1118,7 +1411,7 @@ projectile.setTrigger(false).setRestitution(0.5).setSleepEnabled(false);
|
|
|
1118
1411
|
projectile.setCollisionGroups(2, 1 | 2);
|
|
1119
1412
|
// 3D uses xyz; use [20, 0] for the 2D projectile.
|
|
1120
1413
|
projectile.applyImpulse([20, 0, 0]);
|
|
1121
|
-
|
|
1414
|
+
solver.setMotor(hinge.motor.slot, { speed: -1, maxTorque: 10 });
|
|
1122
1415
|
```
|
|
1123
1416
|
|
|
1124
1417
|
### Bulk edits and GPU dispatches
|
|
@@ -1150,7 +1443,7 @@ For 100,000 bodies this example uploads 200,000 commands together, rather than
|
|
|
1150
1443
|
submitting a GPU job per command. Each GPU invocation owns one edited body and
|
|
1151
1444
|
executes its commands in order. Command/range buffers grow when needed and are
|
|
1152
1445
|
reused. Velocity/force batches dispatch only edited bodies; teleport batches
|
|
1153
|
-
also scan previous manifolds to clear affected contact warm starts. Native 3D
|
|
1446
|
+
also scan previous manifolds to clear affected contact warm starts. Native 3D
|
|
1154
1447
|
teleport cleanup shares the edit submission.
|
|
1155
1448
|
|
|
1156
1449
|
```js
|
|
@@ -1176,16 +1469,19 @@ field; motion commands retain their order.
|
|
|
1176
1469
|
```js
|
|
1177
1470
|
// src/portable-properties.js
|
|
1178
1471
|
export function configureBodies(gpu, bodies) {
|
|
1179
|
-
gpu.editBodies(
|
|
1180
|
-
|
|
1181
|
-
body
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1472
|
+
gpu.editBodies(
|
|
1473
|
+
(function* () {
|
|
1474
|
+
for (const body of bodies)
|
|
1475
|
+
yield {
|
|
1476
|
+
body,
|
|
1477
|
+
isTrigger: false,
|
|
1478
|
+
restitution: 0.4,
|
|
1479
|
+
group: 2,
|
|
1480
|
+
collidesWith: 1 | 2,
|
|
1481
|
+
allowSleep: true,
|
|
1482
|
+
};
|
|
1483
|
+
})(),
|
|
1484
|
+
);
|
|
1189
1485
|
gpu.step(); // Applies the edits, waking, bounce and solving in one submission.
|
|
1190
1486
|
}
|
|
1191
1487
|
```
|
|
@@ -1196,6 +1492,10 @@ time. Batch motor changes by calling `setMotor()` for each motor and stepping
|
|
|
1196
1492
|
once; the uploads touch the target fields and preserve live constraint forces.
|
|
1197
1493
|
Removing or reusing a constraint applies its earlier edits first.
|
|
1198
1494
|
|
|
1495
|
+
WebGPU also reuses its solver-setting upload storage. Unchanged settings stay
|
|
1496
|
+
on the GPU; live changes to gravity, timestep, wind and solver options are
|
|
1497
|
+
uploaded automatically. This applies to both native and Babylon integrations.
|
|
1498
|
+
|
|
1199
1499
|
For frequent velocity updates, use packed arrays. This submits one dispatch,
|
|
1200
1500
|
after flushing earlier queued edits. Each index must be a unique GPU slot in
|
|
1201
1501
|
ascending order. The arrays can be reused each frame; values are copied into GPU
|
|
@@ -1231,13 +1531,21 @@ use compact uploads when the batch contains only linear and angular replacements
|
|
|
1231
1531
|
```js
|
|
1232
1532
|
// src/portable-motion.js
|
|
1233
1533
|
export function createMotionController(gpu, bodies, dimension = 3) {
|
|
1234
|
-
const indices = Uint32Array.from(bodies, body =>
|
|
1534
|
+
const indices = Uint32Array.from(bodies, (body) =>
|
|
1535
|
+
gpu.bodyIndex(body),
|
|
1536
|
+
).sort();
|
|
1235
1537
|
const linear = new Float32Array(indices.length * dimension);
|
|
1236
1538
|
const angular = new Float32Array(indices.length * (dimension === 3 ? 3 : 1));
|
|
1237
1539
|
return {
|
|
1238
|
-
indices,
|
|
1239
|
-
|
|
1240
|
-
|
|
1540
|
+
indices,
|
|
1541
|
+
linear,
|
|
1542
|
+
angular,
|
|
1543
|
+
update() {
|
|
1544
|
+
gpu.setVelocities(indices, { linear, angular });
|
|
1545
|
+
},
|
|
1546
|
+
updateSpin() {
|
|
1547
|
+
gpu.setAngularVelocities(indices, angular);
|
|
1548
|
+
},
|
|
1241
1549
|
};
|
|
1242
1550
|
}
|
|
1243
1551
|
```
|
|
@@ -1252,15 +1560,15 @@ Saved 100,000-body edit measurements from the machine with an **RTX 4070 Laptop
|
|
|
1252
1560
|
GPU**, using five samples after warmup:
|
|
1253
1561
|
|
|
1254
1562
|
| World | Individual body calls, CPU time | `editBodies()`, CPU time | Packed arrays, CPU time | Upload for each method |
|
|
1255
|
-
| ----- | ------------------------------: |
|
|
1256
|
-
| 2D
|
|
1257
|
-
| 3D
|
|
1563
|
+
| ----- | ------------------------------: | -----------------------: | ----------------------: | ---------------------: |
|
|
1564
|
+
| 2D | 11.8 ms | 21.0 ms | 3.2 ms | 1.2 MB |
|
|
1565
|
+
| 3D | 39.8 ms | 70.9 ms | 5.2 ms | 1.6 MB |
|
|
1258
1566
|
|
|
1259
1567
|
These times measure CPU work to prepare and submit velocity edits. They exclude
|
|
1260
1568
|
physics solving and drawing; the GPU completion measurements are recorded
|
|
1261
1569
|
separately. This compares three ways to submit the same edits within this package.
|
|
1262
1570
|
Selected full body records matched exactly. Run `npm run benchmark:native-edits`
|
|
1263
|
-
to measure your own computer. Caller arrays and body handles are prepared before
|
|
1571
|
+
to measure your own computer. Caller arrays and body handles are prepared before
|
|
1264
1572
|
timing and reused. [Raw results](test-results/native-api-performance.json).
|
|
1265
1573
|
|
|
1266
1574
|
The [native API report](test-results/package-performance.html) also measures
|
|
@@ -1365,11 +1673,11 @@ orientations. A spring without `rest` captures the current anchor separation.
|
|
|
1365
1673
|
`breakOnPull` adds linear force to the angular-force break test; it is not a
|
|
1366
1674
|
separate tensile-only material model. Joint changes and commands wake sleeping
|
|
1367
1675
|
bodies. Constraints can be created before initialization or during simulation.
|
|
1368
|
-
Springs
|
|
1676
|
+
Springs support tensile fracture through `breakForce` and `breakStrain`.
|
|
1369
1677
|
|
|
1370
1678
|
For large scenes, the portable builders below avoid making one Babylon mesh per
|
|
1371
1679
|
collider. They are also exported by the package, require no custom shader loader,
|
|
1372
|
-
and
|
|
1680
|
+
and use WebGPU by default. The first portable example uses **z up** explicitly;
|
|
1373
1681
|
the rope, ragdoll and fabric helpers below use **y up**.
|
|
1374
1682
|
|
|
1375
1683
|
### Ball joints, welds and springs
|
|
@@ -1485,7 +1793,7 @@ For the complete rendered presets, open [Rope](index.html?demo=canonical&scene=3
|
|
|
1485
1793
|
[Ragdolls on a rigid net](index.html?demo=canonical&scene=showcase-ragdolls-on-cloth-24k&backend=gpu),
|
|
1486
1794
|
[Breakable](index.html?demo=canonical&scene=3d-breakable&backend=gpu)
|
|
1487
1795
|
and [Castle siege](index.html?demo=cannon). The
|
|
1488
|
-
[castle builder](demo/
|
|
1796
|
+
[castle builder](demo/castle/scene.js) shows connected mortar and roof joints.
|
|
1489
1797
|
|
|
1490
1798
|
### Tearable cloth
|
|
1491
1799
|
|
|
@@ -1561,9 +1869,9 @@ one per simulated point. Intact edges stretch together; broken edges keep each
|
|
|
1561
1869
|
point's share of the material, following its live pose. Detached patches remain
|
|
1562
1870
|
visible and physical indefinitely. There is no automatic debris lifetime.
|
|
1563
1871
|
This is a point-sampled cloth model, not an independent rigid-body shard model.
|
|
1564
|
-
The web demo's [cloth renderer](demo/
|
|
1872
|
+
The web demo's [cloth renderer](demo/tearable-cloth/renderer.js) reads those
|
|
1565
1873
|
stiffness values on the GPU, so drawing does not download cloth poses or joint
|
|
1566
|
-
states. Its [scene builder](demo/
|
|
1874
|
+
states. Its [scene builder](demo/tearable-cloth/scene.js) records which
|
|
1567
1875
|
connections support each triangle. The renderer is application code, excluded
|
|
1568
1876
|
from the package.
|
|
1569
1877
|
|
|
@@ -1790,7 +2098,8 @@ physics.setGravity(new Vector3(0, -9.81, 0));
|
|
|
1790
2098
|
Commands apply at the next step against live GPU mass, inertia and position.
|
|
1791
2099
|
Impulses queued for the same body are combined. Omitting the application point
|
|
1792
2100
|
applies an impulse through the center of mass. Teleport retains velocity unless
|
|
1793
|
-
you also set it
|
|
2101
|
+
you also set it; omitting the quaternion preserves the live GPU rotation.
|
|
2102
|
+
The position is the mesh origin. To remove an object, dispose its aggregate and its mesh:
|
|
1794
2103
|
|
|
1795
2104
|
```js
|
|
1796
2105
|
fallingBox.dispose(); // removes collisions; does not dispose the visual mesh
|
|
@@ -1893,7 +2202,13 @@ console.log(await physics.readSleepStats());
|
|
|
1893
2202
|
```
|
|
1894
2203
|
|
|
1895
2204
|
Impulses, teleports, constraint edits, new bodies and removed supports wake the
|
|
1896
|
-
world.
|
|
2205
|
+
world. Jointless desktop WebGPU worlds with at least 4,096 bodies automatically track conservative connected
|
|
2206
|
+
groups, so an impact wakes the touched group while unrelated piles stay asleep.
|
|
2207
|
+
The static floor does not connect separate piles. Connections persist while
|
|
2208
|
+
bodies sleep and reset on explicit world edits; a group can therefore include
|
|
2209
|
+
objects that touched earlier. Smaller worlds and mobile compatibility paths use
|
|
2210
|
+
whole-world impact waking. Set `sleeping: { islands: true }` to opt into connected
|
|
2211
|
+
groups in a smaller 3D world, or `islands: false` to use whole-world waking. Under
|
|
1897
2212
|
gravity, unsupported slow-moving bodies stay awake; supports extend upward
|
|
1898
2213
|
through resting contacts. This does not guarantee that every suspended or
|
|
1899
2214
|
jointed object will sleep. Sleeping is off by default unless a body explicitly
|
|
@@ -1902,6 +2217,32 @@ world makes bodies eligible by default; `allowSleep:false` excludes a body.
|
|
|
1902
2217
|
Paper comparisons keep sleeping disabled. Portable 3D solvers accept these
|
|
1903
2218
|
same world and initial-body options.
|
|
1904
2219
|
|
|
2220
|
+
Small jointless 3D worlds with up to 32 bodies also batch all movement iterations
|
|
2221
|
+
into one GPU workgroup. Body poses stay in shared memory; contacts remain in GPU
|
|
2222
|
+
storage. The equations, contact order, lane reductions and iteration count stay
|
|
2223
|
+
the same. Adding bodies beyond that limit or adding a joint automatically selects
|
|
2224
|
+
the ordinary movement pipeline. Mobile compatibility paths retain their qualified
|
|
2225
|
+
pipeline. `smallGroups: false` disables this batching for a controlled comparison.
|
|
2226
|
+
|
|
2227
|
+
In two RTX 4070 Laptop GPU runs of the half-million-wall scene, connected-group
|
|
2228
|
+
waking took **9.90–10.14 ms** per later impact step, versus **55.05–55.28 ms**
|
|
2229
|
+
with whole-world waking. Both used four iterations and the same sleep thresholds;
|
|
2230
|
+
457,095 bricks remained asleep with connected groups. Fully awake steps took
|
|
2231
|
+
53.91–54.24 ms with connected groups and 53.39 ms with whole-world waking.
|
|
2232
|
+
These numbers include sleep checks and exclude drawing. This is a sleeping
|
|
2233
|
+
comparison, separate from the paper's awake workload. See the
|
|
2234
|
+
[measurements](test-results/connected-sleep-performance.json) and
|
|
2235
|
+
[visual comparison](test-results/solver-performance.html).
|
|
2236
|
+
|
|
2237
|
+

|
|
2238
|
+
|
|
2239
|
+
```sh
|
|
2240
|
+
# From a repository checkout: two runs, 480 steps per world, 4 iterations, no drawing.
|
|
2241
|
+
npm run benchmark:connected-sleep
|
|
2242
|
+
# Compare identical small-world physics with separate and batched movement.
|
|
2243
|
+
npm run benchmark:small-groups
|
|
2244
|
+
```
|
|
2245
|
+
|
|
1905
2246
|
The [browser performance page](tests/performance.html) has a separate sleeping
|
|
1906
2247
|
benchmark, including the cost of sleep checks while bodies are awake. It reports
|
|
1907
2248
|
the number of boxes asleep and the difference in their final positions. See
|
|
@@ -2136,8 +2477,7 @@ nearest-contact matching and no extra position-correction pass. The original
|
|
|
2136
2477
|
and defaults to `alpha: 0.99` plus a final correction pass. For an explicit
|
|
2137
2478
|
comparison, construct `AvbdScene2D({ alpha: 0.99, postStabilize: true,
|
|
2138
2479
|
matchNearest: false })`. This changes solver settings, not the execution
|
|
2139
|
-
backend
|
|
2140
|
-
AVBD method.
|
|
2480
|
+
backend selected by the pipeline. These settings preserve the core AVBD method.
|
|
2141
2481
|
|
|
2142
2482
|
## 2D sleeping and hinge stops
|
|
2143
2483
|
|
|
@@ -2220,11 +2560,19 @@ checks, and shows the cost while every body is awake as well as after settling.
|
|
|
2220
2560
|
|
|
2221
2561
|
After `npm start`, open [Slingshot siege](index.html?demo=slingshot).
|
|
2222
2562
|
Pull the red ball back and release; Space fires, Next ball reloads and Rebuild
|
|
2223
|
-
restores the forts.
|
|
2563
|
+
restores the forts. A dashed trajectory line follows your aim; the full game
|
|
2564
|
+
also marks the first predicted obstacle using batched circle casts. Mouse and
|
|
2565
|
+
touch dragging work in the full game and the worker comparison view, including
|
|
2566
|
+
shared input. Choose 1,000, 5,000 or 10,000 bricks. Larger scenes use finer courses
|
|
2567
|
+
with the same practical building height. Select a target and press
|
|
2568
|
+
**Aim at target** for a high arc, or aim manually. Targets turn gold when
|
|
2569
|
+
knocked out by strong hits from the ball, falling debris, or a long fall.
|
|
2570
|
+
Smaller impacts accumulate damage, and defeated enemies stay defeated. Clear
|
|
2571
|
+
every target to win. Target counts and bricks
|
|
2224
2572
|
moved show the impact; the collapsible display shows frame rate, contacts,
|
|
2225
2573
|
sleeping bodies and drawing time. Toggle Sleeping to compare the same scene.
|
|
2226
2574
|
Scroll to zoom; Shift-drag pans the view. Boost gives each launched ball one
|
|
2227
|
-
extra impulse and a short thrust. The fixed timestep is 1/120 second with
|
|
2575
|
+
extra impulse and a short thrust. The fixed timestep is 1/120 second with sixteen
|
|
2228
2576
|
solver rounds; the meter shows the actual physics rate alongside frame rate.
|
|
2229
2577
|
|
|
2230
2578
|
The level uses every supported category of 2D physics:
|
|
@@ -2233,7 +2581,7 @@ The level uses every supported category of 2D physics:
|
|
|
2233
2581
|
| ----------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
2234
2582
|
| Boxes, circles, capsules, convex hulls, segments and planes | Bricks, balls, chain links, roof tiles, launcher and ground |
|
|
2235
2583
|
| Joints, world anchors and ropes | Hanging targets and linked ragdoll limbs |
|
|
2236
|
-
| Welds and fracture |
|
|
2584
|
+
| Welds and fracture | Connected masonry walls, foundations and roof tiles use locked joints with a break force; impacts break the mortar |
|
|
2237
2585
|
| Springs | Elastic platforms under the small ragdolls |
|
|
2238
2586
|
| Limited hinges and motors | Powered blue gates reverse at their angle stops; toggle Powered gates |
|
|
2239
2587
|
| Friction and restitution | Bricks grip their supports; Bouncy ball changes the projectile's bounce |
|
|
@@ -2246,8 +2594,8 @@ The renderer draws the GPU body buffer directly. Periodic diagnostic copies coun
|
|
|
2246
2594
|
moved bricks, update gate controls and show broken supports; drawing does not
|
|
2247
2595
|
download poses. Aiming queries run only while aiming. Predictions sample the
|
|
2248
2596
|
current scene and do not provide continuous collision detection.
|
|
2249
|
-
The source is in `demo/
|
|
2250
|
-
`demo/
|
|
2597
|
+
The source is in `demo/slingshot/scene.js` (level construction) and
|
|
2598
|
+
`demo/slingshot/index.js` (GPU controls, events and queries).
|
|
2251
2599
|
Its `2d-slingshot-siege` scene is also in the all-scene benchmark,
|
|
2252
2600
|
which keeps sleeping off for consistent solver measurements.
|
|
2253
2601
|
|
|
@@ -2362,6 +2710,388 @@ setup and submission off the UI thread; they do not accelerate GPU kernels.
|
|
|
2362
2710
|
For large scenes, keep direct GPU rendering in that worker instead of copying
|
|
2363
2711
|
every pose back to the page.
|
|
2364
2712
|
|
|
2713
|
+
## Static terrain: hills, caves and overhangs
|
|
2714
|
+
|
|
2715
|
+
The 32-vertex limit applies to one convex collider. A terrain can contain many
|
|
2716
|
+
small static pieces, preserving hills, concavities, openings and overhangs.
|
|
2717
|
+
Play [Hills, cave and overhang](index.html?demo=canonical&scene=3d-static-terrain&backend=gpu).
|
|
2718
|
+
|
|
2719
|
+

|
|
2720
|
+
|
|
2721
|
+
Heightfields use two triangular prisms per grid cell, extending to a solid base.
|
|
2722
|
+
Mesh terrain uses one thin prism per nondegenerate triangle, with backing behind
|
|
2723
|
+
the triangle's winding. The original front surface stays at the supplied vertices.
|
|
2724
|
+
Both use the existing GPU convex contacts and grid/HPLOC collision search; this
|
|
2725
|
+
is a tiled collision representation, not a dedicated triangle-mesh collision kernel.
|
|
2726
|
+
Internal tile edges can influence sliding contacts. Choose a collision mesh with
|
|
2727
|
+
enough detail for the bodies it supports, rather than using every rendering triangle.
|
|
2728
|
+
|
|
2729
|
+
```js
|
|
2730
|
+
import { AvbdScene3D, createSolverPipeline } from "avbd-babylon/native";
|
|
2731
|
+
|
|
2732
|
+
const scene = new AvbdScene3D({ timeStep: 1 / 120, iterations: 5 }); // y up
|
|
2733
|
+
const columns = 65,
|
|
2734
|
+
rows = 65;
|
|
2735
|
+
const heights = Float32Array.from({ length: columns * rows }, (_, i) => {
|
|
2736
|
+
const x = i % columns,
|
|
2737
|
+
z = Math.floor(i / columns);
|
|
2738
|
+
return 2 * Math.sin(x / 12) * Math.cos(z / 15);
|
|
2739
|
+
});
|
|
2740
|
+
const terrain = scene.addHeightfield({
|
|
2741
|
+
heights,
|
|
2742
|
+
columns,
|
|
2743
|
+
rows,
|
|
2744
|
+
spacing: 2,
|
|
2745
|
+
origin: [-64, 0, -64],
|
|
2746
|
+
baseHeight: -5,
|
|
2747
|
+
friction: 0.65,
|
|
2748
|
+
// Optional holes: one nonzero entry per cell removes both triangles.
|
|
2749
|
+
});
|
|
2750
|
+
scene.addSphere(0.5, { position: [0, 8, 0] });
|
|
2751
|
+
const pipeline = await createSolverPipeline({ backend: "webgpu" });
|
|
2752
|
+
const solver = scene.createSolver(pipeline, { broadphase: "hploc" });
|
|
2753
|
+
solver.advance(1 / 60); // two fixed steps
|
|
2754
|
+
// terrain.dispose(); disables collisions without shifting body indices.
|
|
2755
|
+
// pipeline.destroy(); releases the solver and its owned GPU device.
|
|
2756
|
+
```
|
|
2757
|
+
|
|
2758
|
+
For an arbitrary static mesh, use world-space vertices and triangle indices:
|
|
2759
|
+
|
|
2760
|
+
```js
|
|
2761
|
+
const cave = scene.addStaticMesh({
|
|
2762
|
+
positions: collisionVertices, // xyz triples; concave geometry is retained
|
|
2763
|
+
indices: collisionIndices, // omit for nonindexed triangles
|
|
2764
|
+
thickness: 0.15, // finite backing, in world units
|
|
2765
|
+
friction: 0.6,
|
|
2766
|
+
});
|
|
2767
|
+
```
|
|
2768
|
+
|
|
2769
|
+
Add terrain before creating the solver. Static mesh backing is finite; it does
|
|
2770
|
+
not fill an entire mountain or implement continuous collision detection. Heights
|
|
2771
|
+
are row-major, `origin` offsets the grid, and `upAxis` defaults to 1 (Y); use 2
|
|
2772
|
+
for Z-up worlds. Terrain also works with the CPU pipeline, with higher costs.
|
|
2773
|
+
|
|
2774
|
+
Terrain cooking uses each triangle prism's known topology and computes its mass
|
|
2775
|
+
properties in a local frame. All source triangles and their backing thickness
|
|
2776
|
+
are retained, including caves and overhangs.
|
|
2777
|
+
|
|
2778
|
+
In a Babylon world, the same heightfield helper is available. An existing Babylon
|
|
2779
|
+
mesh can supply its triangles directly, including its current world transform:
|
|
2780
|
+
|
|
2781
|
+
Keep the original mesh visible. `addStaticMesh()` cooks its triangles into static
|
|
2782
|
+
collision pieces; it does not replace the rendered asset with those pieces or
|
|
2783
|
+
turn a cave into one solid convex hull. The terrain demo draws the original
|
|
2784
|
+
ground and cave surfaces with smooth lighting, while the collision pieces stay
|
|
2785
|
+
hidden. Its **Load OBJ or terrain JSON** control accepts OBJ files (Y up) or JSON containing
|
|
2786
|
+
`positions` and `indices` (Z up), centered and scaled for the preview. The demo
|
|
2787
|
+
import is limited to 10,000 triangles; production projects can load assets with
|
|
2788
|
+
their usual Babylon loader and pass the resulting mesh to the helper below.
|
|
2789
|
+
|
|
2790
|
+
```js
|
|
2791
|
+
const physics = await AvbdPhysics.create({
|
|
2792
|
+
scene: babylonScene,
|
|
2793
|
+
capacity: 20_000, // includes terrain pieces and dynamic bodies
|
|
2794
|
+
});
|
|
2795
|
+
const ground = physics.addStaticMesh(groundMesh, {
|
|
2796
|
+
thickness: 0.2,
|
|
2797
|
+
friction: 0.7,
|
|
2798
|
+
});
|
|
2799
|
+
// Or: physics.addHeightfield({ heights, columns, rows, spacing: 2 });
|
|
2800
|
+
// Add dynamic aggregates before the first render/initialize.
|
|
2801
|
+
// ground.dispose(); removes collision participation; groundMesh remains yours.
|
|
2802
|
+
```
|
|
2803
|
+
|
|
2804
|
+
Terrain is static and does not follow later mesh edits. Each piece has a body
|
|
2805
|
+
identity for masks, contacts and queries; there is no per-frame mesh synchronization
|
|
2806
|
+
for terrain. Reserve enough `capacity` before initialization. For large assets,
|
|
2807
|
+
`cookHeightfield()` and `cookStaticMesh()` return serializable geometry that can
|
|
2808
|
+
be prepared in another worker and registered with `addTerrain(cooked, material)`.
|
|
2809
|
+
Cooking happens once, outside the simulation loop.
|
|
2810
|
+
|
|
2811
|
+

|
|
2812
|
+
|
|
2813
|
+
The Babylon mesh helper uses vertex normals to keep the backing behind the
|
|
2814
|
+
visible surface, including mirrored transforms. Meshes without normals use
|
|
2815
|
+
their triangle winding to define the front surface.
|
|
2816
|
+
|
|
2817
|
+
## Ships, water and destruction
|
|
2818
|
+
|
|
2819
|
+
Play [Little ships, big trouble](index.html?demo=naval). Two composite ships trade
|
|
2820
|
+
cannon fire on a 128 × 128 shallow-water grid. Individual planks, deck strips,
|
|
2821
|
+
cabins and masts are welded rigid bodies. Hits release nearby pieces and leave hull
|
|
2822
|
+
openings. Each member's own submerged solid volume produces buoyancy; welds
|
|
2823
|
+
combine the lift from wooden pieces to support a dense keel. Released wood
|
|
2824
|
+
retains its buoyancy while the unsupported keel sinks. There is no flooding
|
|
2825
|
+
timer or trapped-air multiplier. Hull fragments persist until Reset.
|
|
2826
|
+
|
|
2827
|
+

|
|
2828
|
+

|
|
2829
|
+

|
|
2830
|
+
|
|
2831
|
+
Water height and flow feed four buoyancy samples per piece. Lift, drag and their
|
|
2832
|
+
lever arms produce translation, pitch and roll. Water, ship forces, damage and
|
|
2833
|
+
AVBD collisions run on WebGPU; drawing reads GPU poses directly. Wakes use
|
|
2834
|
+
approximate momentum forcing. This surface-water model does not resolve detailed
|
|
2835
|
+
underwater flow or watertight fluid inside arbitrary mesh interiors.
|
|
2836
|
+
|
|
2837
|
+
The rocky island has real static collision shapes. Hard contact loads can break
|
|
2838
|
+
the hull's welds. Use the island buttons to drive either ship ashore, or release
|
|
2839
|
+
the Coral hull's welds to compare floating wood with its sinking keel. Each sail
|
|
2840
|
+
is a spring-connected cloth grid attached to its yard and pushed by gusting wind.
|
|
2841
|
+
Sail particles have lightweight, buoyant material with an effective displacement
|
|
2842
|
+
thickness, so detached fabric floats and drifts rather than sinking. Water drag
|
|
2843
|
+
damps submerged fabric, and submerged portions receive less wind force.
|
|
2844
|
+
|
|
2845
|
+
The demo defaults to a dedicated worker when OffscreenCanvas is available. Its
|
|
2846
|
+
scope selector also offers main-thread execution and optional shared input.
|
|
2847
|
+
Automatic solid-collision selection uses HPLOC for this scene’s mix of large
|
|
2848
|
+
island rocks, ship parts and small sail particles. The existing joint graph is
|
|
2849
|
+
colored at startup to avoid unnecessary solver dispatches; live contacts still
|
|
2850
|
+
use GPU coloring. Both explicit grid and HPLOC modes remain available.
|
|
2851
|
+
See the [scene builder](demo/naval/scene.js), [water integration](demo/naval/water.js)
|
|
2852
|
+
and [water/force shader](demo/naval/water.wgsl). These are demo components; the
|
|
2853
|
+
general package API remains the rigid-body and constraint API.
|
|
2854
|
+
|
|
2855
|
+
```sh
|
|
2856
|
+
npm run test:naval # settling, cannon impacts, grounding and floating debris
|
|
2857
|
+
npm run benchmark:naval # matched grid / HPLOC physics, water and drawing timings
|
|
2858
|
+
```
|
|
2859
|
+
|
|
2860
|
+
## Worker and shared-memory benchmarks
|
|
2861
|
+
|
|
2862
|
+
Open the [interactive worker comparison](tests/workers.html). It can play the
|
|
2863
|
+
canonical scenes using direct GPU drawing or Babylon WebGPU thin instances,
|
|
2864
|
+
with physics and drawing together on the page or in a dedicated worker. CPU
|
|
2865
|
+
comparisons use Canvas 2D drawing in 2D and WebGPU drawing with pose uploads in
|
|
2866
|
+
3D. CPU 2D worker execution does not require WebGPU.
|
|
2867
|
+
|
|
2868
|
+
The scene browser and slingshot demo include an Execution selector that opens
|
|
2869
|
+
the matching scene in this worker play/benchmark view. Main thread, dedicated
|
|
2870
|
+
worker and shared-input worker can each be played or measured individually.
|
|
2871
|
+
The shared-input choice appears only when the host supports SharedArrayBuffer
|
|
2872
|
+
and cross-origin isolation. The Full scene controls link returns to the demo.
|
|
2873
|
+
|
|
2874
|
+
The main benefit of a CPU worker is responsive controls. It runs the same solver;
|
|
2875
|
+
moving it to a worker does not turn it into a parallel solver. The tables below
|
|
2876
|
+
show simulation time and page responsiveness separately. Shared input had no
|
|
2877
|
+
consistent additional speed advantage in these runs. Measurements include
|
|
2878
|
+
the naval scene, 100K boxes, a 10K 2D pile and Babylon rendering:
|
|
2879
|
+
[recorded results](test-results/worker-performance.json).
|
|
2880
|
+
|
|
2881
|
+
Worker timings are estimates and can fluctuate with browser scheduling,
|
|
2882
|
+
background activity and GPU clock speeds. Repeat runs before judging small
|
|
2883
|
+
differences. Workers are generally less affected by busy page controls and other
|
|
2884
|
+
main-thread work, so updates can be steadier. They still share the computer’s
|
|
2885
|
+
resources and do not guarantee faster or more consistent GPU timings.
|
|
2886
|
+
|
|
2887
|
+
<!-- worker-results:start -->
|
|
2888
|
+
Physics per step / drawing per frame, in **milliseconds**. Each cell shows the range of the two runs’ medians. Compare execution modes within a row; different rows use different workloads.
|
|
2889
|
+
|
|
2890
|
+
| Scene · physics · drawing | Main thread | Dedicated worker | Worker + shared input |
|
|
2891
|
+
| --- | ---: | ---: | ---: |
|
|
2892
|
+
| Hills and overhang · WebGPU · Direct drawing | 1.12–3.53 / 0.03–0.09 | 0.75–0.79 / 0.02 | 1.74–2.26 / 0.05 |
|
|
2893
|
+
| Ships, island and cloth sails · WebGPU · Direct drawing | 2.31–3.19 / 0.11–0.19 | 1.97–2.03 / 0.14–0.16 | 2.06–2.10 / 0.11 |
|
|
2894
|
+
| 100K box columns · WebGPU · Direct drawing | 3.05–3.13 / 1.02 | 3.02–3.38 / 1.02–1.05 | 3.06–3.32 / 1.02–1.07 |
|
|
2895
|
+
| 10K 2D pile · WebGPU · Direct drawing | 3.28–4.35 / 0.02–0.03 | 4.22–4.47 / 0.03 | 3.40–4.49 / 0.02–0.03 |
|
|
2896
|
+
| 3D pyramid · WebGPU · Babylon thin instances | 1.82–2.06 / 0.09 | 1.60–2.53 / 0.15 | 2.13–2.14 / 0.10 |
|
|
2897
|
+
| 2D pyramid · WebGPU · Babylon thin instances | 2.57–2.64 / 0.09–0.12 | 3.19–3.23 / 0.12–0.13 | 3.08–3.10 / 0.12 |
|
|
2898
|
+
| 3D pyramid · CPU · Direct drawing | 15.66–17.55 / 0.05 | 15.57–17.80 / 0.05–0.08 | 17.37–18.38 / 0.06–0.08 |
|
|
2899
|
+
| 2D joint grid · CPU · Canvas 2D | 4.96–5.20 / 0.57–0.62 | 4.97–5.19 / 0.58–0.68 | 4.49–4.74 / 0.58–0.64 |
|
|
2900
|
+
|
|
2901
|
+
Page delay p95, in **milliseconds**. Lower means the page’s controls get more frequent opportunities to respond. This includes browser timer scheduling.
|
|
2902
|
+
|
|
2903
|
+
| Scene · physics · drawing | Main thread | Dedicated worker | Worker + shared input |
|
|
2904
|
+
| --- | ---: | ---: | ---: |
|
|
2905
|
+
| Hills and overhang · WebGPU · Direct drawing | 1.40–1.53 | 1.04–1.12 | 1.14–1.20 |
|
|
2906
|
+
| Ships, island and cloth sails · WebGPU · Direct drawing | 1.10–1.66 | 1.08–1.38 | 1.17–1.36 |
|
|
2907
|
+
| 100K box columns · WebGPU · Direct drawing | 1.45–1.58 | 1.00–1.18 | 0.98–1.29 |
|
|
2908
|
+
| 10K 2D pile · WebGPU · Direct drawing | 1.29–1.54 | 1.04–1.36 | 0.93–1.14 |
|
|
2909
|
+
| 3D pyramid · WebGPU · Babylon thin instances | 1.54–1.75 | 1.34–1.51 | 1.03–1.08 |
|
|
2910
|
+
| 2D pyramid · WebGPU · Babylon thin instances | 1.65–1.75 | 1.22–1.26 | 1.05–1.57 |
|
|
2911
|
+
| 3D pyramid · CPU · Direct drawing | 19.54–20.61 | 1.09–1.18 | 1.01–1.24 |
|
|
2912
|
+
| 2D joint grid · CPU · Canvas 2D | 6.42–7.08 | 1.13–1.64 | 1.11–1.60 |
|
|
2913
|
+
|
|
2914
|
+
Recorded on **Intel Core Ultra 7 155H / RTX 4070 Laptop GPU**. All modes use the same scene, timestep, iterations, canvas and renderer. Physics and rendering run together in the selected scope. SharedArrayBuffer carries input only; it does not share GPU buffers or parallelize the solver.
|
|
2915
|
+
<!-- worker-results:end -->
|
|
2916
|
+
|
|
2917
|
+

|
|
2918
|
+
|
|
2919
|
+

|
|
2920
|
+
|
|
2921
|
+
```sh
|
|
2922
|
+
npm run benchmark:workers
|
|
2923
|
+
npm run test:workers
|
|
2924
|
+
```
|
|
2925
|
+
|
|
2926
|
+
The comparison warms up for 30 frames and measures 120 frames at one physics
|
|
2927
|
+
step per frame. It sends 120 pointer updates per second; main, message-worker
|
|
2928
|
+
and shared-input modes use the same scene, canvas, timestep and iterations.
|
|
2929
|
+
WebGPU times come from GPU timestamps; CPU physics uses wall-clock timing.
|
|
2930
|
+
Repeat on the target device before choosing a mode.
|
|
2931
|
+
|
|
2932
|
+
Shared input uses a small atomic mailbox for the latest pointer position and
|
|
2933
|
+
wheel total. Clicks, taps, keys, resize and commands are forwarded separately.
|
|
2934
|
+
Discrete event boundaries prevent newer pointer positions from overtaking a
|
|
2935
|
+
click. Shared memory does not share GPUBuffer objects or speed up GPU kernels.
|
|
2936
|
+
Keeping rendering in the physics worker avoids transferring all poses each frame.
|
|
2937
|
+
|
|
2938
|
+
SharedArrayBuffer requires cross-origin isolation. `npm start` supplies COOP/COEP
|
|
2939
|
+
headers, and [netlify.toml](netlify.toml) supplies the same headers when hosted.
|
|
2940
|
+
The ordinary message mode remains usable without shared memory. Cross-origin
|
|
2941
|
+
assets must be served with compatible CORS/CORP headers under this configuration.
|
|
2942
|
+
|
|
2943
|
+
A worker pool is useful for independent startup jobs, such as cooking terrain
|
|
2944
|
+
chunks. Parallelizing the CPU collision search or constraint solver itself
|
|
2945
|
+
requires changes to ownership, scheduling and result merging; the package does
|
|
2946
|
+
not currently run one physics world across a worker pool. Shared pose arrays
|
|
2947
|
+
could reduce copying if rendering lives in a separate thread, but that is not
|
|
2948
|
+
what the shared-input benchmark measures.
|
|
2949
|
+
|
|
2950
|
+
The demo's reusable [simulation client](demo/shared/runtime/simulationClient.js),
|
|
2951
|
+
[worker runtime](demo/shared/runtime/simulationRuntime.js) and [input mailbox](src/utils/sharedInput.js)
|
|
2952
|
+
show lifecycle, pointer forwarding, resize, batched steps and explicit readbacks.
|
|
2953
|
+
|
|
2954
|
+
## Reusable GPU and worker utilities
|
|
2955
|
+
|
|
2956
|
+
The reusable helpers used by the demos ship in the package. Import them from
|
|
2957
|
+
`avbd-babylon/utils` or the main entry; script-tag builds expose them on `AVBD`.
|
|
2958
|
+
They have no Babylon or DOM dependency and work in a dedicated worker.
|
|
2959
|
+
|
|
2960
|
+
| Utility | Purpose |
|
|
2961
|
+
| --- | --- |
|
|
2962
|
+
| `GpuSurfaceParticles` | The D20 demo's GPU particle sweeps, live particle buffer and optional asynchronous counters. |
|
|
2963
|
+
| `GpuTimer` | Nonblocking GPU timestamps for a compute or render pass. |
|
|
2964
|
+
| `GPUBufferRecycler` | Reuse released buffers with compatible usage and a free-memory budget. |
|
|
2965
|
+
| `GpuContactSample` | Gather a bounded set of contact dots directly on the GPU, rather than downloading the full contact set. |
|
|
2966
|
+
| `seedConstraintColoring` | Optional startup coloring for an existing native constraint graph; GPU coloring continues to check live contacts. |
|
|
2967
|
+
| `addGpuStepPass` | Append custom GPU forces or copies to the solver's existing submission, in 2D or 3D. Multiple features can register without overwriting each other. |
|
|
2968
|
+
| `WorkerCanvas` | DOM-free sizing and input-event adapter around an offscreen canvas. |
|
|
2969
|
+
| Shared input helpers | A nonblocking latest-state mailbox for continuous pointer input, with a message fallback when shared memory is unavailable. |
|
|
2970
|
+
|
|
2971
|
+
The active HPLOC collision tree, automatic work scheduling, bulk edits and
|
|
2972
|
+
selective pose reads are already part of the normal solver pipeline under
|
|
2973
|
+
[`src/`](src/). Example scenes, cameras, game rules and page controls remain
|
|
2974
|
+
in [`demo/`](demo/README.md).
|
|
2975
|
+
|
|
2976
|
+
```js
|
|
2977
|
+
import { createWebGPUDevice } from "avbd-babylon";
|
|
2978
|
+
import { GpuSurfaceParticles } from "avbd-babylon/utils";
|
|
2979
|
+
|
|
2980
|
+
const { device } = await createWebGPUDevice();
|
|
2981
|
+
const particles = new GpuSurfaceParticles(device, {
|
|
2982
|
+
capacity: 100_000,
|
|
2983
|
+
triangleCount: 1,
|
|
2984
|
+
readback: false, // No CPU counters needed for drawing.
|
|
2985
|
+
});
|
|
2986
|
+
particles.writeTriangles(new Float32Array([
|
|
2987
|
+
-10, -10, 0, 0, // a, padded to vec4
|
|
2988
|
+
10, -10, 0, 0, // b
|
|
2989
|
+
0, 10, 0, 0, // c
|
|
2990
|
+
]));
|
|
2991
|
+
particles.step({ dt: 1 / 60, count: 100_000, speed: 80, spawnRadius: 32 });
|
|
2992
|
+
// Bind particles.particleBuffer as read-only storage in your renderer.
|
|
2993
|
+
// Each 48-byte record contains position/life, velocity/radius, then a vec4u seed.
|
|
2994
|
+
// Use the same GPUDevice; no position downloads are required.
|
|
2995
|
+
// Call step() from your animation loop, then destroy() when the effect is removed.
|
|
2996
|
+
particles.destroy();
|
|
2997
|
+
device.destroy();
|
|
2998
|
+
```
|
|
2999
|
+
|
|
3000
|
+
These particles spawn on a sphere and aim toward the origin. A surface hit or
|
|
3001
|
+
expired lifetime respawns the particle; particles ignore one another. Five
|
|
3002
|
+
offset rays approximate each sweep. See [the sprite renderer](demo/collision-benchmark/sprites.js)
|
|
3003
|
+
for a complete example of drawing the live buffer. This helper is separate
|
|
3004
|
+
from the rigid-body solver and its collision masks.
|
|
3005
|
+
|
|
3006
|
+
Custom GPU work can share the physics submission. This is the path used by
|
|
3007
|
+
the naval buoyancy and playground force demos:
|
|
3008
|
+
|
|
3009
|
+
```js
|
|
3010
|
+
import { addGpuStepPass } from "avbd-babylon/utils";
|
|
3011
|
+
|
|
3012
|
+
// world is a WebGPU solver created from AvbdScene2D or AvbdScene3D.
|
|
3013
|
+
// Create your forcePipeline and forceBindings on the same GPUDevice.
|
|
3014
|
+
const removeForces = addGpuStepPass(world, {
|
|
3015
|
+
before(encoder, solver) {
|
|
3016
|
+
const pass = encoder.beginComputePass({ label: "Custom forces" });
|
|
3017
|
+
pass.setPipeline(forcePipeline);
|
|
3018
|
+
pass.setBindGroup(0, forceBindings);
|
|
3019
|
+
pass.dispatchWorkgroups(Math.ceil(solver.bodyCount / 64));
|
|
3020
|
+
pass.end();
|
|
3021
|
+
},
|
|
3022
|
+
});
|
|
3023
|
+
world.step(); // Forces and AVBD use one command submission.
|
|
3024
|
+
removeForces(); // Remove this registration before destroying its resources.
|
|
3025
|
+
```
|
|
3026
|
+
|
|
3027
|
+
Callbacks run synchronously and must end their own passes. Do not finish or
|
|
3028
|
+
submit the provided encoder. An `after` callback can gather results after
|
|
3029
|
+
physics. Recreate your bindings if the solver grows and replaces a buffer;
|
|
3030
|
+
wake affected bodies when activating a new force. CPU solvers reject GPU passes.
|
|
3031
|
+
|
|
3032
|
+
```js
|
|
3033
|
+
import { createSharedInput, writeSharedInput, readSharedInput } from "avbd-babylon/utils";
|
|
3034
|
+
|
|
3035
|
+
const shared = createSharedInput(); // null without COOP/COEP support
|
|
3036
|
+
if (shared) {
|
|
3037
|
+
// Pass shared.buffer to your worker through postMessage, without transferring it.
|
|
3038
|
+
writeSharedInput(shared, { clientX: 120, clientY: 80, buttons: 1 });
|
|
3039
|
+
// In the worker, use new Int32Array(message.input) and retain the last sequence.
|
|
3040
|
+
const state = readSharedInput(shared, -1);
|
|
3041
|
+
if (state) console.log(state.event, state.sequence);
|
|
3042
|
+
}
|
|
3043
|
+
```
|
|
3044
|
+
|
|
3045
|
+
The mailbox has one writer and one reader. Keep clicks, releases and key events
|
|
3046
|
+
in ordered messages; coalescing them would lose discrete actions. Send normal
|
|
3047
|
+
messages when `createSharedInput()` returns null.
|
|
3048
|
+
|
|
3049
|
+
`WorkerCanvas` wraps your transferred `OffscreenCanvas` with `clientWidth`,
|
|
3050
|
+
`clientHeight`, `resize()` and an event surface. Call `dispatch(type, event)`
|
|
3051
|
+
for mirrored pointer events. The [worker initialization](demo/shared/runtime/simulationWorker.js)
|
|
3052
|
+
and [page-side input bridge](demo/shared/runtime/simulationClient.js) use these
|
|
3053
|
+
same package helpers; their scene catalog and controls stay in the demo.
|
|
3054
|
+
|
|
3055
|
+
For timestamps, call `timer.begin()`, spread `timer.descriptor(slot)` into your
|
|
3056
|
+
pass descriptor, and call `timer.encode(encoder, slot)` after ending the pass.
|
|
3057
|
+
Submit the encoder, then call `timer.resolve(slot)`. `latestMs` stays null when
|
|
3058
|
+
timestamp queries are unavailable. The demo [particle pass](src/gpu/surfaceParticles.js)
|
|
3059
|
+
shows this sequence. Buffer recycling preserves contents: overwrite or clear
|
|
3060
|
+
reused storage before expecting zeroes, and release it only after encoding its
|
|
3061
|
+
last use. Destroy helpers when their owning world or renderer is disposed.
|
|
3062
|
+
|
|
3063
|
+
## D20 particle capacity
|
|
3064
|
+
|
|
3065
|
+
Run `npm run benchmark:particles` to search for the particle count that maintains
|
|
3066
|
+
60 FPS, refine the boundary and repeat both sides. It uses the normal visible
|
|
3067
|
+
sprites at 1440 × 900 with unchanged particle radius, speed and collision work.
|
|
3068
|
+
The [saved report](test-results/particle-capacity.html) separates collision GPU
|
|
3069
|
+
time, sprite GPU time and total frame rate. A drawing-disabled run helps show
|
|
3070
|
+
whether rendering contributes to the limit.
|
|
3071
|
+
|
|
3072
|
+
<!-- particle-capacity-results:start -->
|
|
3073
|
+
Saved reference: **NVIDIA GeForce RTX 4070 Laptop GPU**, 1440 × 900, device pixel ratio 1. 8,000,000 visible particles maintained about 60 FPS in every recorded run at that count, including three longer confirmation runs. Some runs missed the target from 8,792,000 particles upward. Near the limit, browser scheduling and GPU clocks can change the result.
|
|
3074
|
+
<!-- particle-capacity-results:end -->
|
|
3075
|
+
|
|
3076
|
+
Open the [particle demo](index.html?demo=benchmark) to play or set counts manually.
|
|
3077
|
+
For a fixed-count measurement, use its browser console:
|
|
3078
|
+
|
|
3079
|
+
```js
|
|
3080
|
+
await __COLLISION_BENCHMARK__.measure({
|
|
3081
|
+
count: 200_000,
|
|
3082
|
+
warmupSeconds: 2,
|
|
3083
|
+
seconds: 8,
|
|
3084
|
+
draw: true,
|
|
3085
|
+
});
|
|
3086
|
+
```
|
|
3087
|
+
|
|
3088
|
+
The result reports actual elapsed frame times, GPU pass timings, live particle
|
|
3089
|
+
counts and errors. A sustained-60 result allows display timing tolerance:
|
|
3090
|
+
at least 59 FPS and 95% of frames below 25 ms. Repeat runs near the boundary;
|
|
3091
|
+
resolution, GPU clocks, browser scheduling and sprite coverage affect capacity.
|
|
3092
|
+
This measures surface-only particle sweeps, rather than AVBD solving or
|
|
3093
|
+
particle-to-particle contacts.
|
|
3094
|
+
|
|
2365
3095
|
## Benchmark replication
|
|
2366
3096
|
|
|
2367
3097
|
Start this repository with `npm start`. Play scenes while the benchmark is
|
|
@@ -2440,7 +3170,18 @@ Each run starts fresh and checks contact storage, parallel-work conflicts and
|
|
|
2440
3170
|
finite poses. GPU stages measure finding contacts, preparing them, planning
|
|
2441
3171
|
parallel work and moving objects. CPU submission and startup have separate
|
|
2442
3172
|
fields; wall time also includes waiting for measurements. These are physics
|
|
2443
|
-
timings, not rendered FPS.
|
|
3173
|
+
timings, not rendered FPS. Interactive playground benchmarks automatically start
|
|
3174
|
+
their action (wind, domino tipping or marble release). GPU stage timings cover
|
|
3175
|
+
the solver; submission and completion times also include their force/feed work.
|
|
3176
|
+
The Galton board holds all 40,000 marbles visibly in its hopper until the gate
|
|
3177
|
+
opens. One GPU dispatch enables their collisions; gravity drains the hopper
|
|
3178
|
+
without teleporting marbles into the peg field. Its benchmark records how many
|
|
3179
|
+
marbles are still in the hopper and how many have passed the outlet, so early
|
|
3180
|
+
discharge samples are distinguishable from runs with all marbles below it.
|
|
3181
|
+
The hopper has a narrow central outlet and a short vertical throat above the
|
|
3182
|
+
first peg. The bin chart counts actual marble positions and overlays a fitted
|
|
3183
|
+
normal curve. Crowding and retracted pegs can change the distribution.
|
|
3184
|
+
The [integration guide](docs/README.md#reproduce-the-collision-and-solver-benchmarks)
|
|
2444
3185
|
includes downloads and profiling commands.
|
|
2445
3186
|
|
|
2446
3187
|
Saved reports were recorded on an **NVIDIA GeForce RTX 4070 Laptop GPU**. Live
|
|
@@ -2468,8 +3209,13 @@ storage buffers to the vertex shaders. Instance matrices are uploaded once.
|
|
|
2468
3209
|
Colors are copied again only when edited. Camera controls, contact overlays,
|
|
2469
3210
|
shape sizes and cloth tearing use the viewer's existing geometry and shaders.
|
|
2470
3211
|
|
|
2471
|
-
The
|
|
2472
|
-
|
|
3212
|
+
The direct WebGPU viewer also batches unique hull geometry into a single draw.
|
|
3213
|
+
In scenes with many cloth particles or hidden collision pieces, its box draw
|
|
3214
|
+
contains only visible boxes. Box-only stress scenes use a contiguous
|
|
3215
|
+
instance layout.
|
|
3216
|
+
|
|
3217
|
+
The [Babylon renderer source](demo/shared/rendering/babylonGpuRenderer.js) and
|
|
3218
|
+
[WGSL adapter](demo/shared/rendering/babylonShaders.js) show the integration. This is a custom
|
|
2473
3219
|
Babylon shader renderer; ordinary Babylon materials use the matrix-update path
|
|
2474
3220
|
shown in [Large pile with thin instances](#large-pile-with-thin-instances).
|
|
2475
3221
|
The demo isolates its access to Babylon 9's internal `_device` field in
|
|
@@ -2501,9 +3247,7 @@ try {
|
|
|
2501
3247
|
babylonWebGPU: result.babylon,
|
|
2502
3248
|
});
|
|
2503
3249
|
} finally {
|
|
2504
|
-
await __BENCHMARK_VIEW__.preview.load(
|
|
2505
|
-
document.getElementById("scene").value,
|
|
2506
|
-
);
|
|
3250
|
+
await __BENCHMARK_VIEW__.preview.load(document.getElementById("scene").value);
|
|
2507
3251
|
}
|
|
2508
3252
|
```
|
|
2509
3253
|
|
|
@@ -2515,10 +3259,10 @@ RTX 4070 Laptop GPU and links the raw measurements and every scene screenshot.
|
|
|
2515
3259
|
|
|
2516
3260
|

|
|
2517
3261
|
|
|
2518
|
-
| Babylon: 2D shapes
|
|
2519
|
-
|
|
|
2520
|
-
| 
|
|
2521
|
-
| Babylon: ragdolls on a net
|
|
3262
|
+
| Babylon: 2D shapes | Babylon: torn fabric |
|
|
3263
|
+
| ------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------- |
|
|
3264
|
+
|  |  |
|
|
3265
|
+
| Babylon: ragdolls on a net | Babylon: 100K columns |
|
|
2522
3266
|
|  |  |
|
|
2523
3267
|
|
|
2524
3268
|
See the [live comparison controls](test-results/screenshots/renderer-live-comparison.png),
|
|
@@ -2590,6 +3334,10 @@ npm run test:web # GPU scenes, benchmarks, controls and reports
|
|
|
2590
3334
|
npm run test:features # Playable 2D/3D hinge, motor, trigger, mask and cast checks
|
|
2591
3335
|
npm run benchmark:native # Bulk edits, sleeping and selected reads; three repeats
|
|
2592
3336
|
npm run benchmark:native-edits # 100K motion edits, 10K property changes and batched queries
|
|
3337
|
+
npm run benchmark:gpu-uploads # WebGPU setting preparation and upload overhead
|
|
3338
|
+
npm run benchmark:gpu-scheduling # GPU color-bucket timing and exact-order checks
|
|
3339
|
+
npm run benchmark:movement # Movement stages, paired timings and exact state checks
|
|
3340
|
+
npm run benchmark:gpu-encoding # CPU setup for GPU steps, excluding GPU/drawing time
|
|
2593
3341
|
```
|
|
2594
3342
|
|
|
2595
3343
|
Results and screenshots are generated in `test-results/`. Normal physics tests
|