avbd-babylon 0.4.4 → 0.4.5

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.
@@ -1,815 +1,879 @@
1
- import type {
2
- AvbdSolverPipeline,
3
- AvbdSolver2D,
4
- AvbdSolver3D,
5
- } from "./solverPipeline.js";
6
- export type AvbdTuple3 = readonly [number, number, number];
7
- export type AvbdTuple2 = readonly [number, number];
8
- export interface AvbdQueryOptions {
9
- maxDistance?: number;
10
- ignore?: number[];
11
- collidesWith?: number;
12
- includeTriggers?: boolean;
13
- }
14
- export interface AvbdNativeRay<V> {
15
- origin: V;
16
- direction: V;
17
- maxDistance?: number;
18
- }
19
- export interface AvbdNativeCast<V> extends AvbdNativeRay<V> {
20
- radius: number;
21
- }
22
- export interface AvbdQueryHit<V> {
23
- index: number;
24
- distance: number;
25
- normal: V;
26
- point: V;
27
- center: V;
28
- }
29
- export type AvbdQuaternion = readonly [number, number, number, number];
30
- export interface AvbdTerrainMaterial {
31
- friction?: number;
32
- restitution?: number;
33
- group?: number;
34
- collidesWith?: number;
35
- mass?: 0;
36
- }
37
- export interface AvbdHeightfieldOptions extends AvbdTerrainMaterial {
38
- heights: ArrayLike<number>;
39
- columns: number;
40
- rows: number;
41
- spacing?: number | AvbdTuple2;
42
- origin?: AvbdTuple3;
43
- upAxis?: 0 | 1 | 2;
44
- /** Local height below every sample; defaults to the minimum height minus 2. */
45
- baseHeight?: number;
46
- /** Row-major cells; a nonzero entry leaves a hole. */
47
- holes?: ArrayLike<number>;
48
- }
49
- export interface AvbdStaticMeshOptions extends AvbdTerrainMaterial {
50
- /** World-space xyz triples. */
51
- positions: ArrayLike<number>;
52
- indices?: ArrayLike<number>;
53
- /** Backing behind triangle winding, in world units. Defaults to 0.1. */
54
- thickness?: number;
55
- }
56
- /** Serializable preprocessing output; can be cooked in a separate worker. */
57
- export interface AvbdCookedTerrain {
58
- kind: "heightfield" | "static-mesh";
59
- triangleCount: number;
60
- pieces: Array<{
61
- shape: object;
62
- position: AvbdTuple3;
63
- rotation: AvbdQuaternion;
64
- }>;
65
- }
66
- export interface AvbdNativeTerrain {
67
- readonly bodies: AvbdRigid[];
68
- readonly triangleCount: number;
69
- readonly kind: "heightfield" | "static-mesh";
70
- /** Disable collision participation; live indices remain stable. */
71
- dispose(): void;
72
- }
73
- export function cookHeightfield(
74
- options: AvbdHeightfieldOptions,
75
- ): AvbdCookedTerrain;
76
- export function cookStaticMesh(
77
- options: AvbdStaticMeshOptions,
78
- ): AvbdCookedTerrain;
79
- export interface AvbdNativeState3D {
80
- position: [number, number, number];
81
- rotation: [number, number, number, number];
82
- linearVelocity: [number, number, number];
83
- angularVelocity: [number, number, number];
84
- mass: number;
85
- effectiveMass: number;
86
- sleeping: boolean;
87
- }
88
- export interface AvbdNativeState2D {
89
- position: [number, number];
90
- angle: number;
91
- linearVelocity: [number, number];
92
- angularVelocity: number;
93
- mass: number;
94
- moment: number;
95
- sleeping: boolean;
96
- }
97
- export interface AvbdNativeBody<V, A, R, S> {
98
- /** Live GPU slot, including any initial spatial sorting. */
99
- readonly gpuIndex: number;
100
- setLinearVelocity(value: V): this;
101
- setAngularVelocity(value: A): this;
102
- applyImpulse(value: V, worldPoint?: V): this;
103
- /** Force for one simulation step. Call each step for continuous force. */
104
- applyForce(value: V, worldPoint?: V): this;
105
- applyAngularImpulse(value: A): this;
106
- applyTorque(value: A): this;
107
- /** Centre-of-mass position. Preserves velocity and omitted rotation. */
108
- teleport(position: V, rotation?: R): this;
109
- setTrigger(enabled: boolean): this;
110
- setRestitution(value: number): this;
111
- setCollisionGroups(group: number, collidesWith?: number): this;
112
- setSleepEnabled(enabled: boolean): this;
113
- wakeUp(): this;
114
- /** Explicit asynchronous download of only this body's live state. */
115
- readState(): Promise<S>;
116
- }
117
- export type AvbdNativeBody3D = AvbdNativeBody<
118
- AvbdTuple3,
119
- AvbdTuple3,
120
- AvbdQuaternion,
121
- AvbdNativeState3D
122
- >;
123
- export type AvbdNativeBody2D = AvbdNativeBody<
124
- AvbdTuple2,
125
- number,
126
- number,
127
- AvbdNativeState2D
128
- >;
129
- export interface AvbdNativeBodyEdit<B, V, A, R> {
130
- body: B;
131
- position?: V;
132
- rotation?: R;
133
- linearVelocity?: V;
134
- angularVelocity?: A;
135
- impulse?: V;
136
- force?: V;
137
- worldPoint?: V;
138
- angularImpulse?: A;
139
- torque?: A;
140
- isTrigger?: boolean;
141
- restitution?: number;
142
- /** Supply group with collidesWith; omitted collidesWith means all groups. */
143
- group?: number;
144
- collidesWith?: number;
145
- allowSleep?: boolean;
146
- }
147
- export interface AvbdAdvanceOptions {
148
- maxSubSteps?: number;
149
- maxFrameTime?: number;
150
- }
151
- export interface AvbdNativeContactOptions {
152
- maxPairs?: number;
153
- maxEvents?: number;
154
- indices?: number[];
155
- }
156
- export interface AvbdNativeContactResult {
157
- events: Array<{
158
- type: "begin" | "end";
159
- a: number;
160
- b: number;
161
- isTrigger: boolean;
162
- point: [number, number, number];
163
- normal: [number, number, number];
164
- impulse: number;
165
- step: number;
166
- }>;
167
- dropped: number;
168
- full: boolean;
169
- }
170
- export interface AvbdNativeSceneBinding {
171
- getEngine(): { getDeltaTime(): number };
172
- onBeforeRenderObservable: {
173
- add(callback: () => void): unknown;
174
- remove(observer: any): unknown;
175
- };
176
- onDisposeObservable: {
177
- addOnce(callback: () => void): unknown;
178
- remove(observer: any): unknown;
179
- };
180
- }
181
- export interface AvbdSceneOptions {
182
- gravity?: number;
183
- timeStep?: number;
184
- iterations?: number;
185
- up?: AvbdTuple3;
186
- }
187
- export interface AvbdNativeBodyOptions {
188
- group?: number;
189
- collidesWith?: number;
190
- allowSleep?: boolean;
191
- restitution?: number;
192
- isTrigger?: boolean;
193
- density?: number;
194
- mass?: number;
195
- friction?: number;
196
- position?: AvbdTuple3;
197
- rotation?: AvbdQuaternion;
198
- velocity?: AvbdTuple3;
199
- angularVelocity?: AvbdTuple3;
200
- }
201
- /** Scene-builder handle. Poses in this record are initial CPU data, not live GPU state. */
202
- export interface AvbdRigid {
203
- mass: number;
204
- size: Float64Array;
205
- moment: Float64Array;
206
- radius: number;
207
- positionLin: Float64Array;
208
- positionAng: Float64Array;
209
- velocityLin: Float64Array;
210
- velocityAng: Float64Array;
211
- }
212
- export interface AvbdGpuCounters {
213
- pairs: number;
214
- contacts: number;
215
- overflow: number;
216
- clashes: number;
217
- colors: number;
218
- manifolds?: number;
219
- }
220
- export interface AvbdGpuParameters {
221
- dt: number;
222
- gravity: number;
223
- iterations: number;
224
- alpha: number;
225
- gamma: number;
226
- }
227
- export interface AvbdGpuParameters3D extends AvbdGpuParameters {
228
- betaLin: number;
229
- betaAng: number;
230
- up?: number[];
231
- startAtRest?: boolean;
232
- massPenalty?: boolean;
233
- reuseContacts?: boolean;
234
- }
235
- export interface AvbdGpuParameters2D extends AvbdGpuParameters {
236
- beta: number;
237
- postStabilize: boolean;
238
- matchNearest: boolean;
239
- stiffnessRescale: boolean;
240
- vbd: boolean;
241
- vbdStiffness: number;
242
- }
243
- /** 3D tracks current awake groups and retains sleeping support connections. */
244
- export interface AvbdSleepOptions {
245
- /** Combined linear and radius-scaled angular motion, world units/second. Default 0.03. */
246
- speedThreshold?: number;
247
- /** Seconds continuously below the speed threshold while supported. Default 0.5. */
248
- timeThreshold?: number;
249
- /** 3D: automatic for large desktop worlds; false selects whole-world waking. */
250
- islands?: boolean;
251
- /** 3D island mode: build contacts once and sleep stationary, supported groups at startup. */
252
- startAsleep?: boolean;
253
- }
254
- export interface AvbdGpuOptions {
255
- bodyBuffer?: GPUBuffer;
256
- bodyCapacity?: number;
257
- colorRounds?: number;
258
- spatialSort?: boolean;
259
- broadphase?: "auto" | "grid" | "hploc";
260
- solverMode?: "auto" | "standard" | "optimized" | "points";
261
- /** 3D: automatically fuse closed jointless worlds of at most 32 bodies. */
262
- smallGroups?: boolean;
263
- bvh?: { rebuildInterval?: number };
264
- capacity?: {
265
- contacts?: number;
266
- pairs?: number;
267
- manifolds?: number;
268
- colors?: number;
269
- joints?: number;
270
- };
271
- /** Reserve coloring capacity when sleeping bodies wake. */
272
- minimumColors?: number;
273
- minimumColorRounds?: number;
274
- sleeping?: boolean | AvbdSleepOptions;
275
- }
276
- export interface AvbdGpuSolver {
277
- readonly bodyBuffer: GPUBuffer;
278
- readonly bodyCount: number;
279
- readonly jointCount: number;
280
- readonly params: AvbdGpuParameters;
281
- step(): void;
282
- /** Elapsed seconds; fixed timestep with bounded catch-up. Returns steps submitted, never waits for GPU or downloads poses. */
283
- advance(elapsed: number, options?: AvbdAdvanceOptions): number;
284
- /** Submit pending edits now; step and queries flush automatically. Does not wait for GPU. */
285
- flushEdits(): void;
286
- /** Immediate single GPU dispatch, after queued edits. Unique ascending GPU indices; packed xy (2D) or xyz (3D) Float32 values. Buffers are reused. */
287
- setLinearVelocities(
288
- indices: ArrayLike<number>,
289
- velocities: ArrayLike<number>,
290
- ): this;
291
- /** Packed radians/sec: one component per 2D body, xyz per 3D body. */
292
- setAngularVelocities(
293
- indices: ArrayLike<number>,
294
- velocities: ArrayLike<number>,
295
- ): this;
296
- /** Replace either or both velocity fields in one dispatch. Unique ascending GPU slots. */
297
- setVelocities(
298
- indices: ArrayLike<number>,
299
- values: { linear?: ArrayLike<number>; angular?: ArrayLike<number> },
300
- ): this;
301
- /** GPU indices in, packed records out: 40 floats/body in 3D, 24 in 2D. posesOnly: 8 floats in 3D, 4 in 2D. */
302
- readSelectedBodies(
303
- indices: ArrayLike<number>,
304
- options?: { posesOnly?: boolean },
305
- ): Promise<Float32Array>;
306
- readBodies(): Promise<Float32Array>;
307
- readJoints(): Promise<Float32Array>;
308
- readCounters(): Promise<AvbdGpuCounters>;
309
- adapt(counters: AvbdGpuCounters): void;
310
- disableConstraint(slot: number): void;
311
- destroy(): void;
312
- }
313
- export interface AvbdGpuSolver3D extends AvbdGpuSolver {
314
- /** Prefer a scene-builder body; numeric arguments are GPU slots, not reference indices. Handles are created on demand. */
315
- body(body: AvbdRigid | number): AvbdNativeBody3D;
316
- bodyIndex(body: AvbdRigid | AvbdNativeBody3D | number): number;
317
- /** Validates before enqueueing. Within a record: teleport, velocity setters, then impulses/forces. Records execute in order per body. */
318
- editBodies(
319
- edits: Iterable<
320
- AvbdNativeBodyEdit<
321
- AvbdRigid | AvbdNativeBody3D | number,
322
- AvbdTuple3,
323
- AvbdTuple3,
324
- AvbdQuaternion
325
- >
326
- >,
327
- ): this;
328
- setLinearVelocity(index: number, velocity: AvbdTuple3): this;
329
- setAngularVelocity(index: number, velocity: AvbdTuple3): this;
330
- applyImpulse(
331
- index: number,
332
- impulse: AvbdTuple3,
333
- worldPoint?: AvbdTuple3,
334
- ): this;
335
- applyForce(index: number, force: AvbdTuple3, worldPoint?: AvbdTuple3): this;
336
- applyAngularImpulse(index: number, impulse: AvbdTuple3): this;
337
- applyTorque(index: number, torque: AvbdTuple3): this;
338
- teleport(
339
- index: number,
340
- position: AvbdTuple3,
341
- rotation?: AvbdQuaternion,
342
- ): this;
343
- readBodyState(index: number): Promise<AvbdNativeState3D>;
344
- enableSleeping(options?: AvbdSleepOptions): this;
345
- disableSleeping(): this;
346
- setSleepEnabled(index: number, enabled: boolean): this;
347
- watchContacts(options?: AvbdNativeContactOptions): this;
348
- readContactEvents(): Promise<AvbdNativeContactResult>;
349
- attachToScene(
350
- scene: AvbdNativeSceneBinding,
351
- options?: AvbdAdvanceOptions & {
352
- afterStep?: (solver: AvbdGpuSolver3D) => void;
353
- },
354
- ): this;
355
- detachFromScene(): this;
356
- setFilters(
357
- indices: ArrayLike<number>,
358
- groups: ArrayLike<number>,
359
- collidesWith: ArrayLike<number>,
360
- ): void;
361
- raycast(
362
- origin: AvbdTuple3,
363
- direction: AvbdTuple3,
364
- options?: AvbdQueryOptions,
365
- ): Promise<AvbdQueryHit<AvbdTuple3> | null>;
366
- raycastAll(
367
- rays: AvbdNativeRay<AvbdTuple3>[],
368
- options?: AvbdQueryOptions,
369
- ): Promise<Array<AvbdQueryHit<AvbdTuple3> | null>>;
370
- sphereCast(
371
- origin: AvbdTuple3,
372
- radius: number,
373
- direction: AvbdTuple3,
374
- options?: AvbdQueryOptions,
375
- ): Promise<AvbdQueryHit<AvbdTuple3> | null>;
376
- sphereCastAll(
377
- casts: AvbdNativeCast<AvbdTuple3>[],
378
- options?: AvbdQueryOptions,
379
- ): Promise<Array<AvbdQueryHit<AvbdTuple3> | null>>;
380
- setMotor(slot: number, options: { speed?: number; maxTorque?: number }): void;
381
- readonly params: AvbdGpuParameters3D;
382
- gpuIndex(referenceIndex: number): number;
383
- releaseJoints(slots: ArrayLike<number>): void;
384
- setWorldAnchor(slot: number, point: ArrayLike<number>): void;
385
- captureConstraintFrames(
386
- entries: Array<{ slot: number; spring?: boolean }>,
387
- ): void;
388
- wakeAll(): void;
389
- setSpringMaterial(
390
- slot: number,
391
- options?: { breakStrain?: number; breakForce?: number },
392
- ): void;
393
- setSensor(index: number, enabled: boolean): void;
394
- setRestitution(index: number, value: number): void;
395
- readSleepStats(): Promise<{ sleeping: number; wakeRequested: boolean }>;
396
- }
397
- export interface AvbdGpuSolver2D extends AvbdGpuSolver {
398
- body(index: number): AvbdNativeBody2D;
399
- bodyIndex(body: AvbdNativeBody2D | number): number;
400
- editBodies(
401
- edits: Iterable<
402
- AvbdNativeBodyEdit<AvbdNativeBody2D | number, AvbdTuple2, number, number>
403
- >,
404
- ): this;
405
- enableSleeping(options?: {
406
- speedThreshold?: number;
407
- timeThreshold?: number;
408
- }): this;
409
- disableSleeping(): this;
410
- setSleepEnabled(index: number, enabled: boolean): this;
411
- wakeAll(): this;
412
- wakeUp(index: number): this;
413
- readSleepStats(): Promise<{ sleeping: number; wakeRequested: boolean }>;
414
- setAngleLimits(slot: number, options: AvbdAngleLimits2D): void;
415
- /** Optional packed geometry. Hull descriptor: [4, radius, vertex-record offset, count]. Vertices: [x,y,normalX,normalY]. */
416
- readonly shapeBuffer?: GPUBuffer;
417
- /** Convex point cloud, 3–32 boundary vertices. Local geometry is centered on its area centroid; position is the center of mass. */
418
- addHull(
419
- points: ArrayLike<number> | readonly AvbdTuple2[],
420
- options?: AvbdNativeBodyOptions2D,
421
- ): number;
422
- attachToScene(
423
- scene: {
424
- getEngine(): { getDeltaTime(): number };
425
- onBeforeRenderObservable: {
426
- add(callback: () => void): unknown;
427
- remove(observer: any): unknown;
428
- };
429
- onDisposeObservable: {
430
- addOnce(callback: () => void): unknown;
431
- remove(observer: any): unknown;
432
- };
433
- },
434
- options?: {
435
- maxSubSteps?: number;
436
- maxFrameTime?: number;
437
- afterStep?: (solver: AvbdGpuSolver2D) => void;
438
- },
439
- ): this;
440
- detachFromScene(): this;
441
- addBox(size: AvbdTuple2, options?: AvbdNativeBodyOptions2D): number;
442
- addCircle(radius: number, options?: AvbdNativeBodyOptions2D): number;
443
- /** Local X axis; length includes both round caps. */
444
- addCapsule(
445
- radius: number,
446
- length: number,
447
- options?: AvbdNativeBodyOptions2D,
448
- ): number;
449
- addSegment(
450
- start: AvbdTuple2,
451
- end: AvbdTuple2,
452
- options?: AvbdNativeSegmentOptions2D,
453
- ): number;
454
- addPlane(
455
- normal?: AvbdTuple2,
456
- offset?: number,
457
- options?: AvbdNativePlaneOptions2D,
458
- ): number;
459
- setLinearVelocity(
460
- index: number,
461
- velocity: AvbdTuple2 | { x: number; y: number },
462
- ): this;
463
- setAngularVelocity(index: number, velocity: number): this;
464
- applyImpulse(
465
- index: number,
466
- impulse: AvbdTuple2 | { x: number; y: number },
467
- point?: AvbdTuple2 | { x: number; y: number },
468
- ): this;
469
- /** Applies force for one simulation step. Call every step for a continuous force. */
470
- applyForce(
471
- index: number,
472
- force: AvbdTuple2 | { x: number; y: number },
473
- point?: AvbdTuple2 | { x: number; y: number },
474
- ): this;
475
- applyAngularImpulse(index: number, impulse: number): this;
476
- applyTorque(index: number, torque: number): this;
477
- /** Preserves velocity; omitted angle preserves current rotation. */
478
- teleport(
479
- index: number,
480
- position: AvbdTuple2 | { x: number; y: number },
481
- angle?: number,
482
- ): this;
483
- readBodyState(index: number): Promise<{
484
- position: [number, number];
485
- angle: number;
486
- linearVelocity: [number, number];
487
- angularVelocity: number;
488
- mass: number;
489
- moment: number;
490
- sleeping: boolean;
491
- }>;
492
- raycast(
493
- origin: AvbdTuple2,
494
- direction: AvbdTuple2,
495
- options?: AvbdQueryOptions,
496
- ): Promise<AvbdQueryHit<AvbdTuple2> | null>;
497
- raycastAll(
498
- rays: AvbdNativeRay<AvbdTuple2>[],
499
- options?: AvbdQueryOptions,
500
- ): Promise<Array<AvbdQueryHit<AvbdTuple2> | null>>;
501
- circleCast(
502
- origin: AvbdTuple2,
503
- radius: number,
504
- direction: AvbdTuple2,
505
- options?: AvbdQueryOptions,
506
- ): Promise<AvbdQueryHit<AvbdTuple2> | null>;
507
- circleCastAll(
508
- casts: AvbdNativeCast<AvbdTuple2>[],
509
- options?: AvbdQueryOptions,
510
- ): Promise<Array<AvbdQueryHit<AvbdTuple2> | null>>;
511
- setRestitution(index: number, value: number): void;
512
- setSensor(index: number, enabled: boolean): void;
513
- setFilters(
514
- indices: ArrayLike<number>,
515
- groups: ArrayLike<number>,
516
- collidesWith: ArrayLike<number>,
517
- ): void;
518
- /** Initially omitting indices watches every body. A supplied list selects pairs touching those bodies; later omissions preserve that selection. */
519
- watchContacts(options?: {
520
- maxPairs?: number;
521
- maxEvents?: number;
522
- indices?: number[];
523
- }): this;
524
- readContactEvents(): Promise<{
525
- events: Array<{
526
- type: "begin" | "end";
527
- a: number;
528
- b: number;
529
- isTrigger: boolean;
530
- point: [number, number, number];
531
- normal: [number, number, number];
532
- impulse: number;
533
- step: number;
534
- }>;
535
- dropped: number;
536
- full: boolean;
537
- }>;
538
- readonly params: AvbdGpuParameters2D;
539
- setMotor(slot: number, options: { speed?: number; maxTorque?: number }): void;
540
- appendJoint(
541
- a: number,
542
- b: number,
543
- anchorA: AvbdTuple2,
544
- anchorB: AvbdTuple2,
545
- stiffness: AvbdTuple3,
546
- breakForce?: number,
547
- ): number;
548
- setWorldAnchor(slot: number, x: number, y: number): void;
549
- }
550
- export interface AvbdNativeConstraint2D extends AvbdNativeConstraint {
551
- /** Manual enable/dispose state; fracture state comes from readJoints(). */
552
- readonly alive: boolean;
553
- }
554
- export interface AvbdAngleLimits2D {
555
- minAngle?: number;
556
- maxAngle?: number;
557
- }
558
- export interface AvbdAngularLimit2D extends AvbdNativeConstraint2D {
559
- setLimits(options: AvbdAngleLimits2D): this;
560
- }
561
- export interface AvbdHinge2D {
562
- readonly joint: AvbdNativeConstraint2D;
563
- readonly limits: AvbdAngularLimit2D | null;
564
- readonly motor: AvbdNativeConstraint2D | null;
565
- readonly disposed: boolean;
566
- dispose(): void;
567
- destroy(): void;
568
- }
569
- export interface AvbdNativeConstraint {
570
- readonly slot: number;
571
- readonly disposed: boolean;
572
- dispose(): void;
573
- destroy(): void;
574
- }
575
- export class AvbdScene3D {
576
- addHeightfield(options: AvbdHeightfieldOptions): AvbdNativeTerrain;
577
- addStaticMesh(options: AvbdStaticMeshOptions): AvbdNativeTerrain;
578
- addTerrain(
579
- cooked: AvbdCookedTerrain,
580
- options?: AvbdTerrainMaterial,
581
- ): AvbdNativeTerrain;
582
- addMotor(
583
- a: AvbdRigid | null,
584
- b: AvbdRigid,
585
- options?: AvbdNativeAngularOptions,
586
- ): AvbdNativeConstraint;
587
- addHinge(
588
- a: AvbdRigid | null,
589
- b: AvbdRigid,
590
- options?: AvbdNativeAngularOptions & {
591
- anchorA?: AvbdTuple3;
592
- anchorB?: AvbdTuple3;
593
- span?: number;
594
- motor?: { speed?: number; maxTorque?: number };
595
- },
596
- ): {
597
- joints: AvbdNativeConstraint[];
598
- limit?: AvbdNativeConstraint;
599
- motor?: AvbdNativeConstraint;
600
- dispose(): void;
601
- };
602
- constructor(options?: AvbdSceneOptions);
603
- readonly bodies: AvbdRigid[];
604
- readonly constraints: AvbdNativeConstraint[];
605
- addBox(size: AvbdTuple3, options?: AvbdNativeBodyOptions): AvbdRigid;
606
- addSphere(radius: number, options?: AvbdNativeBodyOptions): AvbdRigid;
607
- /** Exact rounded capsule along local Y; height includes both end caps. */
608
- addCapsule(
609
- radius: number,
610
- height: number,
611
- options?: AvbdNativeBodyOptions,
612
- ): AvbdRigid;
613
- /** Builds a centred convex collider aligned to its principal axes.
614
- * Position/rotation place that principal frame; use aggregates to retain a mesh frame. */
615
- addHull(
616
- points: ArrayLike<number>,
617
- options?: AvbdNativeBodyOptions,
618
- ): AvbdRigid;
619
- addPoint(
620
- position: AvbdTuple3,
621
- options?: { mass?: number; radius?: number; friction?: number },
622
- ): AvbdRigid;
623
- /** Helpers below build y-up geometry. Fabric lies in the x-z plane. */
624
- addFabric(options?: {
625
- columns?: number;
626
- rows?: number;
627
- spacing?: number;
628
- origin?: AvbdTuple3;
629
- mass?: number;
630
- stiffness?: number;
631
- pinEdges?: boolean;
632
- /** Four corner anchors. When true, pinEdges defaults to false. */
633
- pinCorners?: boolean;
634
- /** N/m at pinned attachments; defaults to stiffness. Shear links retain half stiffness. */
635
- anchorStiffness?: number;
636
- /** Material at a spring's undeformed midpoint, normalized across the sheet.
637
- * stiffnessScale multiplies stretch/shear/bending stiffness after anchorStiffness.
638
- * Unspecified fields keep the fabric defaults. */
639
- materialAt?: (
640
- u: number,
641
- v: number,
642
- ) => {
643
- stiffnessScale?: number;
644
- breakStrain?: number;
645
- breakForce?: number;
646
- };
647
- breakStrain?: number;
648
- breakForce?: number;
649
- bendingStiffness?: number;
650
- }): {
651
- points: AvbdRigid[];
652
- grid: AvbdRigid[][];
653
- indices: Uint32Array;
654
- connections: AvbdNativeConstraint[];
655
- triangleConnections: Uint32Array;
656
- };
657
- addRope(options?: {
658
- segments?: number;
659
- length?: number;
660
- origin?: AvbdTuple3;
661
- width?: number;
662
- endMass?: number;
663
- }): { anchor: AvbdRigid; links: AvbdRigid[] };
664
- addRagdoll(options?: { origin?: AvbdTuple3; mass?: number }): {
665
- torso: AvbdRigid;
666
- head: AvbdRigid;
667
- bodies: AvbdRigid[];
668
- };
669
- addJoint(
670
- a: AvbdRigid | null,
671
- b: AvbdRigid,
672
- anchorA?: AvbdTuple3,
673
- anchorB?: AvbdTuple3,
674
- options?: {
675
- linearStiffness?: number;
676
- angularStiffness?: number;
677
- breakForce?: number;
678
- breakOnPull?: boolean;
679
- },
680
- ): AvbdNativeConstraint;
681
- addSpring(
682
- a: AvbdRigid,
683
- b: AvbdRigid,
684
- anchorA?: AvbdTuple3,
685
- anchorB?: AvbdTuple3,
686
- options?: {
687
- stiffness?: number;
688
- rest?: number;
689
- breakStrain?: number;
690
- breakForce?: number;
691
- },
692
- ): AvbdNativeConstraint;
693
- createSolver(device: GPUDevice, options?: AvbdGpuOptions): AvbdGpuSolver3D;
694
- createSolver(
695
- pipeline: AvbdSolverPipeline,
696
- options?: AvbdGpuOptions,
697
- ): AvbdSolver3D;
698
- }
699
- export interface AvbdNativeAngularOptions {
700
- axisA?: AvbdTuple3;
701
- axisB?: AvbdTuple3;
702
- speed?: number;
703
- maxTorque?: number;
704
- minAngle?: number;
705
- maxAngle?: number;
706
- }
707
- export interface AvbdNativeBodyOptions2D {
708
- allowSleep?: boolean;
709
- density?: number;
710
- friction?: number;
711
- position?: AvbdTuple2;
712
- angle?: number;
713
- /** [linear X, linear Y, angular radians/second]. */
714
- velocity?: AvbdTuple3;
715
- restitution?: number;
716
- isTrigger?: boolean;
717
- group?: number;
718
- collidesWith?: number;
719
- }
720
- export interface AvbdNativeSegmentOptions2D extends Omit<
721
- AvbdNativeBodyOptions2D,
722
- "position" | "angle"
723
- > {
724
- /** Zero by default. Zero-thickness segments must be static. */
725
- radius?: number;
726
- }
727
- export type AvbdNativePlaneOptions2D = Omit<
728
- AvbdNativeBodyOptions2D,
729
- "position" | "angle" | "density"
730
- > & { density?: 0 };
731
- export class AvbdScene2D {
732
- constructor(
733
- options?: Omit<AvbdSceneOptions, "up"> & {
734
- alpha?: number;
735
- postStabilize?: boolean;
736
- matchNearest?: boolean;
737
- },
738
- );
739
- readonly bodyCount: number;
740
- /** Convex point cloud, 3–32 boundary vertices. Position is the area centroid / center of mass. */
741
- addHull(
742
- points: ArrayLike<number> | readonly AvbdTuple2[],
743
- options?: AvbdNativeBodyOptions2D,
744
- ): number;
745
- addCircle(radius: number, options?: AvbdNativeBodyOptions2D): number;
746
- /** Local X axis; length includes both round caps. */
747
- addCapsule(
748
- radius: number,
749
- length: number,
750
- options?: AvbdNativeBodyOptions2D,
751
- ): number;
752
- /** Finite two-sided line, or a rounded segment if radius > 0. */
753
- addSegment(
754
- start: AvbdTuple2,
755
- end: AvbdTuple2,
756
- options?: AvbdNativeSegmentOptions2D,
757
- ): number;
758
- /** Normal is normalized. Allowed side: dot(normal, point) >= offset. */
759
- addPlane(
760
- normal?: AvbdTuple2,
761
- offset?: number,
762
- options?: AvbdNativePlaneOptions2D,
763
- ): number;
764
- addBox(
765
- size: readonly [number, number],
766
- options?: AvbdNativeBodyOptions2D,
767
- ): number;
768
- addJoint(
769
- a: number,
770
- b: number,
771
- anchorA?: readonly [number, number],
772
- anchorB?: readonly [number, number],
773
- options?: { stiffness?: AvbdTuple3; breakForce?: number },
774
- ): AvbdNativeConstraint2D;
775
- addSpring(
776
- a: number,
777
- b: number,
778
- anchorA?: readonly [number, number],
779
- anchorB?: readonly [number, number],
780
- options?: { stiffness?: number; rest?: number },
781
- ): AvbdNativeConstraint2D;
782
- addMotor(
783
- a: number,
784
- b: number,
785
- options?: { speed?: number; maxTorque?: number },
786
- ): AvbdNativeConstraint2D;
787
- addAngularLimit(
788
- a: number,
789
- b: number,
790
- options?: AvbdAngleLimits2D,
791
- ): AvbdAngularLimit2D;
792
- addHinge(
793
- a: number,
794
- b: number,
795
- anchorA?: AvbdTuple2,
796
- anchorB?: AvbdTuple2,
797
- options?: AvbdAngleLimits2D & {
798
- motor?: { speed?: number; maxTorque?: number };
799
- },
800
- ): AvbdHinge2D;
801
- createSolver(
802
- device: GPUDevice,
803
- options?: {
804
- bodyBuffer?: GPUBuffer;
805
- bodyCapacity?: number;
806
- colorRounds?: number;
807
- minimumColors?: number;
808
- sleeping?: boolean | { speedThreshold?: number; timeThreshold?: number };
809
- },
810
- ): AvbdGpuSolver2D;
811
- createSolver(
812
- pipeline: AvbdSolverPipeline,
813
- options?: AvbdGpuOptions,
814
- ): AvbdSolver2D;
815
- }
1
+ import type {
2
+ AvbdSolverPipeline,
3
+ AvbdSolver2D,
4
+ AvbdSolver3D,
5
+ } from "./solverPipeline.js";
6
+ export type AvbdTuple3 = readonly [number, number, number];
7
+ export type AvbdTuple2 = readonly [number, number];
8
+ /** Queries return the nearest hit per ray/cast. Ignore lists contain live body slots. */
9
+ export interface AvbdQueryOptions {
10
+ maxDistance?: number;
11
+ ignore?: number[];
12
+ collidesWith?: number;
13
+ includeTriggers?: boolean;
14
+ }
15
+ export interface AvbdNativeRay<V> {
16
+ origin: V;
17
+ direction: V;
18
+ maxDistance?: number;
19
+ }
20
+ export interface AvbdNativeCast<V> extends AvbdNativeRay<V> {
21
+ radius: number;
22
+ }
23
+ export interface AvbdQueryHit<V> {
24
+ index: number;
25
+ distance: number;
26
+ normal: V;
27
+ point: V;
28
+ center: V;
29
+ }
30
+ export type AvbdQuaternion = readonly [number, number, number, number];
31
+ export interface AvbdTerrainMaterial {
32
+ friction?: number;
33
+ restitution?: number;
34
+ group?: number;
35
+ collidesWith?: number;
36
+ mass?: 0;
37
+ }
38
+ export interface AvbdHeightfieldOptions extends AvbdTerrainMaterial {
39
+ heights: ArrayLike<number>;
40
+ columns: number;
41
+ rows: number;
42
+ spacing?: number | AvbdTuple2;
43
+ origin?: AvbdTuple3;
44
+ upAxis?: 0 | 1 | 2;
45
+ /** Local height below every sample; defaults to the minimum height minus 2. */
46
+ baseHeight?: number;
47
+ /** Row-major cells; a nonzero entry leaves a hole. */
48
+ holes?: ArrayLike<number>;
49
+ }
50
+ export interface AvbdStaticMeshOptions extends AvbdTerrainMaterial {
51
+ /** World-space xyz triples. */
52
+ positions: ArrayLike<number>;
53
+ indices?: ArrayLike<number>;
54
+ /** Backing behind triangle winding, in world units. Defaults to 0.1. */
55
+ thickness?: number;
56
+ }
57
+ /** Serializable preprocessing output; can be cooked in a separate worker. */
58
+ export interface AvbdCookedTerrain {
59
+ kind: "heightfield" | "static-mesh";
60
+ triangleCount: number;
61
+ pieces: Array<{
62
+ shape: object;
63
+ position: AvbdTuple3;
64
+ rotation: AvbdQuaternion;
65
+ }>;
66
+ }
67
+ export interface AvbdNativeTerrain {
68
+ readonly bodies: AvbdRigid[];
69
+ readonly triangleCount: number;
70
+ readonly kind: "heightfield" | "static-mesh";
71
+ /** Disable collision participation; live indices remain stable. */
72
+ dispose(): void;
73
+ }
74
+ export function cookHeightfield(
75
+ options: AvbdHeightfieldOptions,
76
+ ): AvbdCookedTerrain;
77
+ export function cookStaticMesh(
78
+ options: AvbdStaticMeshOptions,
79
+ ): AvbdCookedTerrain;
80
+ export interface AvbdNativeState3D {
81
+ position: [number, number, number];
82
+ rotation: [number, number, number, number];
83
+ linearVelocity: [number, number, number];
84
+ angularVelocity: [number, number, number];
85
+ mass: number;
86
+ effectiveMass: number;
87
+ sleeping: boolean;
88
+ }
89
+ export interface AvbdNativeState2D {
90
+ position: [number, number];
91
+ angle: number;
92
+ linearVelocity: [number, number];
93
+ angularVelocity: number;
94
+ mass: number;
95
+ moment: number;
96
+ sleeping: boolean;
97
+ }
98
+ /**
99
+ * Lightweight live-body commands. Setters and impulses queue until step(),
100
+ * flushEdits() or a state query; creating a handle does not download a pose.
101
+ */
102
+ export interface AvbdNativeBody<V, A, R, S> {
103
+ /** Live GPU slot, including any initial spatial sorting. */
104
+ readonly gpuIndex: number;
105
+ setLinearVelocity(value: V): this;
106
+ setAngularVelocity(value: A): this;
107
+ /** Impulse in world coordinates; an optional world point also changes angular velocity. */
108
+ applyImpulse(value: V, worldPoint?: V): this;
109
+ /** Force for one simulation step. Call each step for continuous force. */
110
+ applyForce(value: V, worldPoint?: V): this;
111
+ applyAngularImpulse(value: A): this;
112
+ /** Torque for one fixed step; call again each step for a sustained torque. */
113
+ applyTorque(value: A): this;
114
+ /** Centre-of-mass position. Preserves velocity and omitted rotation. */
115
+ teleport(position: V, rotation?: R): this;
116
+ /** Detect overlaps without contact response; enable watchContacts() to read events. */
117
+ setTrigger(enabled: boolean): this;
118
+ /** Bounce coefficient in [0, 1]; zero still resolves penetration and friction. */
119
+ setRestitution(value: number): this;
120
+ /** Unsigned bitmasks. Both bodies must accept the other's group to interact. */
121
+ setCollisionGroups(group: number, collidesWith?: number): this;
122
+ /** Per-body eligibility; enabling also enables the world sleep policy when needed. */
123
+ setSleepEnabled(enabled: boolean): this;
124
+ wakeUp(): this;
125
+ /** Explicit asynchronous download of only this body's live state. */
126
+ readState(): Promise<S>;
127
+ }
128
+ export type AvbdNativeBody3D = AvbdNativeBody<
129
+ AvbdTuple3,
130
+ AvbdTuple3,
131
+ AvbdQuaternion,
132
+ AvbdNativeState3D
133
+ >;
134
+ export type AvbdNativeBody2D = AvbdNativeBody<
135
+ AvbdTuple2,
136
+ number,
137
+ number,
138
+ AvbdNativeState2D
139
+ >;
140
+ /**
141
+ * One bulk edit record. Rotation requires position; worldPoint applies to force
142
+ * and impulse. Queue the iterable once instead of issuing many individual calls.
143
+ */
144
+ export interface AvbdNativeBodyEdit<B, V, A, R> {
145
+ body: B;
146
+ position?: V;
147
+ rotation?: R;
148
+ linearVelocity?: V;
149
+ angularVelocity?: A;
150
+ impulse?: V;
151
+ force?: V;
152
+ worldPoint?: V;
153
+ angularImpulse?: A;
154
+ torque?: A;
155
+ isTrigger?: boolean;
156
+ restitution?: number;
157
+ /** Supply group with collidesWith; omitted collidesWith means all groups. */
158
+ group?: number;
159
+ collidesWith?: number;
160
+ allowSleep?: boolean;
161
+ }
162
+ export interface AvbdAdvanceOptions {
163
+ /** Maximum fixed steps per advance() call; default 6. Bounds overload catch-up. */
164
+ maxSubSteps?: number;
165
+ /** Maximum elapsed seconds accepted per call; default 0.05. */
166
+ maxFrameTime?: number;
167
+ }
168
+ export interface AvbdNativeContactOptions {
169
+ maxPairs?: number;
170
+ maxEvents?: number;
171
+ /** Watch only these live slots; omitted means all current bodies. */
172
+ indices?: number[];
173
+ }
174
+ export interface AvbdNativeContactResult {
175
+ events: Array<{
176
+ type: "begin" | "end";
177
+ a: number;
178
+ b: number;
179
+ isTrigger: boolean;
180
+ point: [number, number, number];
181
+ normal: [number, number, number];
182
+ impulse: number;
183
+ step: number;
184
+ }>;
185
+ dropped: number;
186
+ full: boolean;
187
+ }
188
+ export interface AvbdNativeSceneBinding {
189
+ getEngine(): { getDeltaTime(): number };
190
+ onBeforeRenderObservable: {
191
+ add(callback: () => void): unknown;
192
+ remove(observer: any): unknown;
193
+ };
194
+ onDisposeObservable: {
195
+ addOnce(callback: () => void): unknown;
196
+ remove(observer: any): unknown;
197
+ };
198
+ }
199
+ export interface AvbdSceneOptions {
200
+ /** Signed acceleration along up; negative pulls downward. */
201
+ gravity?: number;
202
+ /** Fixed-step duration in seconds. Frame/render rate is separate. */
203
+ timeStep?: number;
204
+ iterations?: number;
205
+ /** 3D unit up vector; defaults to [0, 1, 0]. */
206
+ up?: AvbdTuple3;
207
+ }
208
+ export interface AvbdNativeBodyOptions {
209
+ group?: number;
210
+ collidesWith?: number;
211
+ allowSleep?: boolean;
212
+ restitution?: number;
213
+ isTrigger?: boolean;
214
+ density?: number;
215
+ mass?: number;
216
+ friction?: number;
217
+ position?: AvbdTuple3;
218
+ rotation?: AvbdQuaternion;
219
+ velocity?: AvbdTuple3;
220
+ angularVelocity?: AvbdTuple3;
221
+ }
222
+ /** Scene-builder handle. Poses in this record are initial CPU data, not live GPU state. */
223
+ export interface AvbdRigid {
224
+ mass: number;
225
+ size: Float64Array;
226
+ moment: Float64Array;
227
+ radius: number;
228
+ positionLin: Float64Array;
229
+ positionAng: Float64Array;
230
+ velocityLin: Float64Array;
231
+ velocityAng: Float64Array;
232
+ }
233
+ export interface AvbdGpuCounters {
234
+ pairs: number;
235
+ contacts: number;
236
+ overflow: number;
237
+ clashes: number;
238
+ colors: number;
239
+ manifolds?: number;
240
+ }
241
+ export interface AvbdGpuParameters {
242
+ dt: number;
243
+ gravity: number;
244
+ iterations: number;
245
+ alpha: number;
246
+ gamma: number;
247
+ }
248
+ export interface AvbdGpuParameters3D extends AvbdGpuParameters {
249
+ betaLin: number;
250
+ betaAng: number;
251
+ up?: number[];
252
+ startAtRest?: boolean;
253
+ massPenalty?: boolean;
254
+ reuseContacts?: boolean;
255
+ }
256
+ export interface AvbdGpuParameters2D extends AvbdGpuParameters {
257
+ beta: number;
258
+ postStabilize: boolean;
259
+ matchNearest: boolean;
260
+ stiffnessRescale: boolean;
261
+ vbd: boolean;
262
+ vbdStiffness: number;
263
+ }
264
+ /** 3D tracks current awake groups and retains sleeping support connections. */
265
+ export interface AvbdSleepOptions {
266
+ /**
267
+ * Combined linear and radius-scaled angular motion, world units/second. Default 0.03.
268
+ *
269
+ * Dense 3D contact groups need every awake member quiet and supported before resting.
270
+ */
271
+ speedThreshold?: number;
272
+ /** Seconds continuously below the speed threshold while supported. Default 0.5. */
273
+ timeThreshold?: number;
274
+ /** 3D: automatic for large desktop worlds; false selects whole-world waking. */
275
+ islands?: boolean;
276
+ /** 3D island mode: build contacts once and sleep stationary, supported groups at startup. */
277
+ startAsleep?: boolean;
278
+ }
279
+ export interface AvbdGpuOptions {
280
+ /**
281
+ * Optional caller-owned storage. Share its GPUDevice with the renderer and
282
+ * keep it alive until the world and renderer have finished using it.
283
+ */
284
+ bodyBuffer?: GPUBuffer;
285
+ bodyCapacity?: number;
286
+ colorRounds?: number;
287
+ spatialSort?: boolean;
288
+ /** auto selects a spatial search from scene characteristics; grid/hploc force a choice. */
289
+ broadphase?: "auto" | "grid" | "hploc";
290
+ solverMode?: "auto" | "standard" | "optimized" | "points";
291
+ /** 3D: automatically fuse closed jointless worlds of at most 32 bodies. */
292
+ smallGroups?: boolean;
293
+ bvh?: { rebuildInterval?: number };
294
+ capacity?: {
295
+ contacts?: number;
296
+ pairs?: number;
297
+ manifolds?: number;
298
+ colors?: number;
299
+ joints?: number;
300
+ };
301
+ /** Reserve coloring capacity when sleeping bodies wake. */
302
+ minimumColors?: number;
303
+ minimumColorRounds?: number;
304
+ sleeping?: boolean | AvbdSleepOptions;
305
+ }
306
+ export interface AvbdGpuSolver {
307
+ readonly bodyBuffer: GPUBuffer;
308
+ readonly bodyCount: number;
309
+ readonly jointCount: number;
310
+ readonly params: AvbdGpuParameters;
311
+ /** Submit one fixed step without waiting for GPU completion or downloading poses. */
312
+ step(): void;
313
+ /** Elapsed seconds; fixed timestep with bounded catch-up. Returns steps submitted, never waits for GPU or downloads poses. */
314
+ advance(elapsed: number, options?: AvbdAdvanceOptions): number;
315
+ /** Submit pending edits now; step and queries flush automatically. Does not wait for GPU. */
316
+ flushEdits(): void;
317
+ /** Immediate single GPU dispatch, after queued edits. Unique ascending GPU indices; packed xy (2D) or xyz (3D) Float32 values. Buffers are reused. */
318
+ setLinearVelocities(
319
+ indices: ArrayLike<number>,
320
+ velocities: ArrayLike<number>,
321
+ ): this;
322
+ /** Packed radians/sec: one component per 2D body, xyz per 3D body. */
323
+ setAngularVelocities(
324
+ indices: ArrayLike<number>,
325
+ velocities: ArrayLike<number>,
326
+ ): this;
327
+ /** Replace either or both velocity fields in one dispatch. Unique ascending GPU slots. */
328
+ setVelocities(
329
+ indices: ArrayLike<number>,
330
+ values: { linear?: ArrayLike<number>; angular?: ArrayLike<number> },
331
+ ): this;
332
+ /** GPU indices in, packed records out: 40 floats/body in 3D, 24 in 2D. posesOnly: 8 floats in 3D, 4 in 2D. */
333
+ readSelectedBodies(
334
+ indices: ArrayLike<number>,
335
+ options?: { posesOnly?: boolean },
336
+ ): Promise<Float32Array>;
337
+ /** Explicit full-world download in GPU slot order; prefer selected reads for gameplay. */
338
+ readBodies(): Promise<Float32Array>;
339
+ readJoints(): Promise<Float32Array>;
340
+ readCounters(): Promise<AvbdGpuCounters>;
341
+ adapt(counters: AvbdGpuCounters): void;
342
+ disableConstraint(slot: number): void;
343
+ destroy(): void;
344
+ }
345
+ export interface AvbdGpuSolver3D extends AvbdGpuSolver {
346
+ /** Prefer a scene-builder body; numeric arguments are GPU slots, not reference indices. Handles are created on demand. */
347
+ body(body: AvbdRigid | number): AvbdNativeBody3D;
348
+ bodyIndex(body: AvbdRigid | AvbdNativeBody3D | number): number;
349
+ /** Validates before enqueueing. Within a record: teleport, velocity setters, then impulses/forces. Records execute in order per body. */
350
+ editBodies(
351
+ edits: Iterable<
352
+ AvbdNativeBodyEdit<
353
+ AvbdRigid | AvbdNativeBody3D | number,
354
+ AvbdTuple3,
355
+ AvbdTuple3,
356
+ AvbdQuaternion
357
+ >
358
+ >,
359
+ ): this;
360
+ setLinearVelocity(index: number, velocity: AvbdTuple3): this;
361
+ setAngularVelocity(index: number, velocity: AvbdTuple3): this;
362
+ applyImpulse(
363
+ index: number,
364
+ impulse: AvbdTuple3,
365
+ worldPoint?: AvbdTuple3,
366
+ ): this;
367
+ applyForce(index: number, force: AvbdTuple3, worldPoint?: AvbdTuple3): this;
368
+ applyAngularImpulse(index: number, impulse: AvbdTuple3): this;
369
+ applyTorque(index: number, torque: AvbdTuple3): this;
370
+ teleport(
371
+ index: number,
372
+ position: AvbdTuple3,
373
+ rotation?: AvbdQuaternion,
374
+ ): this;
375
+ readBodyState(index: number): Promise<AvbdNativeState3D>;
376
+ enableSleeping(options?: AvbdSleepOptions): this;
377
+ disableSleeping(): this;
378
+ setSleepEnabled(index: number, enabled: boolean): this;
379
+ watchContacts(options?: AvbdNativeContactOptions): this;
380
+ readContactEvents(): Promise<AvbdNativeContactResult>;
381
+ attachToScene(
382
+ scene: AvbdNativeSceneBinding,
383
+ options?: AvbdAdvanceOptions & {
384
+ afterStep?: (solver: AvbdGpuSolver3D) => void;
385
+ },
386
+ ): this;
387
+ detachFromScene(): this;
388
+ setFilters(
389
+ indices: ArrayLike<number>,
390
+ groups: ArrayLike<number>,
391
+ collidesWith: ArrayLike<number>,
392
+ ): void;
393
+ raycast(
394
+ origin: AvbdTuple3,
395
+ direction: AvbdTuple3,
396
+ options?: AvbdQueryOptions,
397
+ ): Promise<AvbdQueryHit<AvbdTuple3> | null>;
398
+ raycastAll(
399
+ rays: AvbdNativeRay<AvbdTuple3>[],
400
+ options?: AvbdQueryOptions,
401
+ ): Promise<Array<AvbdQueryHit<AvbdTuple3> | null>>;
402
+ sphereCast(
403
+ origin: AvbdTuple3,
404
+ radius: number,
405
+ direction: AvbdTuple3,
406
+ options?: AvbdQueryOptions,
407
+ ): Promise<AvbdQueryHit<AvbdTuple3> | null>;
408
+ sphereCastAll(
409
+ casts: AvbdNativeCast<AvbdTuple3>[],
410
+ options?: AvbdQueryOptions,
411
+ ): Promise<Array<AvbdQueryHit<AvbdTuple3> | null>>;
412
+ setMotor(slot: number, options: { speed?: number; maxTorque?: number }): void;
413
+ readonly params: AvbdGpuParameters3D;
414
+ gpuIndex(referenceIndex: number): number;
415
+ releaseJoints(slots: ArrayLike<number>): void;
416
+ setWorldAnchor(slot: number, point: ArrayLike<number>): void;
417
+ captureConstraintFrames(
418
+ entries: Array<{ slot: number; spring?: boolean }>,
419
+ ): void;
420
+ wakeAll(): void;
421
+ setSpringMaterial(
422
+ slot: number,
423
+ options?: { breakStrain?: number; breakForce?: number },
424
+ ): void;
425
+ setSensor(index: number, enabled: boolean): void;
426
+ setRestitution(index: number, value: number): void;
427
+ readSleepStats(): Promise<{ sleeping: number; wakeRequested: boolean }>;
428
+ }
429
+ export interface AvbdGpuSolver2D extends AvbdGpuSolver {
430
+ body(index: number): AvbdNativeBody2D;
431
+ bodyIndex(body: AvbdNativeBody2D | number): number;
432
+ editBodies(
433
+ edits: Iterable<
434
+ AvbdNativeBodyEdit<AvbdNativeBody2D | number, AvbdTuple2, number, number>
435
+ >,
436
+ ): this;
437
+ enableSleeping(options?: {
438
+ speedThreshold?: number;
439
+ timeThreshold?: number;
440
+ }): this;
441
+ disableSleeping(): this;
442
+ setSleepEnabled(index: number, enabled: boolean): this;
443
+ wakeAll(): this;
444
+ wakeUp(index: number): this;
445
+ readSleepStats(): Promise<{ sleeping: number; wakeRequested: boolean }>;
446
+ setAngleLimits(slot: number, options: AvbdAngleLimits2D): void;
447
+ /** Optional packed geometry. Hull descriptor: [4, radius, vertex-record offset, count]. Vertices: [x,y,normalX,normalY]. */
448
+ readonly shapeBuffer?: GPUBuffer;
449
+ /** Convex point cloud, 3–32 boundary vertices. Local geometry is centered on its area centroid; position is the center of mass. */
450
+ addHull(
451
+ points: ArrayLike<number> | readonly AvbdTuple2[],
452
+ options?: AvbdNativeBodyOptions2D,
453
+ ): number;
454
+ attachToScene(
455
+ scene: {
456
+ getEngine(): { getDeltaTime(): number };
457
+ onBeforeRenderObservable: {
458
+ add(callback: () => void): unknown;
459
+ remove(observer: any): unknown;
460
+ };
461
+ onDisposeObservable: {
462
+ addOnce(callback: () => void): unknown;
463
+ remove(observer: any): unknown;
464
+ };
465
+ },
466
+ options?: {
467
+ maxSubSteps?: number;
468
+ maxFrameTime?: number;
469
+ afterStep?: (solver: AvbdGpuSolver2D) => void;
470
+ },
471
+ ): this;
472
+ detachFromScene(): this;
473
+ /** Full XY extents; density: 0 makes a static body. */
474
+ addBox(size: AvbdTuple2, options?: AvbdNativeBodyOptions2D): number;
475
+ addCircle(radius: number, options?: AvbdNativeBodyOptions2D): number;
476
+ /** Local X axis; length includes both round caps. */
477
+ addCapsule(
478
+ radius: number,
479
+ length: number,
480
+ options?: AvbdNativeBodyOptions2D,
481
+ ): number;
482
+ addSegment(
483
+ start: AvbdTuple2,
484
+ end: AvbdTuple2,
485
+ options?: AvbdNativeSegmentOptions2D,
486
+ ): number;
487
+ addPlane(
488
+ normal?: AvbdTuple2,
489
+ offset?: number,
490
+ options?: AvbdNativePlaneOptions2D,
491
+ ): number;
492
+ setLinearVelocity(
493
+ index: number,
494
+ velocity: AvbdTuple2 | { x: number; y: number },
495
+ ): this;
496
+ setAngularVelocity(index: number, velocity: number): this;
497
+ applyImpulse(
498
+ index: number,
499
+ impulse: AvbdTuple2 | { x: number; y: number },
500
+ point?: AvbdTuple2 | { x: number; y: number },
501
+ ): this;
502
+ /** Applies force for one simulation step. Call every step for a continuous force. */
503
+ applyForce(
504
+ index: number,
505
+ force: AvbdTuple2 | { x: number; y: number },
506
+ point?: AvbdTuple2 | { x: number; y: number },
507
+ ): this;
508
+ applyAngularImpulse(index: number, impulse: number): this;
509
+ applyTorque(index: number, torque: number): this;
510
+ /** Preserves velocity; omitted angle preserves current rotation. */
511
+ teleport(
512
+ index: number,
513
+ position: AvbdTuple2 | { x: number; y: number },
514
+ angle?: number,
515
+ ): this;
516
+ readBodyState(index: number): Promise<{
517
+ position: [number, number];
518
+ angle: number;
519
+ linearVelocity: [number, number];
520
+ angularVelocity: number;
521
+ mass: number;
522
+ moment: number;
523
+ sleeping: boolean;
524
+ }>;
525
+ raycast(
526
+ origin: AvbdTuple2,
527
+ direction: AvbdTuple2,
528
+ options?: AvbdQueryOptions,
529
+ ): Promise<AvbdQueryHit<AvbdTuple2> | null>;
530
+ raycastAll(
531
+ rays: AvbdNativeRay<AvbdTuple2>[],
532
+ options?: AvbdQueryOptions,
533
+ ): Promise<Array<AvbdQueryHit<AvbdTuple2> | null>>;
534
+ circleCast(
535
+ origin: AvbdTuple2,
536
+ radius: number,
537
+ direction: AvbdTuple2,
538
+ options?: AvbdQueryOptions,
539
+ ): Promise<AvbdQueryHit<AvbdTuple2> | null>;
540
+ circleCastAll(
541
+ casts: AvbdNativeCast<AvbdTuple2>[],
542
+ options?: AvbdQueryOptions,
543
+ ): Promise<Array<AvbdQueryHit<AvbdTuple2> | null>>;
544
+ setRestitution(index: number, value: number): void;
545
+ setSensor(index: number, enabled: boolean): void;
546
+ setFilters(
547
+ indices: ArrayLike<number>,
548
+ groups: ArrayLike<number>,
549
+ collidesWith: ArrayLike<number>,
550
+ ): void;
551
+ /** Initially omitting indices watches every body. A supplied list selects pairs touching those bodies; later omissions preserve that selection. */
552
+ watchContacts(options?: {
553
+ maxPairs?: number;
554
+ maxEvents?: number;
555
+ indices?: number[];
556
+ }): this;
557
+ readContactEvents(): Promise<{
558
+ events: Array<{
559
+ type: "begin" | "end";
560
+ a: number;
561
+ b: number;
562
+ isTrigger: boolean;
563
+ point: [number, number, number];
564
+ normal: [number, number, number];
565
+ impulse: number;
566
+ step: number;
567
+ }>;
568
+ dropped: number;
569
+ full: boolean;
570
+ }>;
571
+ readonly params: AvbdGpuParameters2D;
572
+ setMotor(slot: number, options: { speed?: number; maxTorque?: number }): void;
573
+ appendJoint(
574
+ a: number,
575
+ b: number,
576
+ anchorA: AvbdTuple2,
577
+ anchorB: AvbdTuple2,
578
+ stiffness: AvbdTuple3,
579
+ breakForce?: number,
580
+ ): number;
581
+ setWorldAnchor(slot: number, x: number, y: number): void;
582
+ }
583
+ export interface AvbdNativeConstraint2D extends AvbdNativeConstraint {
584
+ /** Manual enable/dispose state; fracture state comes from readJoints(). */
585
+ readonly alive: boolean;
586
+ }
587
+ export interface AvbdAngleLimits2D {
588
+ minAngle?: number;
589
+ maxAngle?: number;
590
+ }
591
+ export interface AvbdAngularLimit2D extends AvbdNativeConstraint2D {
592
+ setLimits(options: AvbdAngleLimits2D): this;
593
+ }
594
+ export interface AvbdHinge2D {
595
+ readonly joint: AvbdNativeConstraint2D;
596
+ readonly limits: AvbdAngularLimit2D | null;
597
+ readonly motor: AvbdNativeConstraint2D | null;
598
+ readonly disposed: boolean;
599
+ dispose(): void;
600
+ destroy(): void;
601
+ }
602
+ export interface AvbdNativeConstraint {
603
+ readonly slot: number;
604
+ readonly disposed: boolean;
605
+ dispose(): void;
606
+ destroy(): void;
607
+ }
608
+ /**
609
+ * Build renderer-independent 3D topology before createSolver(). Body records are
610
+ * initial state; use live world handles for edits after upload. Defaults to Y up.
611
+ */
612
+ export class AvbdScene3D {
613
+ addHeightfield(options: AvbdHeightfieldOptions): AvbdNativeTerrain;
614
+ addStaticMesh(options: AvbdStaticMeshOptions): AvbdNativeTerrain;
615
+ addTerrain(
616
+ cooked: AvbdCookedTerrain,
617
+ options?: AvbdTerrainMaterial,
618
+ ): AvbdNativeTerrain;
619
+ /** Torque-limited angular drive; axes are body-local, or world-space for null A. */
620
+ addMotor(
621
+ a: AvbdRigid | null,
622
+ b: AvbdRigid,
623
+ options?: AvbdNativeAngularOptions,
624
+ ): AvbdNativeConstraint;
625
+ addHinge(
626
+ a: AvbdRigid | null,
627
+ b: AvbdRigid,
628
+ options?: AvbdNativeAngularOptions & {
629
+ anchorA?: AvbdTuple3;
630
+ anchorB?: AvbdTuple3;
631
+ span?: number;
632
+ motor?: { speed?: number; maxTorque?: number };
633
+ },
634
+ ): {
635
+ joints: AvbdNativeConstraint[];
636
+ limit?: AvbdNativeConstraint;
637
+ motor?: AvbdNativeConstraint;
638
+ dispose(): void;
639
+ };
640
+ constructor(options?: AvbdSceneOptions);
641
+ readonly bodies: AvbdRigid[];
642
+ readonly constraints: AvbdNativeConstraint[];
643
+ /** Full XYZ extents; mass: 0 or density: 0 makes a static body. */
644
+ addBox(size: AvbdTuple3, options?: AvbdNativeBodyOptions): AvbdRigid;
645
+ addSphere(radius: number, options?: AvbdNativeBodyOptions): AvbdRigid;
646
+ /** Exact rounded capsule along local Y; height includes both end caps. */
647
+ addCapsule(
648
+ radius: number,
649
+ height: number,
650
+ options?: AvbdNativeBodyOptions,
651
+ ): AvbdRigid;
652
+ /** Builds a centred convex collider aligned to its principal axes.
653
+ * Position/rotation place that principal frame; use aggregates to retain a mesh frame. */
654
+ addHull(
655
+ points: ArrayLike<number>,
656
+ options?: AvbdNativeBodyOptions,
657
+ ): AvbdRigid;
658
+ /** Spherical collision sample with translation only, suitable for spring systems. */
659
+ addPoint(
660
+ position: AvbdTuple3,
661
+ options?: { mass?: number; radius?: number; friction?: number },
662
+ ): AvbdRigid;
663
+ /** Helpers below build y-up geometry. Fabric lies in the x-z plane. */
664
+ addFabric(options?: {
665
+ columns?: number;
666
+ rows?: number;
667
+ spacing?: number;
668
+ origin?: AvbdTuple3;
669
+ mass?: number;
670
+ stiffness?: number;
671
+ pinEdges?: boolean;
672
+ /** Four corner anchors. When true, pinEdges defaults to false. */
673
+ pinCorners?: boolean;
674
+ /** N/m at pinned attachments; defaults to stiffness. Shear links retain half stiffness. */
675
+ anchorStiffness?: number;
676
+ /** Material at a spring's undeformed midpoint, normalized across the sheet.
677
+ * stiffnessScale multiplies stretch/shear/bending stiffness after anchorStiffness.
678
+ * Unspecified fields keep the fabric defaults. */
679
+ materialAt?: (
680
+ u: number,
681
+ v: number,
682
+ ) => {
683
+ stiffnessScale?: number;
684
+ breakStrain?: number;
685
+ breakForce?: number;
686
+ };
687
+ breakStrain?: number;
688
+ breakForce?: number;
689
+ bendingStiffness?: number;
690
+ }): {
691
+ points: AvbdRigid[];
692
+ grid: AvbdRigid[][];
693
+ indices: Uint32Array;
694
+ connections: AvbdNativeConstraint[];
695
+ triangleConnections: Uint32Array;
696
+ };
697
+ /** Rigid links laid out down Y from origin, regardless of the configured up vector. */
698
+ addRope(options?: {
699
+ segments?: number;
700
+ length?: number;
701
+ origin?: AvbdTuple3;
702
+ width?: number;
703
+ endMass?: number;
704
+ }): { anchor: AvbdRigid; links: AvbdRigid[] };
705
+ /** Y-up articulated rigid bodies; rotate/reposition parts for a different layout. */
706
+ addRagdoll(options?: { origin?: AvbdTuple3; mass?: number }): {
707
+ torso: AvbdRigid;
708
+ head: AvbdRigid;
709
+ bodies: AvbdRigid[];
710
+ };
711
+ /**
712
+ * Body-local anchors; null A uses a world-space anchor. Default linear rows are
713
+ * hard and angular rows free; angularStiffness: Infinity makes a weld.
714
+ */
715
+ addJoint(
716
+ a: AvbdRigid | null,
717
+ b: AvbdRigid,
718
+ anchorA?: AvbdTuple3,
719
+ anchorB?: AvbdTuple3,
720
+ options?: {
721
+ linearStiffness?: number;
722
+ angularStiffness?: number;
723
+ breakForce?: number;
724
+ breakOnPull?: boolean;
725
+ },
726
+ ): AvbdNativeConstraint;
727
+ /**
728
+ * Two local anchors; rest: -1 captures initial separation. Finite tensile
729
+ * breakForce or breakStrain enables breaking.
730
+ */
731
+ addSpring(
732
+ a: AvbdRigid,
733
+ b: AvbdRigid,
734
+ anchorA?: AvbdTuple3,
735
+ anchorB?: AvbdTuple3,
736
+ options?: {
737
+ stiffness?: number;
738
+ rest?: number;
739
+ breakStrain?: number;
740
+ breakForce?: number;
741
+ },
742
+ ): AvbdNativeConstraint;
743
+ /**
744
+ * Consume this builder once. Use world.body(handle) to resolve spatially sorted
745
+ * GPU slots; initial body records are not automatically updated from GPU poses.
746
+ */
747
+ createSolver(device: GPUDevice, options?: AvbdGpuOptions): AvbdGpuSolver3D;
748
+ createSolver(
749
+ pipeline: AvbdSolverPipeline,
750
+ options?: AvbdGpuOptions,
751
+ ): AvbdSolver3D;
752
+ }
753
+ export interface AvbdNativeAngularOptions {
754
+ axisA?: AvbdTuple3;
755
+ axisB?: AvbdTuple3;
756
+ speed?: number;
757
+ maxTorque?: number;
758
+ minAngle?: number;
759
+ maxAngle?: number;
760
+ }
761
+ export interface AvbdNativeBodyOptions2D {
762
+ allowSleep?: boolean;
763
+ density?: number;
764
+ friction?: number;
765
+ position?: AvbdTuple2;
766
+ angle?: number;
767
+ /** [linear X, linear Y, angular radians/second]. */
768
+ velocity?: AvbdTuple3;
769
+ restitution?: number;
770
+ isTrigger?: boolean;
771
+ group?: number;
772
+ collidesWith?: number;
773
+ }
774
+ export interface AvbdNativeSegmentOptions2D extends Omit<
775
+ AvbdNativeBodyOptions2D,
776
+ "position" | "angle"
777
+ > {
778
+ /** Zero by default. Zero-thickness segments must be static. */
779
+ radius?: number;
780
+ }
781
+ export type AvbdNativePlaneOptions2D = Omit<
782
+ AvbdNativeBodyOptions2D,
783
+ "position" | "angle" | "density"
784
+ > & { density?: 0 };
785
+ /**
786
+ * Build XY topology before createSolver(). Returns numeric body indices;
787
+ * world.body(index) provides the object-oriented command facade after upload.
788
+ */
789
+ export class AvbdScene2D {
790
+ constructor(
791
+ options?: Omit<AvbdSceneOptions, "up"> & {
792
+ alpha?: number;
793
+ postStabilize?: boolean;
794
+ matchNearest?: boolean;
795
+ },
796
+ );
797
+ readonly bodyCount: number;
798
+ /** Convex point cloud, 3–32 boundary vertices. Position is the area centroid / center of mass. */
799
+ addHull(
800
+ points: ArrayLike<number> | readonly AvbdTuple2[],
801
+ options?: AvbdNativeBodyOptions2D,
802
+ ): number;
803
+ addCircle(radius: number, options?: AvbdNativeBodyOptions2D): number;
804
+ /** Local X axis; length includes both round caps. */
805
+ addCapsule(
806
+ radius: number,
807
+ length: number,
808
+ options?: AvbdNativeBodyOptions2D,
809
+ ): number;
810
+ /** Finite two-sided line, or a rounded segment if radius > 0. */
811
+ addSegment(
812
+ start: AvbdTuple2,
813
+ end: AvbdTuple2,
814
+ options?: AvbdNativeSegmentOptions2D,
815
+ ): number;
816
+ /** Normal is normalized. Allowed side: dot(normal, point) >= offset. */
817
+ addPlane(
818
+ normal?: AvbdTuple2,
819
+ offset?: number,
820
+ options?: AvbdNativePlaneOptions2D,
821
+ ): number;
822
+ addBox(
823
+ size: readonly [number, number],
824
+ options?: AvbdNativeBodyOptions2D,
825
+ ): number;
826
+ /**
827
+ * Body-local anchors; A = -1 anchors to the world. stiffness is [X, Y, rotation];
828
+ * Infinity locks a row and zero frees it. Lock all rows for a 2D weld.
829
+ */
830
+ addJoint(
831
+ a: number,
832
+ b: number,
833
+ anchorA?: readonly [number, number],
834
+ anchorB?: readonly [number, number],
835
+ options?: { stiffness?: AvbdTuple3; breakForce?: number },
836
+ ): AvbdNativeConstraint2D;
837
+ /** Connect two body-local anchors; rest is the unstretched length in world units. */
838
+ addSpring(
839
+ a: number,
840
+ b: number,
841
+ anchorA?: readonly [number, number],
842
+ anchorB?: readonly [number, number],
843
+ options?: { stiffness?: number; rest?: number },
844
+ ): AvbdNativeConstraint2D;
845
+ /** Relative angular drive in radians/second; maxTorque bounds its effort. */
846
+ addMotor(
847
+ a: number,
848
+ b: number,
849
+ options?: { speed?: number; maxTorque?: number },
850
+ ): AvbdNativeConstraint2D;
851
+ addAngularLimit(
852
+ a: number,
853
+ b: number,
854
+ options?: AvbdAngleLimits2D,
855
+ ): AvbdAngularLimit2D;
856
+ addHinge(
857
+ a: number,
858
+ b: number,
859
+ anchorA?: AvbdTuple2,
860
+ anchorB?: AvbdTuple2,
861
+ options?: AvbdAngleLimits2D & {
862
+ motor?: { speed?: number; maxTorque?: number };
863
+ },
864
+ ): AvbdHinge2D;
865
+ createSolver(
866
+ device: GPUDevice,
867
+ options?: {
868
+ bodyBuffer?: GPUBuffer;
869
+ bodyCapacity?: number;
870
+ colorRounds?: number;
871
+ minimumColors?: number;
872
+ sleeping?: boolean | { speedThreshold?: number; timeThreshold?: number };
873
+ },
874
+ ): AvbdGpuSolver2D;
875
+ createSolver(
876
+ pipeline: AvbdSolverPipeline,
877
+ options?: AvbdGpuOptions,
878
+ ): AvbdSolver2D;
879
+ }