avbd-babylon 0.0.0-stage → 0.2.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/LICENSE +21 -0
- package/README.md +2606 -2
- package/dist/NOTICE +145 -0
- package/dist/avbd.d.ts +3 -0
- package/dist/avbd.global.d.ts +2 -0
- package/dist/avbd.global.js +4884 -0
- package/dist/avbd.js +4884 -0
- package/dist/babylonAvbd.d.ts +285 -0
- package/dist/gpuDevice.d.ts +10 -0
- package/dist/native.d.ts +2 -0
- package/dist/native.js +1 -0
- package/dist/nativeScenes.d.ts +735 -0
- package/docs/BABYLON_API.md +356 -0
- package/docs/HPLOC.md +116 -0
- package/docs/README.md +459 -0
- package/docs/RELEASE.md +167 -0
- package/package.json +112 -4
- package/tests/README.md +170 -0
|
@@ -0,0 +1,356 @@
|
|
|
1
|
+
# Babylon.js integration
|
|
2
|
+
|
|
3
|
+
Start with the [integration README](README.md) for a complete scene, bundler setup,
|
|
4
|
+
instance rendering examples and reproducible collision/solver benchmarks.
|
|
5
|
+
|
|
6
|
+
`babylonAvbd.js` provides a small aggregate/body API with Babylon's familiar
|
|
7
|
+
mass, friction, velocity, impulse and disposal conventions. It wraps the same
|
|
8
|
+
GPU rigid-body solver used by the laboratory. It is not an implementation of
|
|
9
|
+
Babylon's complete Physics V2 plugin interface and does not replace
|
|
10
|
+
`scene.enablePhysics()` or accept Babylon `PhysicsAggregate` instances.
|
|
11
|
+
|
|
12
|
+
The standalone `avbd-babylon` ESM and `AVBD` global bundles have no Babylon
|
|
13
|
+
runtime imports. Creating a world with a scene attaches fixed-step updates and
|
|
14
|
+
disposal automatically. Aggregates accept that scene, a world, or an omitted
|
|
15
|
+
last argument when their mesh belongs to the world’s scene. Both `AvbdShapeType`
|
|
16
|
+
and the supported Babylon `PhysicsShapeType` enum values work. Pass
|
|
17
|
+
`autoAttach: false` when you manage stepping yourself.
|
|
18
|
+
|
|
19
|
+
`AvbdPhysics.create()` checks 3D floor contacts once per device and automatically
|
|
20
|
+
selects a working GPU path. This covers Android drivers that need compatible
|
|
21
|
+
contact calculations or smaller hull workgroups. Desktop devices that pass
|
|
22
|
+
retain their normal shaders and dispatches. The check adds no per-step readbacks.
|
|
23
|
+
For native 3D solvers, use `createWebGPUDevice({validate3D:true})`, or await
|
|
24
|
+
`prepareWebGPUDevice3D(device)` before using a renderer-owned device. Preparation
|
|
25
|
+
is cached and preserves device ownership. 2D does not require the 3D check.
|
|
26
|
+
|
|
27
|
+
```js
|
|
28
|
+
import { Vector3 } from "@babylonjs/core/Maths/math.vector.js";
|
|
29
|
+
import { AvbdPhysics, AvbdPhysicsAggregate, AvbdShapeType } from "avbd-babylon";
|
|
30
|
+
|
|
31
|
+
const physics = await AvbdPhysics.create({
|
|
32
|
+
scene,
|
|
33
|
+
gravity: new Vector3(0, -9.81, 0),
|
|
34
|
+
iterations: 10,
|
|
35
|
+
capacity: 2048,
|
|
36
|
+
solverMode: "auto", // default: choose the measured implementation for the load
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
new AvbdPhysicsAggregate(floor, AvbdShapeType.BOX, { mass: 0 }, scene);
|
|
40
|
+
const boxPhysics = new AvbdPhysicsAggregate(
|
|
41
|
+
box,
|
|
42
|
+
AvbdShapeType.BOX,
|
|
43
|
+
{ mass: 2, friction: 0.6 },
|
|
44
|
+
scene,
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
// Create the scene in a batch, then compile/allocate the world once.
|
|
48
|
+
physics.initialize();
|
|
49
|
+
console.log(physics.solverDecision); // selected mode and why it was chosen
|
|
50
|
+
// create({ scene }) attaches simulation and disposal automatically.
|
|
51
|
+
boxPhysics.body.setLinearVelocity(new Vector3(0, 0, 5));
|
|
52
|
+
boxPhysics.body.applyImpulse(new Vector3(3, 0, 0), box.position);
|
|
53
|
+
|
|
54
|
+
// Aggregate disposal disables its collisions; the mesh remains caller-owned.
|
|
55
|
+
// boxPhysics.dispose();
|
|
56
|
+
// physics.dispose(); // detaches the observer and releases GPU resources
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
`solverMode` is `"auto"` by default. AVBD selects the GPU contact calculation
|
|
60
|
+
from body count and active joints, and shares threads based on contact load in
|
|
61
|
+
very large scenes. All layouts retain the same physics settings. The read-only
|
|
62
|
+
`physics.solverDecision` explains the choice. `physics.setSolverMode()` accepts
|
|
63
|
+
`"standard"`, `"optimized"`, `"points"` or `"auto"` for advanced profiling and
|
|
64
|
+
compatibility. These names identify internal implementations, not quality
|
|
65
|
+
levels or paper results. Overrides preserve live GPU state; first use may
|
|
66
|
+
compile pipelines. Start with the automatic default.
|
|
67
|
+
|
|
68
|
+
Box, sphere and capsule collisions are native. Capsules use an exact rounded
|
|
69
|
+
segment along local Y; height includes both caps and X/Z diameters must match.
|
|
70
|
+
`CYLINDER` and `CONVEX_HULL` use a polygonal convex hull, with a 32-vertex limit. Mesh geometry,
|
|
71
|
+
positive scaling, centre of mass and principal inertia are captured at creation;
|
|
72
|
+
the visual transform is reconstructed from the GPU body's principal frame.
|
|
73
|
+
Meshes must have no parent. Bake parent transforms before creating aggregates.
|
|
74
|
+
Nonuniform sphere dimensions and unsupported shape types fail explicitly.
|
|
75
|
+
|
|
76
|
+
AVBD supports restitution from 0 to 1 and non-solving trigger volumes. Zero
|
|
77
|
+
restitution adds no bounce passes. The larger coefficient in a pair wins;
|
|
78
|
+
impacts below 1 m/s do not bounce. Use `body.setRestitution()` for live changes.
|
|
79
|
+
`isTrigger:true` detects dynamic-body overlaps without contact forces;
|
|
80
|
+
`body.setTrigger()` changes the mode. Events include `isTrigger`, and masks
|
|
81
|
+
apply to sensors too. Static–static pairs are not scanned. CCD,
|
|
82
|
+
arbitrary concave meshes and the full Physics V2 plugin
|
|
83
|
+
interface are not implemented. Static bodies given velocity move kinematically.
|
|
84
|
+
|
|
85
|
+
`addHinge(a,b,{axisA,axisB,anchorA,anchorB,minAngle,maxAngle,motor})` constrains
|
|
86
|
+
rotation to one axis, with optional angle stops and a torque-limited motor.
|
|
87
|
+
Axes/anchors use local mesh frames; angle stops are radians relative to creation,
|
|
88
|
+
and motor `speed` is radians/second and `maxTorque` is N·m. Change the drive with
|
|
89
|
+
`hinge.motor.setMotor()`; dispose the complete hinge with `hinge.dispose()`.
|
|
90
|
+
`addMotor()` drives an angular axis independently.
|
|
91
|
+
|
|
92
|
+
`raycast()` / `raycastAll()` query current poses. `sphereCast(origin,radius,direction,options)`
|
|
93
|
+
and `sphereCastAll()` sweep a sphere against rounded target geometry, including
|
|
94
|
+
box and hull corners. Queries accept `maxDistance`, `collidesWith`, `ignore`
|
|
95
|
+
(body handles) and `includeTriggers` (default true). Results include distance,
|
|
96
|
+
body, normal and target contact point; sphere casts also include the center at
|
|
97
|
+
impact. Batched queries return one nearest hit per input. Initial overlaps return
|
|
98
|
+
zero distance and a normal opposite travel. Queries are separate from simulation
|
|
99
|
+
CCD. Portable solvers expose the same queries using numeric ignored body indices;
|
|
100
|
+
2D uses `circleCast()` and two-component coordinates.
|
|
101
|
+
|
|
102
|
+
`addJoint(a, b, options)` connects ball-socket anchors; a null `a` makes a world
|
|
103
|
+
anchor. `addWeld()` also locks angular motion and captures the relative rotation
|
|
104
|
+
on the GPU. `addSpring()` accepts stiffness in N/m and optional rest length,
|
|
105
|
+
otherwise captured from the live anchors. Anchors use mesh-local coordinates,
|
|
106
|
+
with a world position for a null endpoint. `breakForce` enables fracture;
|
|
107
|
+
`breakOnPull` also tests linear force. `constraint.readState()` reads one record.
|
|
108
|
+
`setWorldAnchor()` moves a world anchor; `dispose()` releases a reusable joint
|
|
109
|
+
slot. Body disposal also disposes connected constraints.
|
|
110
|
+
|
|
111
|
+
Spring `breakStrain` is tensile extension relative to its rest length (0.35
|
|
112
|
+
means 35%). Spring `breakForce` measures tension in newtons. Compression does
|
|
113
|
+
not fracture springs. These settings work before and after GPU initialization.
|
|
114
|
+
|
|
115
|
+
World `sleeping:true` enables the GPU policy. Aggregate `allowSleep:false`
|
|
116
|
+
keeps that body awake; explicit `allowSleep:true` also enables the policy if
|
|
117
|
+
needed. `body.setSleepEnabled(boolean)` changes eligibility and wakes the
|
|
118
|
+
world. Impacts, support removal and edits wake bodies conservatively.
|
|
119
|
+
|
|
120
|
+
`raycast(origin, direction, options)` and batched `raycastAll(rays, options)`
|
|
121
|
+
return asynchronous nearest hits with body handles, distance, point and normal.
|
|
122
|
+
`ignore` and `collidesWith` filter rays. Direction must be nonzero. These queries
|
|
123
|
+
scan bodies on the GPU: use a few interactive casts, rather than large sensor
|
|
124
|
+
fleets. `body.setCollisionGroups(group, mask)` changes solver collision masks.
|
|
125
|
+
|
|
126
|
+
`onContact(callback)` enables GPU begin/end events and returns an unsubscribe
|
|
127
|
+
function. Events do not download poses. `maxContactPairs` and `maxContactEvents`
|
|
128
|
+
reserve storage; `readContactEvents()` reports dropped events or a full pair
|
|
129
|
+
table. End events do not retain an old position. Callbacks run asynchronously.
|
|
130
|
+
|
|
131
|
+
`sleeping: true` or `{ speedThreshold: 0.03, timeThreshold: 0.5 }` enables an
|
|
132
|
+
optional GPU policy. Supported quiet bodies sleep; impacts, moving joint
|
|
133
|
+
endpoints and explicit edits wake the world conservatively. Unsupported bodies
|
|
134
|
+
under gravity stay awake. `wakeAll()`, `body.wakeUp()` and `readSleepStats()` are
|
|
135
|
+
available. Suspended jointed objects are not guaranteed to sleep. Defaults and
|
|
136
|
+
paper-comparison benchmarks keep sleeping disabled.
|
|
137
|
+
|
|
138
|
+
`body.setLinearVelocity()`, `setAngularVelocity()`, `applyImpulse()`,
|
|
139
|
+
`applyAngularImpulse()` and `teleport(position, quaternion)` enqueue compact
|
|
140
|
+
GPU commands. Commands for the same body are combined before submission;
|
|
141
|
+
impulses use the current GPU mass, inertia and position. They never replace a
|
|
142
|
+
body with a stale CPU pose. `teleport` retains velocity unless a velocity setter
|
|
143
|
+
is also called. New aggregates can be added within reserved capacity after
|
|
144
|
+
initialization; disposed body slots are not reused. For repeated spawning,
|
|
145
|
+
reserve projectile bodies and teleport them. `applyForce()` and `applyTorque()`
|
|
146
|
+
apply forces for one fixed step, using impulses on live GPU state.
|
|
147
|
+
|
|
148
|
+
Collision search accepts `broadphase: "auto"` (default), `"grid"` or `"hploc"`
|
|
149
|
+
when creating a world. Automatic mode uses the GPU tree for large mixed-size
|
|
150
|
+
scenes whose grid cells cover many small objects; uniform brick scenes retain
|
|
151
|
+
the grid. `bvh: { rebuildInterval: 64 }` sets how often tree ordering is rebuilt.
|
|
152
|
+
All bounds are still updated every step. The [tree documentation](HPLOC.md)
|
|
153
|
+
describes extra memory, adapter limits and reproducible comparisons.
|
|
154
|
+
|
|
155
|
+
The default bridge mirrors small scenes to Babylon meshes asynchronously. It
|
|
156
|
+
allows one outstanding pose copy and does not wait in the render loop. That
|
|
157
|
+
bridge gathers only the mirrored poses (32 bytes each), rather than the whole
|
|
158
|
+
world. It still costs GPU readback and CPU mesh updates, so large scenes should use
|
|
159
|
+
the direct GPU path:
|
|
160
|
+
|
|
161
|
+
```js
|
|
162
|
+
const physics = await AvbdPhysics.create({
|
|
163
|
+
device, // optionally share a renderer's publicly provided device
|
|
164
|
+
capacity: 100_001,
|
|
165
|
+
syncMeshes: false, // no automatic pose readbacks or per-mesh updates
|
|
166
|
+
});
|
|
167
|
+
// Build aggregates, with { sync: false } if they need no explicit mesh mirror.
|
|
168
|
+
physics.initialize();
|
|
169
|
+
const bodyBuffer = physics.bodyBuffer;
|
|
170
|
+
const instanceIndex = aggregate.body.gpuIndex;
|
|
171
|
+
physics.step(); // exactly one fixed 1/60 s step; does not await the GPU
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
The body buffer uses 40 floats / 160 bytes per body: position at 0, quaternion
|
|
175
|
+
(x,y,z,w) at 4, full size at 16, mass at 19, principal moments at 20, linear
|
|
176
|
+
velocity at 32 and angular velocity at 36. Spatial sorting means creation order
|
|
177
|
+
is not GPU order; always use `body.gpuIndex`. Babylon coordinates are used
|
|
178
|
+
directly (y up by default), unlike the canonical lab's z-up scene builders.
|
|
179
|
+
|
|
180
|
+
Use a renderer that reads this buffer directly, such as the laboratory renderer,
|
|
181
|
+
adapted for the world's up axis. Do not create 100,000 automatically synchronized
|
|
182
|
+
Babylon mesh transforms. The bridge rejects more than `maxSyncedBodies` (2048
|
|
183
|
+
by default). `readBodies()` and `syncMeshes()` are explicit asynchronous tools
|
|
184
|
+
for inspection and small-scene interoperability. `readBodies([bodyB, bodyA])`
|
|
185
|
+
gathers only those 160-byte records in requested order. `body.readState()` reads
|
|
186
|
+
one record. `getRenderBinding(device)` returns the buffer and byte offsets and
|
|
187
|
+
checks device identity; it does not bind storage to thin instances automatically.
|
|
188
|
+
`readState()` retains physical `mass` and also reports `effectiveMass` (zero
|
|
189
|
+
while sleeping) and `sleeping`. Raw body buffers store effective mass at offset 19.
|
|
190
|
+
|
|
191
|
+
`AvbdScene3D` provides portable GPU builders including `addPoint`, `addFabric`,
|
|
192
|
+
`addRope` and `addRagdoll`. `AvbdScene2D` includes GPU boxes, circles, capsules,
|
|
193
|
+
convex polygons, finite segments, infinite planes, joints, springs and motors. Both are package exports. Their CPU records prepare metadata; simulation
|
|
194
|
+
uses the GPU object returned by `createSolver(device)`. See the root
|
|
195
|
+
[README](../README.md) for recipes and coordinate conventions.
|
|
196
|
+
|
|
197
|
+
The 2D solver uses numeric body indices. After `createSolver(device)`, use
|
|
198
|
+
`gpu.setLinearVelocity(index,[x,y])`, `setAngularVelocity(index,radiansPerSecond)`,
|
|
199
|
+
`applyImpulse(index,[x,y],worldPoint?)`, `applyForce(index,[x,y],worldPoint?)`
|
|
200
|
+
and `teleport(index,[x,y],angle?)`. Commands execute in order using live GPU
|
|
201
|
+
poses. `applyAngularImpulse(index,impulse)` and `applyTorque(index,torque)`
|
|
202
|
+
use scalar angular values in 2D. All angular velocities are radians/second.
|
|
203
|
+
Teleport retains velocity and preserves angle when omitted. A force is
|
|
204
|
+
applied for one fixed step. `readBodyState(index)` copies a single body and returns
|
|
205
|
+
position, angle, linear/angular velocity, mass and moment. Queries and reads
|
|
206
|
+
include pending commands. These vector arguments also accept Babylon `Vector2`.
|
|
207
|
+
|
|
208
|
+
2D `addJoint()`, `addSpring()` and `addMotor()` return disposable constraint
|
|
209
|
+
handles. Build constraints before `createSolver()`; disposal is safe before or
|
|
210
|
+
after creation. `gpu.setMotor(handle.slot,{speed,maxTorque})` changes a live drive.
|
|
211
|
+
Motor speed is A's angular velocity minus B's, so positive speed with world A
|
|
212
|
+
(`-1`) drives B clockwise. Zero torque permits free spin. `gpu.appendJoint()`
|
|
213
|
+
exposes the native live-joint operation with explicit local anchors and row
|
|
214
|
+
stiffness. Its angular rest angle uses the initial scene poses; use free angular
|
|
215
|
+
stiffness for dragging. `gpu.setWorldAnchor(slot,x,y)` only accepts world joints.
|
|
216
|
+
`disableConstraint(slot)` releases a live constraint without rebuilding the world.
|
|
217
|
+
Handle `disposed` describes manual removal; inspect GPU joint records for fracture.
|
|
218
|
+
|
|
219
|
+
Native 2D and 3D solvers also provide `gpu.body()`, a renderer-independent live
|
|
220
|
+
body handle. Use a builder body reference in 3D or the builder's numeric index in
|
|
221
|
+
2D. The handle supports `setLinearVelocity`, `setAngularVelocity`, `applyImpulse`,
|
|
222
|
+
`applyForce`, `applyAngularImpulse`, `applyTorque`, `teleport`, `setTrigger`,
|
|
223
|
+
`setRestitution`, `setCollisionGroups`, `setSleepEnabled`, `wakeUp` and `readState`.
|
|
224
|
+
3D angular values are xyz vectors and rotations are xyzw quaternions; 2D uses
|
|
225
|
+
scalar angular values. Poses and impulse lever arms come from live GPU state.
|
|
226
|
+
|
|
227
|
+
`gpu.bodyIndex(referenceOrHandle)` returns the GPU slot, including initial spatial
|
|
228
|
+
sorting. Numeric arguments are GPU slots. Handles are created only when requested;
|
|
229
|
+
bulk operations can work directly with references or indices.
|
|
230
|
+
|
|
231
|
+
`gpu.editBodies(iterable)` validates the batch before enqueueing commands. Each
|
|
232
|
+
record has a `body` and optional `position`, `rotation`, `linearVelocity`,
|
|
233
|
+
`angularVelocity`, `impulse`, `force`, `worldPoint`, `angularImpulse`, `torque`,
|
|
234
|
+
`isTrigger`, `restitution`, `group`, `collidesWith` and `allowSleep`.
|
|
235
|
+
Supply `group` when setting `collidesWith`; an omitted mask means all groups.
|
|
236
|
+
Within one record, a teleport precedes velocity setters, followed by impulses and
|
|
237
|
+
forces. Repeated records preserve per-body order. `step`, reads and queries flush
|
|
238
|
+
the queue automatically; `flushEdits()` submits without stepping or waiting.
|
|
239
|
+
One grouped dispatch owns each edited body. Teleports also invalidate previous
|
|
240
|
+
contacts touching those bodies. CPU staging and GPU upload buffers are reused.
|
|
241
|
+
Individual linear/angular setters and velocity-only `editBodies()` batches automatically
|
|
242
|
+
use compact velocity uploads; mixed commands keep their per-body order.
|
|
243
|
+
Adjacent velocity replacement setters coalesce, while impulses remain ordered.
|
|
244
|
+
Native 3D teleport cleanup is encoded in the edit submission. Babylon aggregate
|
|
245
|
+
linear/angular edits also use compact uploads; mixed aggregate edits reuse their
|
|
246
|
+
command storage. No explicit bulk call is required to batch individual setters.
|
|
247
|
+
|
|
248
|
+
`gpu.setLinearVelocities(indices, velocities)` submits packed xy/xyz velocity
|
|
249
|
+
arrays in one dispatch after earlier commands. Indices must be unique ascending
|
|
250
|
+
GPU slots. It reuses GPU buffers and accepts reusable typed arrays without
|
|
251
|
+
building command objects. Other velocity components and body properties remain
|
|
252
|
+
unchanged. Use it for frequent bulk velocity updates; use `editBodies()` for
|
|
253
|
+
mixed commands. Both methods wake sleeping bodies conservatively.
|
|
254
|
+
|
|
255
|
+
`gpu.setAngularVelocities(indices, values)` accepts one radians/sec component per
|
|
256
|
+
2D body or xyz per 3D body. `gpu.setVelocities(indices,{linear,angular})` replaces
|
|
257
|
+
either or both fields in one dispatch. These methods validate unique ascending
|
|
258
|
+
slots, reuse staging and upload storage, and preserve omitted fields.
|
|
259
|
+
|
|
260
|
+
Body-property replacements, motor/world-anchor targets and 2D limit changes collect
|
|
261
|
+
until a step, body/joint read or geometry query. Repeated edits to one property coalesce. Contiguous
|
|
262
|
+
words share direct uploads; sparse words use a GPU scatter dispatch without
|
|
263
|
+
overwriting neighboring solver state. Constraint removal/reuse and body replacement
|
|
264
|
+
flush earlier patches. `flushEdits()` submits without advancing time.
|
|
265
|
+
|
|
266
|
+
Normal steps combine queued edits, optional sleep/wake checks, optional bounce
|
|
267
|
+
handling and solving into one physics queue submission. Contact-event collection,
|
|
268
|
+
readbacks and rendering can submit additional work. Stable solver pass constants
|
|
269
|
+
are cached; changes to coloring, iterations or stabilization invalidate the cache.
|
|
270
|
+
Ray/shape-cast storage has a separate pool of at most two idle jobs, each about
|
|
271
|
+
2 MiB or less. Concurrent requests never share mapped storage.
|
|
272
|
+
|
|
273
|
+
`gpu.readSelectedBodies(indices,{posesOnly})` downloads only requested records:
|
|
274
|
+
40 floats per 3D body or 24 per 2D body; compact poses have 8 or 4 floats.
|
|
275
|
+
Small repeated reads reuse an idle staging pool. Concurrent reads receive separate
|
|
276
|
+
GPU storage and independent returned arrays. At most two buffers per role remain
|
|
277
|
+
idle, each selected-record buffer at most 8 MiB; larger reads release their storage
|
|
278
|
+
on completion. World/solver disposal releases the pool.
|
|
279
|
+
`readState()` downloads just one live body. Normal `step()` and
|
|
280
|
+
`advance(elapsedSeconds,{maxSubSteps,maxFrameTime})` never download poses.
|
|
281
|
+
Both dimensions support optional `attachToScene(scene)` / `detachFromScene()`
|
|
282
|
+
without importing Babylon. The renderer owns mesh synchronization.
|
|
283
|
+
|
|
284
|
+
Native 3D body options include `group` and `collidesWith`, and both solvers expose
|
|
285
|
+
`setFilters(indices,groups,collidesWith)`. Values must be unsigned 32-bit masks,
|
|
286
|
+
one of each per live body. Invalid batches are rejected before applying any edit.
|
|
287
|
+
|
|
288
|
+
In either dimension, `gpu.watchContacts({indices:[projectileSlot,goalSlot]})` restricts entry/exit events
|
|
289
|
+
to pairs touching those bodies, avoiding event traffic for an entire brick pile.
|
|
290
|
+
Omit `indices` on the first call to watch all bodies. Later calls without it
|
|
291
|
+
preserve the selection; a supplied array replaces it. 2D live spawns are watched
|
|
292
|
+
automatically only when watching all bodies. Drain `readContactEvents()` regularly
|
|
293
|
+
and inspect its `dropped` count when choosing event capacities.
|
|
294
|
+
|
|
295
|
+
Both the 2D builder and live solver offer `addBox(size,options)`,
|
|
296
|
+
`addCircle(radius,options)`, `addCapsule(radius,length,options)`,
|
|
297
|
+
`addHull(points,options)`, `addSegment(start,end,options)` and `addPlane(normal?,offset?,options)`.
|
|
298
|
+
Live spawning uses the reserved `bodyCapacity` and throws if full. Capsules run
|
|
299
|
+
along local X and their full length includes both caps. Segment endpoints are
|
|
300
|
+
world coordinates; optional radius makes a rounded segment. Segments default
|
|
301
|
+
to static, and zero-thickness segments cannot be dynamic. Planes are always
|
|
302
|
+
static solid half-spaces: the normalized normal points toward the allowed side,
|
|
303
|
+
`dot(normal,point) >= offset`. Their default is a floor at y=0. Finite segments
|
|
304
|
+
are two-sided. Circles, capsules and segments use analytic contacts and casts;
|
|
305
|
+
dynamic disks and capsules use exact area mass/inertia.
|
|
306
|
+
|
|
307
|
+
2D hulls accept flat xy values or arrays of `[x,y]` points. The convex boundary
|
|
308
|
+
has 3–32 vertices after duplicate, collinear and interior points are removed.
|
|
309
|
+
Geometry is centered on its area centroid; `position` sets that center of mass,
|
|
310
|
+
and `angle` rotates around it. Mass/inertia use exact polygon integrals. Contacts
|
|
311
|
+
use edge clipping, and GPU rays and disk sweeps use the actual polygon. Live
|
|
312
|
+
hull spawning grows the optional geometry storage within device limits; it does
|
|
313
|
+
not change the body buffer layout or reserve extra storage in box-only worlds.
|
|
314
|
+
|
|
315
|
+
`gpu.attachToScene(scene,{maxSubSteps?,maxFrameTime?,afterStep?})` automatically
|
|
316
|
+
steps 2D physics before Babylon rendering and destroys the solver on scene
|
|
317
|
+
disposal. Defaults: six catch-up steps and at most 0.05 seconds elapsed per
|
|
318
|
+
frame. No poses are downloaded by this loop. `detachFromScene()` removes both
|
|
319
|
+
observers without destroying the solver. Destroying a solver also detaches it;
|
|
320
|
+
the shared device remains owned by the caller. Drawing remains your choice.
|
|
321
|
+
|
|
322
|
+
`AvbdScene2D.addHinge(a,b,anchorA?,anchorB?,{minAngle?,maxAngle?,motor?})`
|
|
323
|
+
returns `{joint,limits,motor,dispose(),destroy()}`. Bounds are radians of
|
|
324
|
+
A-minus-B rotation relative to creation; they are unwrapped and may span multiple
|
|
325
|
+
turns. Missing bounds leave angular motion free. Stops use two unilateral AVBD
|
|
326
|
+
rows rather than pose clamping. `addAngularLimit(a,b,options)` adds only the
|
|
327
|
+
angular rows. Its handle exposes `setLimits(options)` before and after solver
|
|
328
|
+
creation; `gpu.setAngleLimits(slot,options)` also changes them live. Equal bounds
|
|
329
|
+
lock the relative angle. `gpu.setMotor(hinge.motor.slot,options)` changes a motor.
|
|
330
|
+
|
|
331
|
+
2D `createSolver(device,{sleeping:true})` enables GPU sleeping. Options can set
|
|
332
|
+
positive `speedThreshold` and `timeThreshold`. Body `allowSleep:true` enables the
|
|
333
|
+
policy automatically; `false` excludes that body. `gpu.setSleepEnabled(index,bool)`
|
|
334
|
+
changes eligibility. `enableSleeping(options)` enables or reconfigures the policy;
|
|
335
|
+
`disableSleeping()` wakes all bodies and releases it. `wakeUp(index)` conservatively
|
|
336
|
+
wakes the world. `readSleepStats()` returns counts, and `readBodyState(index)`
|
|
337
|
+
includes `sleeping`. Supported quiet bodies rest; impacts, lost supports, active
|
|
338
|
+
motors and edits wake before movement solving. Sleeping keeps mass/inertia and
|
|
339
|
+
collision detection intact. The GPU body flag is `velocity.w`; renderers must
|
|
340
|
+
not mistake sleeping for zero mass. No additional buffer is required by the
|
|
341
|
+
movement or topology shader; the optional policy owns its state separately.
|
|
342
|
+
|
|
343
|
+
When sharing a device, the caller owns it; world disposal leaves it alive. A
|
|
344
|
+
device created by `AvbdPhysics.create()` belongs to the world and is destroyed
|
|
345
|
+
with it. Direct source imports require the repository's WGSL text loader (or equivalent
|
|
346
|
+
bundler configuration); the built package already bundles its shaders. The mixed gallery is a complete runnable example at
|
|
347
|
+
`/?demo=showcase`.
|
|
348
|
+
|
|
349
|
+
`createWebGPUDevice({requiredLimits,preferredLimits})` treats required limits as
|
|
350
|
+
requirements: unsupported requests fail with the limit name and adapter value.
|
|
351
|
+
Preferred maximum capacities adapt downward to the adapter. Requirements override
|
|
352
|
+
preferences for the same limit. Babylon world creation uses preferred capacities
|
|
353
|
+
so small scenes can run on devices with lower storage limits. Unexpected device
|
|
354
|
+
loss stops automatic Babylon stepping, releases the world and records details in
|
|
355
|
+
`physics.deviceLost` and `physics.errors`; recreate the world with a new device to
|
|
356
|
+
resume. A disposed world does not report its normal device destruction as a failure.
|
package/docs/HPLOC.md
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
# GPU tree collision detection
|
|
2
|
+
|
|
3
|
+
`GpuHploc` in `gpuHploc.js` builds and traverses a GPU bounding-volume hierarchy.
|
|
4
|
+
Its pairs feed the existing narrowphase and AVBD solver. Shapes, friction,
|
|
5
|
+
rotation, solving rounds and contact equations stay the same; pinned upstream
|
|
6
|
+
sources remain unchanged.
|
|
7
|
+
|
|
8
|
+
This is a portable hierarchical PLOC treelet variant, based on the clustering
|
|
9
|
+
family described in [H-PLOC](https://gpuopen.com/download/HPLOC.pdf), **not** the
|
|
10
|
+
paper's single-launch wave implementation. `hplocpp.wgsl` builds the tree;
|
|
11
|
+
`hplocSort.wgsl` sorts keys.
|
|
12
|
+
|
|
13
|
+
## Algorithm
|
|
14
|
+
|
|
15
|
+
1. Compute world AABBs from live GPU positions, quaternions and collider sizes.
|
|
16
|
+
2. Reduce scene bounds on the GPU, generate 30-bit Morton keys and sort them
|
|
17
|
+
with stable radix passes. No fixed scene coordinates or lost duplicate keys.
|
|
18
|
+
3. Merge mutual nearest neighbours by surface-area cost in 16-cluster treelets.
|
|
19
|
+
Construct hierarchy levels until one root remains. Dispatch boundaries
|
|
20
|
+
publish global writes; barriers publish local merges. There are no
|
|
21
|
+
cross-workgroup spin waits or relaxed-atomic publication assumptions.
|
|
22
|
+
4. Build escape links for stackless traversal. Dynamic queries find static and
|
|
23
|
+
kinematic leaves; giant ground slabs do not traverse the whole tree.
|
|
24
|
+
5. Refit **every leaf and internal bound every step**. Rebuild ordering and
|
|
25
|
+
clustering every 64 steps by default. Refitting preserves coverage after
|
|
26
|
+
teleports; rebuilding improves traversal quality.
|
|
27
|
+
|
|
28
|
+
Pair acceptance reuses the grid's masks, disabled-joint handling, bounding
|
|
29
|
+
spheres, world AABBs, face-axis rejection and overflow accounting. Dynamic
|
|
30
|
+
pairs are emitted once; dynamic/static pairs are emitted from the dynamic
|
|
31
|
+
body. Hulls retain the canonical hull narrowphase.
|
|
32
|
+
|
|
33
|
+
## Selection and limits
|
|
34
|
+
|
|
35
|
+
`AppGpuSolver3D` and `AvbdPhysics.create` accept `broadphase: "auto"`, `"grid"`
|
|
36
|
+
or `"hploc"`, and `bvh: { rebuildInterval: 64 }`. The lab and browser benchmark
|
|
37
|
+
have the same choices. Explicit selections let you compare on your GPU.
|
|
38
|
+
|
|
39
|
+
Automatic selection uses a tree for at least 10,000 bodies when either:
|
|
40
|
+
|
|
41
|
+
- Grid cells are at least four times a typical body's bounding-sphere diameter.
|
|
42
|
+
- At least a quarter of dynamic bodies are thin plates, and there is at least
|
|
43
|
+
one active constraint per body. This covers the rigid ragdoll net.
|
|
44
|
+
|
|
45
|
+
For this policy, a thin plate has a shortest dimension no more than 40% of its
|
|
46
|
+
middle dimension, with the middle dimension at least half the longest. Rods,
|
|
47
|
+
spheres and convex hulls do not qualify as plates. Shape metadata is checked
|
|
48
|
+
when bodies or active constraint counts change; positions are not downloaded.
|
|
49
|
+
|
|
50
|
+
Body count alone does not select a tree. Adapter dispatch/storage limits are checked; automatic mode
|
|
51
|
+
keeps the grid when unsupported. Explicit unsupported requests fail clearly.
|
|
52
|
+
|
|
53
|
+
This is a conservative policy based on the measured 1K, 10K and 50K mixed-size workloads and the 24K ragdoll net,
|
|
54
|
+
not a universal crossover point. Scene layout, size distribution and GPU matter.
|
|
55
|
+
|
|
56
|
+
Extra buffers store nodes, links, sorting and clustering. JSON records primary
|
|
57
|
+
buffer bytes, rebuild/refit counts and the first GPU step. Startup GPU time
|
|
58
|
+
excludes CPU scene creation and shader compilation. Warm timing includes
|
|
59
|
+
periodic rebuilds, but excludes the first build; inspect both. Grid buffers
|
|
60
|
+
remain available for selection changes. This path adds no sleeping.
|
|
61
|
+
Reported primary bytes exclude prefix-scan scratch, small uniforms and pipelines.
|
|
62
|
+
|
|
63
|
+
## Replication and checks
|
|
64
|
+
|
|
65
|
+
Open `/tests/performance.html` and close other GPU workloads. Paired comparison
|
|
66
|
+
alternates execution order every step and preserves each scene's settings.
|
|
67
|
+
Every step checks overflow and solve-group conflicts.
|
|
68
|
+
|
|
69
|
+
```js
|
|
70
|
+
try {
|
|
71
|
+
const result = await __PERFORMANCE__.runPaired(
|
|
72
|
+
"3d-mixed-sizes-50k",
|
|
73
|
+
["grid", "hploc"],
|
|
74
|
+
60,
|
|
75
|
+
60,
|
|
76
|
+
);
|
|
77
|
+
console.table(
|
|
78
|
+
result.results.map((r) => ({
|
|
79
|
+
method: r.broadphase,
|
|
80
|
+
collisionMs: r.summary.collision.mean,
|
|
81
|
+
physicsMs: r.summary.total.mean,
|
|
82
|
+
extraBytes: r.bvh?.extraBytes ?? 0,
|
|
83
|
+
})),
|
|
84
|
+
);
|
|
85
|
+
} finally {
|
|
86
|
+
await __BENCHMARK_VIEW__.preview.load(document.querySelector("#scene").value);
|
|
87
|
+
}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Use `grid-detail` / `hploc-detail` to separate build/refit, pair search,
|
|
91
|
+
narrowphase, body solving and contact-force updates. Extra pass boundaries
|
|
92
|
+
affect timing, so compare matching detailed modes. Raw data retains rebuild
|
|
93
|
+
spikes and noisy samples. An unchanged solve-stage difference over 10% is flagged.
|
|
94
|
+
|
|
95
|
+
For the ragdoll net, compare `"showcase-ragdolls-on-cloth-24k"` with
|
|
96
|
+
`["grid", "hploc"]`, 1440 warmup steps and 60 samples. This measures the scene
|
|
97
|
+
after six simulated seconds at its full 240 Hz collision rate. Saved paired
|
|
98
|
+
results on the RTX 4070 Laptop GPU use the same timestep and five AVBD iterations;
|
|
99
|
+
see `test-results/solver-ragdoll-settled.json`. The separate
|
|
100
|
+
`node scripts/check-ragdoll-net.mjs` regression checks every quarter-second
|
|
101
|
+
through 30 simulated seconds with automatic selection. It checks whole limbs
|
|
102
|
+
below the sheet and partial capsule/triangle intersections deeper than 5 mm.
|
|
103
|
+
The net uses overlapping plates and excludes its eight immediate neighbors
|
|
104
|
+
from self-collisions; distant folds still collide.
|
|
105
|
+
|
|
106
|
+
Tests compare unique, complete pair sets against the grid for empty and partial
|
|
107
|
+
treelets, duplicate keys, rotated hulls and boxes, spheres, giant statics,
|
|
108
|
+
ignored and released joints, filters, teleports and added bodies. Physical regressions cover floor support,
|
|
109
|
+
joints, sleeping and support removal; browser checks exercise motion and drawing.
|
|
110
|
+
|
|
111
|
+
`node scripts/check-hploc-motion.mjs` checks 360 steps of the 10K/50K mixed-size
|
|
112
|
+
scenes, 110K ring, 100K columns and half-million walls with the tree forced.
|
|
113
|
+
It checks every step's capacity and scheduling counters, measures floor support
|
|
114
|
+
and rotation, and captures four screenshots per scene. `npm run test:web`
|
|
115
|
+
includes this check alongside all scene and benchmark views.
|
|
116
|
+
|