@ignifx/physics-2d 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +29 -158
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +71 -137
- package/dist/index.js.map +1 -1
- package/package.json +5 -5
- package/skills/physics-2d/SKILL.md +22 -3
package/dist/index.d.ts
CHANGED
|
@@ -82,20 +82,9 @@ export declare function defaultPhysics2DSettings(): Physics2DSettings;
|
|
|
82
82
|
//#endregion
|
|
83
83
|
//#region src/material.d.ts
|
|
84
84
|
/**
|
|
85
|
-
*
|
|
86
|
-
*
|
|
87
|
-
*
|
|
88
|
-
*
|
|
89
|
-
* `11-2d-toolkit.md` §8 lists a `PhysicsMaterial2D`, and `09-physics.md` §2.4 already defines
|
|
90
|
-
* `{ "format": "ignifx.physicsmaterial", "friction": …, "staticFriction": …, "restitution": … }`.
|
|
91
|
-
* Inventing a second document for the same two numbers would give artists two files to keep in
|
|
92
|
-
* step, so this package reads **the same format and the same `.physicsmaterial.json` extension**,
|
|
93
|
-
* ignoring `staticFriction` (Rapier 2D has one friction coefficient, `geometry/collider.d.ts`).
|
|
94
|
-
* `@ignifx/physics` and `@ignifx/physics-2d` never run in one world — `IGX-1101` — so the two
|
|
95
|
-
* loaders never collide.
|
|
96
|
-
*
|
|
97
|
-
* The 2D-only knobs, how two surfaces' friction and restitution are **combined**, are fields on the
|
|
98
|
-
* collider rather than on the asset, so the document stays byte-identical to the 3D one.
|
|
85
|
+
* Read the shared `.physicsmaterial.json` format, ignoring `staticFriction` because Rapier has one
|
|
86
|
+
* friction coefficient. Combine rules stay on colliders. Only one physics extension runs per world,
|
|
87
|
+
* so the 2D and 3D loaders do not compete.
|
|
99
88
|
*/
|
|
100
89
|
/**
|
|
101
90
|
* The asset type name `.physicsmaterial.json` addresses resolve to.
|
|
@@ -184,23 +173,10 @@ export declare function parsePhysicsMaterial2D(address: string, document: JsonVa
|
|
|
184
173
|
//#endregion
|
|
185
174
|
//#region src/lite/rapier/world.d.ts
|
|
186
175
|
/**
|
|
187
|
-
*
|
|
188
|
-
*
|
|
189
|
-
*
|
|
190
|
-
*
|
|
191
|
-
* ## Facts measured against `@dimforge/rapier2d-compat@0.20.0` (spike S6.2)
|
|
192
|
-
*
|
|
193
|
-
* - **The broadphase is only built by `World.step`.** Before the first step, `castRay` returns
|
|
194
|
-
* `null` and `KinematicCharacterController.computeColliderMovement` reports the full requested
|
|
195
|
-
* motion with no obstacles; a collider created after the last step is invisible until the next
|
|
196
|
-
* one. {@link primeWorld} runs a zero-length step, which builds the broadphase without
|
|
197
|
-
* integrating — but it is *not* free of side effects on a running simulation (a settled pile
|
|
198
|
-
* hashes differently), so the runtime primes only when the collider set changed.
|
|
199
|
-
* - **`InteractionGroups` is one 32-bit number**: membership in the high 16 bits, filter in the low
|
|
200
|
-
* 16 (`geometry/interaction_groups.d.ts`). Sixteen layers, not thirty-two.
|
|
201
|
-
* - **`ActiveCollisionTypes.DEFAULT` excludes non-dynamic pairs**, so a kinematic character never
|
|
202
|
-
* reports entering a static sensor. Colliders that want events are given
|
|
203
|
-
* `ActiveCollisionTypes.ALL`.
|
|
176
|
+
* Keep Rapier calls inside the adapter (ADR-0006).
|
|
177
|
+
* Queries need a built broadphase. Prime after collider-set changes only: a zero-length step can
|
|
178
|
+
* still affect a running simulation. Interaction groups support sixteen layers; event-enabled
|
|
179
|
+
* colliders include non-dynamic pairs so characters can enter static sensors.
|
|
204
180
|
*/
|
|
205
181
|
/**
|
|
206
182
|
* Rapier's physics world.
|
|
@@ -265,29 +241,9 @@ type RapierShape2D = {
|
|
|
265
241
|
//#endregion
|
|
266
242
|
//#region src/components/collider.d.ts
|
|
267
243
|
/**
|
|
268
|
-
*
|
|
269
|
-
*
|
|
270
|
-
*
|
|
271
|
-
*
|
|
272
|
-
* ## Corrections to §8
|
|
273
|
-
*
|
|
274
|
-
* - **The material is two fields, not a union.** A schema field has exactly one kind (ADR-0004), so
|
|
275
|
-
* {@link Collider2D.material} (an asset reference) and {@link Collider2D.inlineMaterial} (a
|
|
276
|
-
* record) are separate; the asset wins, the inline record is the fallback, and
|
|
277
|
-
* `physics2d.defaultMaterial` is the last resort. This is the same split `@ignifx/physics` makes.
|
|
278
|
-
* - **Combine rules are collider fields.** Rapier combines two surfaces' friction and restitution
|
|
279
|
-
* with a per-collider rule (`dynamics/coefficient_combine_rule.d.ts`); the 3D
|
|
280
|
-
* `ignifx.physicsmaterial` document has no such field, so the knob lives here rather than
|
|
281
|
-
* forking the asset format.
|
|
282
|
-
* - **A collider may stand for several Rapier colliders.** `TilemapCollider2D` produces one polygon
|
|
283
|
-
* per merged tile run, so {@link Collider2D.collectShapes} fills a list rather than returning one
|
|
284
|
-
* shape.
|
|
285
|
-
*
|
|
286
|
-
* ## Sizes and scale
|
|
287
|
-
*
|
|
288
|
-
* Sizes are authored in local metres and multiplied by the entity's lossy scale when the shapes are
|
|
289
|
-
* built. Rapier cannot rescale a built shape, so a later scale change needs
|
|
290
|
-
* {@link Collider2D.rebuild}.
|
|
244
|
+
* Build shapes from local metres multiplied by entity scale; call `rebuild` after changing scale.
|
|
245
|
+
* Material assets take precedence over inline values, then project defaults. Friction and restitution
|
|
246
|
+
* combine rules belong to each collider. A tilemap collider can produce several backend shapes.
|
|
291
247
|
*/
|
|
292
248
|
/**
|
|
293
249
|
* How two surfaces' coefficients are combined when they touch, mirroring Rapier's
|
|
@@ -390,26 +346,8 @@ export declare abstract class Collider2D extends Component implements ComponentH
|
|
|
390
346
|
//#endregion
|
|
391
347
|
//#region src/events.d.ts
|
|
392
348
|
/**
|
|
393
|
-
*
|
|
394
|
-
*
|
|
395
|
-
*
|
|
396
|
-
* ## The callbacks are the 3D names
|
|
397
|
-
*
|
|
398
|
-
* `11-2d-toolkit.md` §8 writes them as `onCollisionEnter2D`/`onTriggerEnter2D`. The kernel's
|
|
399
|
-
* `PhysicsCallbackName` union has five members and no `2D` variants
|
|
400
|
-
* (`packages/core/src/lifecycle/callbacks.ts`), and a world uses either `physics()` or
|
|
401
|
-
* `physics2d()` — never both, which is what `IGX-1101` enforces. So a 2D game implements
|
|
402
|
-
* `onCollisionEnter`, `onCollisionStay`, `onCollisionExit`, `onTriggerEnter`, and `onTriggerExit`,
|
|
403
|
-
* and receives the 2D payloads below. §8 is corrected accordingly.
|
|
404
|
-
*
|
|
405
|
-
* ## Identities are native
|
|
406
|
-
*
|
|
407
|
-
* Rapier reports **both** colliders of every event (`pipeline/event_queue.d.ts`,
|
|
408
|
-
* `drainCollisionEvents`), so `other` is never `null` for a live body and the ADR-0013 waiver that
|
|
409
|
-
* 3D physics needs has no counterpart here.
|
|
410
|
-
*
|
|
411
|
-
* Every payload is **pooled**: the same instances are reused for every event of a step, so a script
|
|
412
|
-
* that needs a value after its callback returns must copy it out (coding standards §7).
|
|
349
|
+
* 2D physics uses the same five collision and trigger callback names as 3D, without a `2D` suffix.
|
|
350
|
+
* Rapier supplies both collider identities. Payloads are pooled; copy values needed after a callback.
|
|
413
351
|
*/
|
|
414
352
|
/**
|
|
415
353
|
* One contact point of a 2D collision. Pooled with its owning {@link Collision2D}.
|
|
@@ -488,24 +426,9 @@ interface CharacterCollision2D {
|
|
|
488
426
|
//#endregion
|
|
489
427
|
//#region src/components/rigidbody.d.ts
|
|
490
428
|
/**
|
|
491
|
-
*
|
|
492
|
-
*
|
|
493
|
-
*
|
|
494
|
-
* ## Decisions §8 leaves open
|
|
495
|
-
*
|
|
496
|
-
* - **`mass` is exact, and `0` means "weigh the colliders".** Rapier derives a body's mass and
|
|
497
|
-
* angular inertia from each collider's *density* and area (`geometry/collider.d.ts`,
|
|
498
|
-
* `setDensity`), and `setAdditionalMass` adds mass without inertia. So a non-zero `mass` is
|
|
499
|
-
* applied by scaling every collider's density until the body weighs exactly that, which keeps the
|
|
500
|
-
* inertia distribution the shapes imply; `mass: 0` leaves Rapier's own density of 1 kg/m² alone.
|
|
501
|
-
* - **`freezeRotation` is one boolean, not three.** A 2D body has one rotational degree of freedom
|
|
502
|
-
* (`dynamics/rigid_body.d.ts`, `lockRotations`).
|
|
503
|
-
* - **Damping is per body here, unlike 3D.** Havok has no per-body damping; Rapier does
|
|
504
|
-
* (`setLinearDamping`, `setAngularDamping`), so `linearDamping` and `angularDamping` are real
|
|
505
|
-
* fields rather than upstream requests.
|
|
506
|
-
* - **`collisionEvents` is a three-valued enum**, exactly as in `@ignifx/physics`: a schema field
|
|
507
|
-
* has one kind (ADR-0004), and `"auto"` recomputes from `ctx.entityImplements` whenever a
|
|
508
|
-
* component joins or leaves the entity.
|
|
429
|
+
* A nonzero mass rescales collider densities while preserving their inertia distribution.
|
|
430
|
+
* Zero keeps the default density of 1 kg/m². Rotation has one freeze flag; damping is per body.
|
|
431
|
+
* Automatic collision events follow callbacks as components join or leave the entity.
|
|
509
432
|
*/
|
|
510
433
|
/**
|
|
511
434
|
* How a 2D body moves.
|
|
@@ -675,27 +598,9 @@ export declare class Rigidbody2D extends Component implements ComponentHooks {
|
|
|
675
598
|
//#endregion
|
|
676
599
|
//#region src/components/character-controller.d.ts
|
|
677
600
|
/**
|
|
678
|
-
*
|
|
679
|
-
*
|
|
680
|
-
*
|
|
681
|
-
* ## How it is driven
|
|
682
|
-
*
|
|
683
|
-
* Call {@link CharacterController2D.move} from `fixedUpdate`. The requested displacements of one
|
|
684
|
-
* step are summed and handed to Rapier's `computeColliderMovement` by the step system, so two
|
|
685
|
-
* scripts moving the same character compose instead of fighting. Gravity is **not** applied by this
|
|
686
|
-
* component: a bare controller is purely kinematic, exactly as the 3D one is, and the toolkit
|
|
687
|
-
* controller scripts integrate gravity themselves.
|
|
688
|
-
*
|
|
689
|
-
* After the step the controller owns the entity's world X and Y. Z and `rotation2D` stay
|
|
690
|
-
* user-controlled.
|
|
691
|
-
*
|
|
692
|
-
* ## The one addition to §8: `shape`
|
|
693
|
-
*
|
|
694
|
-
* §8 describes the controller as a capsule. Measured against `@dimforge/rapier2d-compat@0.20.0`
|
|
695
|
-
* (spike S6.2), Rapier's autostep clears a 0.3 m step with a **box** character and refuses anything
|
|
696
|
-
* above about 0.15 m with a capsule of radius 0.2 — so `stepOffset` is only usable with a box.
|
|
697
|
-
* {@link CharacterController2D.shape} therefore chooses between the two, defaulting to `"capsule"`
|
|
698
|
-
* as §8 says, and the limitation is documented in the skill and in ADR-0006's validation section.
|
|
601
|
+
* Sum `move` displacements during `fixedUpdate`; game scripts supply gravity.
|
|
602
|
+
* After the step the controller owns world X/Y, while Z and rotation remain user-controlled.
|
|
603
|
+
* Box shapes support taller autosteps than capsules; see ADR-0006's validation results.
|
|
699
604
|
*/
|
|
700
605
|
/**
|
|
701
606
|
* The collision shape a 2D character controller uses.
|
|
@@ -779,6 +684,10 @@ export declare class CharacterController2D extends Component implements Componen
|
|
|
779
684
|
/**
|
|
780
685
|
* Emitted once per obstacle the character hit during a step.
|
|
781
686
|
*
|
|
687
|
+
* @remarks
|
|
688
|
+
* Trigger colliders are not obstacles — the character walks straight through them — so a sensor
|
|
689
|
+
* never appears here. Listen for `onTriggerEnter`/`onTriggerExit` on the entity's scripts instead.
|
|
690
|
+
*
|
|
782
691
|
* @returns The signal, created on first access.
|
|
783
692
|
*/
|
|
784
693
|
get onCollided(): Signal<CharacterCollision2D>;
|
|
@@ -970,25 +879,9 @@ export declare class EdgeCollider2D extends Collider2D {
|
|
|
970
879
|
//#endregion
|
|
971
880
|
//#region src/components/tilemap-collider.d.ts
|
|
972
881
|
/**
|
|
973
|
-
*
|
|
974
|
-
*
|
|
975
|
-
*
|
|
976
|
-
* ## How it reaches the data
|
|
977
|
-
*
|
|
978
|
-
* `@ignifx/2d` is an **optional** peer of `@ignifx/physics-2d` (a physics-only 2D game needs no
|
|
979
|
-
* sprite toolkit), so this component never imports the toolkit as a value — only the
|
|
980
|
-
* `TilemapCollisionData` type. A game, or `@ignifx/2d`'s own tilemap loader, assigns
|
|
981
|
-
* {@link TilemapCollider2D.collisionData}; the runtime compares the data's `version` at the start of
|
|
982
|
-
* every fixed step and rebuilds the shapes when it changes, which is the same moment
|
|
983
|
-
* `Tilemap.onCollisionChanged` fires and needs no signal subscription across the package boundary.
|
|
984
|
-
*
|
|
985
|
-
* ## Why closed polylines rather than polygons
|
|
986
|
-
*
|
|
987
|
-
* A merged run of solid tiles is not convex in general — an L of tiles merges into an L — and
|
|
988
|
-
* Rapier's polygon shape is a **convex hull** (`geometry/shape.d.ts`, `ConvexPolygon`), which would
|
|
989
|
-
* silently fill the notch. A closed `polyline` reproduces the outline exactly and is the shape
|
|
990
|
-
* static level geometry wants; the trade-off is that a polyline is infinitely thin, so a body that
|
|
991
|
-
* starts inside the tilemap is not pushed out.
|
|
882
|
+
* Read tile collision data through the optional 2D toolkit's type and rebuild when its version changes.
|
|
883
|
+
* Closed polylines preserve concave tile outlines that convex hulls would fill in. They are thin
|
|
884
|
+
* surfaces, so a body starting inside the map is not pushed out.
|
|
992
885
|
*/
|
|
993
886
|
/**
|
|
994
887
|
* The collision surface of a tilemap, as one static body's worth of Rapier shapes.
|
|
@@ -1446,7 +1339,7 @@ declare class Physics2DRuntime {
|
|
|
1446
1339
|
teleportController(component: CharacterController2D, position: Vec2Like): void;
|
|
1447
1340
|
/**
|
|
1448
1341
|
* Restores the authoritative pose of every interpolated body, undoing the display pose the
|
|
1449
|
-
* `
|
|
1342
|
+
* `Update` system wrote at the top of the previous frame's `Update` (`09-physics.md` §1).
|
|
1450
1343
|
*/
|
|
1451
1344
|
restorePoses(): void;
|
|
1452
1345
|
/**
|
|
@@ -1596,31 +1489,9 @@ export declare class Physics2DService {
|
|
|
1596
1489
|
//#endregion
|
|
1597
1490
|
//#region src/extension.d.ts
|
|
1598
1491
|
/**
|
|
1599
|
-
* The
|
|
1600
|
-
* `
|
|
1601
|
-
*
|
|
1602
|
-
* ## Why `@ignifx/2d` is optional rather than required
|
|
1603
|
-
*
|
|
1604
|
-
* `00-overview.md` §2 draws `@ignifx/physics-2d` above `@ignifx/2d`, and the layering rule allows
|
|
1605
|
-
* the dependency. At runtime the only thing this package needs from the toolkit is the
|
|
1606
|
-
* `TilemapCollisionData` **type**, which is erased at build time, so a platformer that draws its own
|
|
1607
|
-
* graphics can register `physics2d()` on its own. The manifest therefore declares
|
|
1608
|
-
* `requires: []` and `optional: ["@ignifx/2d"]`, and `package.json` makes `@ignifx/2d` an optional
|
|
1609
|
-
* peer dependency.
|
|
1610
|
-
*
|
|
1611
|
-
* ## Why the world is built in `onStart`
|
|
1612
|
-
*
|
|
1613
|
-
* `register` runs before `createApp` builds the `World` (`packages/core/src/app/app.ts`,
|
|
1614
|
-
* `initialize`), and the Rapier world needs the world's layer table; Rapier's WebAssembly module
|
|
1615
|
-
* also has to be instantiated, which is asynchronous. So `register` declares everything against a
|
|
1616
|
-
* {@link Physics2DHost} holder and `onStart` fills the holder in.
|
|
1617
|
-
*
|
|
1618
|
-
* ## One physics extension per world
|
|
1619
|
-
*
|
|
1620
|
-
* `11-2d-toolkit.md` §8: "A world uses either `physics()` or `physics2d()`; registering both throws
|
|
1621
|
-
* `IGX-1101`". `@ignifx/physics` publishes its Havok simulation scene through
|
|
1622
|
-
* `ExtensionContext.setSimulationScene`, so `app.world.lite.simulationScene !== null` at `onStart`
|
|
1623
|
-
* is the test — and it needs no dependency on `@ignifx/physics`, which this package must not have.
|
|
1492
|
+
* The 2D toolkit is optional; physics only needs its tile-collision data type.
|
|
1493
|
+
* Create the Rapier world in `onStart`, once the world's layer table exists and WASM is ready.
|
|
1494
|
+
* Reject a world already using 3D physics with `IGX-1101`.
|
|
1624
1495
|
*/
|
|
1625
1496
|
/**
|
|
1626
1497
|
* What `physics2d()` accepts.
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/settings.ts","../src/material.ts","../src/lite/rapier/world.ts","../src/components/collider.ts","../src/events.ts","../src/components/rigidbody.ts","../src/components/character-controller.ts","../src/components/colliders.ts","../src/components/tilemap-collider.ts","../src/errors.ts","../src/queries.ts","../src/runtime/body-record.ts","../src/runtime/runtime.ts","../src/runtime/host.ts","../src/service.ts","../src/extension.ts","../src/schemas.ts"],"mappings":";;;;;;;;;;;;;;;;;qBAiBa;;;;;;UAOI;;WAEN;;WAEA;;;;;;;;;;;;;;;;;;;UAoBM;;WAEN,SAAS;;;;;WAKT,iBAAiB,SAAS;;WAE1B,iBAAiB;;WAEjB;;WAEA;;;;;;;;;;;;;wBAuBK,2BAA2B;;;;;;;;wBAoB3B,4BAA4B
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/settings.ts","../src/material.ts","../src/lite/rapier/world.ts","../src/components/collider.ts","../src/events.ts","../src/components/rigidbody.ts","../src/components/character-controller.ts","../src/components/colliders.ts","../src/components/tilemap-collider.ts","../src/errors.ts","../src/queries.ts","../src/runtime/body-record.ts","../src/runtime/runtime.ts","../src/runtime/host.ts","../src/service.ts","../src/extension.ts","../src/schemas.ts"],"mappings":";;;;;;;;;;;;;;;;;qBAiBa;;;;;;UAOI;;WAEN;;WAEA;;;;;;;;;;;;;;;;;;;UAoBM;;WAEN,SAAS;;;;;WAKT,iBAAiB,SAAS;;WAE1B,iBAAiB;;WAEjB;;WAEA;;;;;;;;;;;;;wBAuBK,2BAA2B;;;;;;;;wBAoB3B,4BAA4B;;;;;;;;;;;;;qBCzF/B;;;;;;qBAOA;;;;;;qBAOA;;;;;;qBAOA;;;;;;;;;;;;qBAaA,6BAA6B;;SAEjC;;WAGE;;WAGA;;WAGA;;;;;;;EAQT,YAAY,cAAc,QAAQ;;;;;;;;;;;;;SAkB3B,WAAW,cAAc,QAAQ,QAAQ,2BAA2B;;;;;;;;;wBAe7D,iCAAiC,YAAY;;;;;;;;;;;wBAqB7C,uBAAuB,iBAAiB,UAAU,YAAY;;;;;;;;;;;;;;KC3FlE,cAAc;;;;;;KAOd,aAAa;;;;;;KAOb,iBAAiB;;;;;;cAchB;;WAEX;;WAEA;;WAEA;;;;;;;KAQU,uBAAuB,2BAA2B;;;;;;KA+BlD;WACG;WAAsB;WAA4B;;WAClD;WAAyB;;WACzB;WAA0B;WAA6B;;WACvD;WAA0B,QAAQ;;WAClC;WAA2B,QAAQ;;;;;;;;;;;;;;;qBC1FrC;;;;;;KAOD,sBAAsB;;;;;;UAOjB;;WAEN,OAAO;;WAEP,QAAQ;;WAER;;WAEA;;;;;;;;;wBAUK,oBAAoB;;;;;;8BAuBd,mBAAmB,qBAAqB;;SAErD;;EAGP,QAAgB;;EAGhB;;EAGA,UAAkB,YAAY;;EAG9B,gBAAwB;;EAGxB,iBAAyB;;EAGzB,oBAA4B;;;;;;EAO5B;;EAGA;;EAGA;;EAMA;;EAKA;;;;;;;;;;;EAcA;;;;;;;EAUA,gBAAgB,UAAU,0BAA0B;;;;;;;;;WAY3C,cAAc,OAAO,UAAU,KAAK;;;;;;;;;;;;;UC7I9B;;WAEN,OAAO;;WAEP,QAAQ;;WAER;;;;;;;;;;;;;;;;;;;UAoBM;;WAEN,OAAO;;WAEP,eAAe;;WAEf,MAAM;;WAEN,cAAc;;;;;;;UAQR;;WAEN,OAAO;;WAEP,eAAe;;WAEf,MAAM;;WAEN,cAAc;;WAEd,mBAAmB;;WAEnB,kBAAkB;;;;;;;UAQZ;;WAEN,OAAO;;WAEP,eAAe;;WAEf,OAAO;;WAEP,QAAQ;;;;;;;;;;;;;;qBClEN;;;;;;KAOD,qBAAqB;;;;;;qBAOpB;;;;;;KAOD,8BAA8B;;;;;;qBAO7B;;;;;;KAOD,+BAA+B;;;;;;;UAQ1B;;WAEN,MAAM;;;;;;;;;;;;;;;qBAgBJ,oBAAoB,qBAAqB;;;SAE7C;;SAGA;;SAGA,QAAQ;EAEf,UAAkB;EAElB;EAEA;EAEA;EAEA;EAEA;EAEA,eAAuB;EAEvB,iBAAyB;;EAGzB;;EAMA;;EAKA;;;;;EAQA;;;;;;MASI,UAAU;;;;;;MASV,kBAAkB;;;;;;MAWlB,eAAe,OAAO;;;;;;;EAU1B,oBAAoB,KAAK,cAAc;;;;;;MAUnC;;;;;;MASA,gBAAgB;;;;;;MAShB;;;;;;;;;;;;;;EAiBJ,SAAS,OAAO,UAAU,QAAQ;;;;;;;EAUlC,WAAW,SAAS,UAAU,QAAQ;;;;;;EAStC,UAAU;;;;;;;;EAWV,SAAS,UAAU,UAAU;;;;;;;;;;;;;;qBC3NlB;;;;;;KAOD,2BAA2B;;;;;;;;;;;;;;;;;qBAkB1B,8BAA8B,qBAAqB;;;SAEvD;;SAGA;;SAGA,QAAQ;EAEf,OAAe;EAEf;EAEA;EAEA,QAAgB;EAEhB;EAEA;EAEA;EAEA;EAEA;EAEA;EAEA,eAAuB;;EAqBvB;;EAMA;;EAKA;;EAKA;;;;;;EASA,KAAK,cAAc;;;;;;MASf,YAAY;;;;;;MAYZ;;;;;;MASA,gBAAgB;;;;;;;;;;MAahB,cAAc,OAAO;;;;;;EAUzB,SAAS,UAAU;;;;;;;;EAYnB,mBAAmB;;;;;;;;;;;EAcnB,gBAAgB,UAAU,UAAU,mBAAmB,QAAQ,iBAAiB;;;;;;;;EAiBhF,UAAU,WAAW;;;;;;;;;qBCpMV;;;;;;KAOD,6BAA6B;;;;;;;;;;;;qBAa5B,sBAAsB;;SAE1B;;SAGA,QAAQ;EAEf,MAAc;;EAGd;;;;;;;;;EAaA,cAAc,OAAO,UAAU,KAAK;;;;;;;;qBAoBzB,yBAAyB;;SAE7B;;SAGA,QAAQ;EAEf;;EAGA;;;;;;;;;EAaA,cAAc,OAAO,UAAU,KAAK;;;;;;;;;;;;;qBAqBzB,0BAA0B;;SAE9B;;SAGA,QAAQ;EAEf;EAEA;EAEA,WAAmB;;EAGnB;;;;;;;;;EAaA,cAAc,OAAO,UAAU,KAAK;;;;;;;;;;;;qBA2BzB,0BAA0B;;SAE9B;;SAGA,QAAQ;EAEf,QAAgB;;EAGhB;;;;;;;;;;EAcA,cAAc,OAAO,UAAU,KAAK;;;;;;;qBAkBzB,uBAAuB;;SAE3B;;SAGA,QAAQ;EAEf,QAAgB;;EAGhB;;;;;;;;;;EAcA,cAAc,OAAO,UAAU,KAAK;;;;;;;;;;;;;;;;;;;;;qBC/NzB,0BAA0B;;;SAE9B;;SAGA,QAAQ;;EASf;;;;;;MAUI,iBAAiB;;;;;;MASjB,cAAc,OAAO;;;;;;;;;EAczB;;;;;;;;;EAYA,cAAc,OAAO,UAAU,KAAK;;;;;;;;;;;;;;;;;;;;;;;;;;;qBCxDzB;;WAEX;;WAEA;;WAEA;;WAEA;;WAEA;;WAEA;;WAEA;;WAEA;;WAEA;;;;;;;YAQU,6BAA6B,iCAAiC;;;;;;qBAO7D,2BAA2B,SAAS;;;;;;UAiBhC;;WAEN,UAAU;;WAEV;;WAEA;;;;;;;;;;;;;;;;;;;wBAoBK,eACd,MAAM,oBACN,iBACA,UAAU,wBACT;;;;;;;;;;;;;;;;;UCzFc;;WAEN,YAAY;;WAEZ;;;;;;;;;;;UAYM;;WAEN,QAAQ;;WAER,UAAU;;WAEV,OAAO;;WAEP,QAAQ;;WAER;;;;;;;UAQM;;WAEN,QAAQ;;WAER,UAAU;;WAEV,OAAO;;WAEP,QAAQ;;WAER;;WAEA;;;;;;;;;;;UCnDM;;WAEN,QAAQ;;EAEjB,WAAW;;EAEX,YAAY;;EAEZ,MAAM;;EAEN,WAAW;;EAEX,SAAS;;EAET,oBAAoB;;EAEpB,QAAQ;;EAER;;EAEA;;EAEA;;WAES,kBAAkB;;EAE3B;;WAES,iBAAiB;;EAE1B;;;;;;WAMS,iBAAiB;;EAE1B;;EAEA;;EAEA,aAAa;;;;;;;;;;;;;UCsBE;;WAEN,KAAK;;WAEL,UAAU;;WAEV,OAAO;;WAEP,UAAU;;;;;;;qBAQR;;;;;;qBAcA;;UAiEH;;WAEC,QAAQ;;WAER,OAAO;;WAEP,UAAU;;;;;;;cAiBR;;;;;;;;EAyFX,YAAY,SAAS;;;;;;MAmBjB,SAAS;;;;;;;MAUT;;;;;;MASA,oBAAoB;;;;;;MASpB,YAAY;;;;;;EAShB,WAAW,SAAS;;;;;;EASpB,UAAU,QAAQ;;;;;;EAWlB,oBAAoB,WAAW;;;;;;EAS/B,kBAAkB,WAAW;;;;;;;EAU7B,aAAa,QAAQ,SAAS;;;;;;;EAU9B,SAAS,QAAQ,SAAS;;;;;;;;EAW1B,UAAU,UAAU,wBAAwB;;;;;;;EAU5C,OAAO,QAAQ;;;;;;;EAWf,WAAW,QAAQ,QAAQ,KAAK;;;;;;;EAahC,YAAY,QAAQ;;;;;;;EAWpB,kBAAkB,QAAQ,QAAQ,UAAU;;;;;;;EAa5C,mBAAmB,QAAQ,QAAQ;;;;;;;;EAcnC,SAAS,QAAQ,QAAQ,OAAO,UAAU,OAAO;;;;;;;;EAcjD,WAAW,QAAQ,QAAQ,SAAS,UAAU,OAAO;;;;;;;EAarD,UAAU,QAAQ,QAAQ;;;;;;;;EAc1B,SAAS,QAAQ,QAAQ,UAAU,UAAU;;;;;;;EAqB7C,mBAAmB,WAAW,uBAAuB,UAAU;;;;;EAQ/D;;;;;;;EAeA,KAAK;;;;;;EA+BL,YAAY;;EAiBZ;;;;;;;;;;;;;;;;cC/kBW;;EAEX,SAAS;;;;;;;;;;UCeM;;WAEN,OAAO;;;;;;;;;;;;;;;qBAgBL;;;;;;;;;;EAyBX,YAAY,MAAM,eAAe,UAAU;;;;;;MAUvC,WAAW;;;;;;MASX,QAAQ,OAAO;;;;;;MAUf,UAAU;;;;;;;;;;;EAcd,QACE,QAAQ,UACR,WAAW,UACX,sBACA,UAAU,iBACT;;;;;;;;;;;EA8BH,WACE,QAAQ,UACR,WAAW,UACX,sBACA,UAAU,0BACA;;;;;;;;;;EA0CZ,cAAc,QAAQ,UAAU,gBAAgB,UAAU,0BAA0B;;;;;;;;;;;EAcpF,WAAW,QAAQ,UAAU,MAAM,UAAU,mBAAsB,UAAU,0BAA0B;;;;;;;;;;;;EAqBvG,UACE,QAAQ,UACR,gBACA,WAAW,UACX,qBACA,UAAU,iBACT;;;;;;;;;;;;;;UCrMY;;;;;;;WAON,mBAAmB;;;YAIlB;;aAEC,WAAW;;;;;;;;;;;;;;;;qBAiBX,YAAY,UAAU,qBAAqB;;;;;;;;;;;;;;;;;;;;;;;;;wBCrCxC,mBAAmB,SAAS,eAAe"}
|
package/dist/index.js
CHANGED
|
@@ -16,24 +16,9 @@ const Physics2DHostKey = createServiceKey("@ignifx/physics-2d:host");
|
|
|
16
16
|
//#endregion
|
|
17
17
|
//#region src/components/rigidbody.ts
|
|
18
18
|
/**
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
* ## Decisions §8 leaves open
|
|
23
|
-
*
|
|
24
|
-
* - **`mass` is exact, and `0` means "weigh the colliders".** Rapier derives a body's mass and
|
|
25
|
-
* angular inertia from each collider's *density* and area (`geometry/collider.d.ts`,
|
|
26
|
-
* `setDensity`), and `setAdditionalMass` adds mass without inertia. So a non-zero `mass` is
|
|
27
|
-
* applied by scaling every collider's density until the body weighs exactly that, which keeps the
|
|
28
|
-
* inertia distribution the shapes imply; `mass: 0` leaves Rapier's own density of 1 kg/m² alone.
|
|
29
|
-
* - **`freezeRotation` is one boolean, not three.** A 2D body has one rotational degree of freedom
|
|
30
|
-
* (`dynamics/rigid_body.d.ts`, `lockRotations`).
|
|
31
|
-
* - **Damping is per body here, unlike 3D.** Havok has no per-body damping; Rapier does
|
|
32
|
-
* (`setLinearDamping`, `setAngularDamping`), so `linearDamping` and `angularDamping` are real
|
|
33
|
-
* fields rather than upstream requests.
|
|
34
|
-
* - **`collisionEvents` is a three-valued enum**, exactly as in `@ignifx/physics`: a schema field
|
|
35
|
-
* has one kind (ADR-0004), and `"auto"` recomputes from `ctx.entityImplements` whenever a
|
|
36
|
-
* component joins or leaves the entity.
|
|
19
|
+
* A nonzero mass rescales collider densities while preserving their inertia distribution.
|
|
20
|
+
* Zero keeps the default density of 1 kg/m². Rotation has one freeze flag; damping is per body.
|
|
21
|
+
* Automatic collision events follow callbacks as components join or leave the entity.
|
|
37
22
|
*/
|
|
38
23
|
/**
|
|
39
24
|
* How a 2D body moves.
|
|
@@ -233,27 +218,9 @@ function rigidbody2DSchema() {
|
|
|
233
218
|
//#endregion
|
|
234
219
|
//#region src/components/character-controller.ts
|
|
235
220
|
/**
|
|
236
|
-
*
|
|
237
|
-
*
|
|
238
|
-
*
|
|
239
|
-
* ## How it is driven
|
|
240
|
-
*
|
|
241
|
-
* Call {@link CharacterController2D.move} from `fixedUpdate`. The requested displacements of one
|
|
242
|
-
* step are summed and handed to Rapier's `computeColliderMovement` by the step system, so two
|
|
243
|
-
* scripts moving the same character compose instead of fighting. Gravity is **not** applied by this
|
|
244
|
-
* component: a bare controller is purely kinematic, exactly as the 3D one is, and the toolkit
|
|
245
|
-
* controller scripts integrate gravity themselves.
|
|
246
|
-
*
|
|
247
|
-
* After the step the controller owns the entity's world X and Y. Z and `rotation2D` stay
|
|
248
|
-
* user-controlled.
|
|
249
|
-
*
|
|
250
|
-
* ## The one addition to §8: `shape`
|
|
251
|
-
*
|
|
252
|
-
* §8 describes the controller as a capsule. Measured against `@dimforge/rapier2d-compat@0.20.0`
|
|
253
|
-
* (spike S6.2), Rapier's autostep clears a 0.3 m step with a **box** character and refuses anything
|
|
254
|
-
* above about 0.15 m with a capsule of radius 0.2 — so `stepOffset` is only usable with a box.
|
|
255
|
-
* {@link CharacterController2D.shape} therefore chooses between the two, defaulting to `"capsule"`
|
|
256
|
-
* as §8 says, and the limitation is documented in the skill and in ADR-0006's validation section.
|
|
221
|
+
* Sum `move` displacements during `fixedUpdate`; game scripts supply gravity.
|
|
222
|
+
* After the step the controller owns world X/Y, while Z and rotation remain user-controlled.
|
|
223
|
+
* Box shapes support taller autosteps than capsules; see ADR-0006's validation results.
|
|
257
224
|
*/
|
|
258
225
|
/**
|
|
259
226
|
* The collision shape a 2D character controller uses.
|
|
@@ -349,6 +316,10 @@ var CharacterController2D = class CharacterController2D extends Component {
|
|
|
349
316
|
/**
|
|
350
317
|
* Emitted once per obstacle the character hit during a step.
|
|
351
318
|
*
|
|
319
|
+
* @remarks
|
|
320
|
+
* Trigger colliders are not obstacles — the character walks straight through them — so a sensor
|
|
321
|
+
* never appears here. Listen for `onTriggerEnter`/`onTriggerExit` on the entity's scripts instead.
|
|
322
|
+
*
|
|
352
323
|
* @returns The signal, created on first access.
|
|
353
324
|
*/
|
|
354
325
|
get onCollided() {
|
|
@@ -521,20 +492,9 @@ function physics2DError(code, message, options) {
|
|
|
521
492
|
//#endregion
|
|
522
493
|
//#region src/material.ts
|
|
523
494
|
/**
|
|
524
|
-
*
|
|
525
|
-
*
|
|
526
|
-
*
|
|
527
|
-
*
|
|
528
|
-
* `11-2d-toolkit.md` §8 lists a `PhysicsMaterial2D`, and `09-physics.md` §2.4 already defines
|
|
529
|
-
* `{ "format": "ignifx.physicsmaterial", "friction": …, "staticFriction": …, "restitution": … }`.
|
|
530
|
-
* Inventing a second document for the same two numbers would give artists two files to keep in
|
|
531
|
-
* step, so this package reads **the same format and the same `.physicsmaterial.json` extension**,
|
|
532
|
-
* ignoring `staticFriction` (Rapier 2D has one friction coefficient, `geometry/collider.d.ts`).
|
|
533
|
-
* `@ignifx/physics` and `@ignifx/physics-2d` never run in one world — `IGX-1101` — so the two
|
|
534
|
-
* loaders never collide.
|
|
535
|
-
*
|
|
536
|
-
* The 2D-only knobs, how two surfaces' friction and restitution are **combined**, are fields on the
|
|
537
|
-
* collider rather than on the asset, so the document stays byte-identical to the 3D one.
|
|
495
|
+
* Read the shared `.physicsmaterial.json` format, ignoring `staticFriction` because Rapier has one
|
|
496
|
+
* friction coefficient. Combine rules stay on colliders. Only one physics extension runs per world,
|
|
497
|
+
* so the 2D and 3D loaders do not compete.
|
|
538
498
|
*/
|
|
539
499
|
/**
|
|
540
500
|
* The asset type name `.physicsmaterial.json` addresses resolve to.
|
|
@@ -681,29 +641,9 @@ function numberAt(object, key, fallback) {
|
|
|
681
641
|
//#endregion
|
|
682
642
|
//#region src/components/collider.ts
|
|
683
643
|
/**
|
|
684
|
-
*
|
|
685
|
-
*
|
|
686
|
-
*
|
|
687
|
-
*
|
|
688
|
-
* ## Corrections to §8
|
|
689
|
-
*
|
|
690
|
-
* - **The material is two fields, not a union.** A schema field has exactly one kind (ADR-0004), so
|
|
691
|
-
* {@link Collider2D.material} (an asset reference) and {@link Collider2D.inlineMaterial} (a
|
|
692
|
-
* record) are separate; the asset wins, the inline record is the fallback, and
|
|
693
|
-
* `physics2d.defaultMaterial` is the last resort. This is the same split `@ignifx/physics` makes.
|
|
694
|
-
* - **Combine rules are collider fields.** Rapier combines two surfaces' friction and restitution
|
|
695
|
-
* with a per-collider rule (`dynamics/coefficient_combine_rule.d.ts`); the 3D
|
|
696
|
-
* `ignifx.physicsmaterial` document has no such field, so the knob lives here rather than
|
|
697
|
-
* forking the asset format.
|
|
698
|
-
* - **A collider may stand for several Rapier colliders.** `TilemapCollider2D` produces one polygon
|
|
699
|
-
* per merged tile run, so {@link Collider2D.collectShapes} fills a list rather than returning one
|
|
700
|
-
* shape.
|
|
701
|
-
*
|
|
702
|
-
* ## Sizes and scale
|
|
703
|
-
*
|
|
704
|
-
* Sizes are authored in local metres and multiplied by the entity's lossy scale when the shapes are
|
|
705
|
-
* built. Rapier cannot rescale a built shape, so a later scale change needs
|
|
706
|
-
* {@link Collider2D.rebuild}.
|
|
644
|
+
* Build shapes from local metres multiplied by entity scale; call `rebuild` after changing scale.
|
|
645
|
+
* Material assets take precedence over inline values, then project defaults. Friction and restitution
|
|
646
|
+
* combine rules belong to each collider. A tilemap collider can produce several backend shapes.
|
|
707
647
|
*/
|
|
708
648
|
/**
|
|
709
649
|
* How two surfaces' coefficients are combined when they touch, mirroring Rapier's
|
|
@@ -789,19 +729,9 @@ var Collider2D = class extends Component {
|
|
|
789
729
|
//#endregion
|
|
790
730
|
//#region src/components/colliders.ts
|
|
791
731
|
/**
|
|
792
|
-
*
|
|
793
|
-
*
|
|
794
|
-
*
|
|
795
|
-
* ## The two point-list colliders
|
|
796
|
-
*
|
|
797
|
-
* - `PolygonCollider2D` builds a **convex** shape (`ColliderDesc.convexHull`). Rapier 2D also ships
|
|
798
|
-
* `convexDecomposition`, which needs a triangle mesh with an index buffer and runs V-HACD — far
|
|
799
|
-
* too heavy for a component field. Concave outlines are expressed as several
|
|
800
|
-
* `PolygonCollider2D`s on one entity, which is what a compound body is for, or as an
|
|
801
|
-
* `EdgeCollider2D` when the outline is open.
|
|
802
|
-
* - `EdgeCollider2D` builds a `polyline`, the open chain a platformer's ground contour wants. A
|
|
803
|
-
* polyline is infinitely thin: a fast body can tunnel through it, and a character controller that
|
|
804
|
-
* starts *inside* one is not pushed out. Static level geometry is what it is for.
|
|
732
|
+
* Polygon colliders use convex hulls; represent concave geometry with multiple colliders.
|
|
733
|
+
* Edge colliders are thin, open polylines for static contours: fast bodies can tunnel through them,
|
|
734
|
+
* and bodies starting inside are not pushed out.
|
|
805
735
|
*/
|
|
806
736
|
/** A quarter turn in radians, which is how a horizontal capsule is expressed. */
|
|
807
737
|
const QUARTER_TURN = Math.PI / 2;
|
|
@@ -1124,25 +1054,9 @@ function degenerate(entity, component, requirement) {
|
|
|
1124
1054
|
//#endregion
|
|
1125
1055
|
//#region src/components/tilemap-collider.ts
|
|
1126
1056
|
/**
|
|
1127
|
-
*
|
|
1128
|
-
*
|
|
1129
|
-
*
|
|
1130
|
-
* ## How it reaches the data
|
|
1131
|
-
*
|
|
1132
|
-
* `@ignifx/2d` is an **optional** peer of `@ignifx/physics-2d` (a physics-only 2D game needs no
|
|
1133
|
-
* sprite toolkit), so this component never imports the toolkit as a value — only the
|
|
1134
|
-
* `TilemapCollisionData` type. A game, or `@ignifx/2d`'s own tilemap loader, assigns
|
|
1135
|
-
* {@link TilemapCollider2D.collisionData}; the runtime compares the data's `version` at the start of
|
|
1136
|
-
* every fixed step and rebuilds the shapes when it changes, which is the same moment
|
|
1137
|
-
* `Tilemap.onCollisionChanged` fires and needs no signal subscription across the package boundary.
|
|
1138
|
-
*
|
|
1139
|
-
* ## Why closed polylines rather than polygons
|
|
1140
|
-
*
|
|
1141
|
-
* A merged run of solid tiles is not convex in general — an L of tiles merges into an L — and
|
|
1142
|
-
* Rapier's polygon shape is a **convex hull** (`geometry/shape.d.ts`, `ConvexPolygon`), which would
|
|
1143
|
-
* silently fill the notch. A closed `polyline` reproduces the outline exactly and is the shape
|
|
1144
|
-
* static level geometry wants; the trade-off is that a polyline is infinitely thin, so a body that
|
|
1145
|
-
* starts inside the tilemap is not pushed out.
|
|
1057
|
+
* Read tile collision data through the optional 2D toolkit's type and rebuild when its version changes.
|
|
1058
|
+
* Closed polylines preserve concave tile outlines that convex hulls would fill in. They are thin
|
|
1059
|
+
* surfaces, so a body starting inside the map is not pushed out.
|
|
1146
1060
|
*/
|
|
1147
1061
|
/**
|
|
1148
1062
|
* The collision surface of a tilemap, as one static body's worth of Rapier shapes.
|
|
@@ -1286,21 +1200,9 @@ function segment(from, to, scale) {
|
|
|
1286
1200
|
//#endregion
|
|
1287
1201
|
//#region src/lite/rapier/module.ts
|
|
1288
1202
|
/**
|
|
1289
|
-
*
|
|
1290
|
-
*
|
|
1291
|
-
*
|
|
1292
|
-
* `src/lite/**` — hence `src/lite/rapier/`.
|
|
1293
|
-
*
|
|
1294
|
-
* ## Why `-compat`
|
|
1295
|
-
*
|
|
1296
|
-
* The `-compat` build inlines the WebAssembly module as base64 inside `rapier.mjs`, so it needs no
|
|
1297
|
-
* asset serving and works unchanged in Node and in the browser (spike S6.2, ADR-0006 Validation).
|
|
1298
|
-
* `init()` is what decodes and instantiates it; every other Rapier call before that throws.
|
|
1299
|
-
*
|
|
1300
|
-
* The import is static: `world.ts` and `service.ts` also import Rapier's classes statically, so a
|
|
1301
|
-
* dynamic import here alone would not move the 2 MB module out of an app's entry chunk (measured in
|
|
1302
|
-
* Phase 6). Splitting it needs the adapter to read every class off the loaded module instead, which
|
|
1303
|
-
* is the Phase 12 bundle work.
|
|
1203
|
+
* Initialise Rapier's embedded WebAssembly before making backend calls.
|
|
1204
|
+
* The compat build includes the binary, so both Node and browser apps need no separate asset.
|
|
1205
|
+
* Rapier classes are imported statically by the adapters; delaying `init` does not split that bundle.
|
|
1304
1206
|
*
|
|
1305
1207
|
* @internal
|
|
1306
1208
|
*/
|
|
@@ -2024,6 +1926,12 @@ var Physics2DHost = class {
|
|
|
2024
1926
|
//#endregion
|
|
2025
1927
|
//#region src/lite/rapier/character.ts
|
|
2026
1928
|
/**
|
|
1929
|
+
* The obstacle filter every controller move uses. Sensors describe regions, not geometry, so they
|
|
1930
|
+
* are excluded from collide-and-slide; the pair is still reported by the world step, which is where
|
|
1931
|
+
* `onTriggerEnter`/`onTriggerExit` come from.
|
|
1932
|
+
*/
|
|
1933
|
+
const OBSTACLE_FLAGS = QueryFilterFlags.EXCLUDE_SENSORS;
|
|
1934
|
+
/**
|
|
2027
1935
|
* Creates a character controller.
|
|
2028
1936
|
*
|
|
2029
1937
|
* @param world - The world.
|
|
@@ -2071,6 +1979,10 @@ function tuneController(controller, tuning) {
|
|
|
2071
1979
|
/**
|
|
2072
1980
|
* Runs one collide-and-slide move.
|
|
2073
1981
|
*
|
|
1982
|
+
* @remarks
|
|
1983
|
+
* Sensors are excluded from the obstacle set ({@link OBSTACLE_FLAGS}), so a character walks into and
|
|
1984
|
+
* out of a trigger volume at full speed and the world step raises the intersection events.
|
|
1985
|
+
*
|
|
2074
1986
|
* @param controller - The controller.
|
|
2075
1987
|
* @param collider - The character's own collider.
|
|
2076
1988
|
* @param desired - The displacement to attempt, in metres.
|
|
@@ -2085,11 +1997,11 @@ function computeControllerMovement(controller, collider, desired, groups, filter
|
|
|
2085
1997
|
if (filter === null) controller.computeColliderMovement(collider, {
|
|
2086
1998
|
x: desired.x,
|
|
2087
1999
|
y: desired.y
|
|
2088
|
-
},
|
|
2000
|
+
}, OBSTACLE_FLAGS, groups);
|
|
2089
2001
|
else controller.computeColliderMovement(collider, {
|
|
2090
2002
|
x: desired.x,
|
|
2091
2003
|
y: desired.y
|
|
2092
|
-
},
|
|
2004
|
+
}, OBSTACLE_FLAGS, groups, filter);
|
|
2093
2005
|
controller.computedMovement(out);
|
|
2094
2006
|
return controller.computedGrounded();
|
|
2095
2007
|
}
|
|
@@ -2430,7 +2342,7 @@ var Physics2DRuntime = class {
|
|
|
2430
2342
|
}
|
|
2431
2343
|
/**
|
|
2432
2344
|
* Restores the authoritative pose of every interpolated body, undoing the display pose the
|
|
2433
|
-
* `
|
|
2345
|
+
* `Update` system wrote at the top of the previous frame's `Update` (`09-physics.md` §1).
|
|
2434
2346
|
*/
|
|
2435
2347
|
restorePoses() {
|
|
2436
2348
|
for (let index = 0; index < this.#records.length; index += 1) {
|
|
@@ -2497,8 +2409,9 @@ var Physics2DRuntime = class {
|
|
|
2497
2409
|
destroyWorld(this.#world);
|
|
2498
2410
|
}
|
|
2499
2411
|
/**
|
|
2500
|
-
* Publishes the frame's counters and zeroes the accumulators.
|
|
2501
|
-
*
|
|
2412
|
+
* Publishes the frame's counters and zeroes the accumulators. The top of `Update`, where the
|
|
2413
|
+
* display pose is written, is the first point in a frame at which every fixed step of that frame
|
|
2414
|
+
* is done.
|
|
2502
2415
|
*/
|
|
2503
2416
|
#publishCounters() {
|
|
2504
2417
|
this.#counters.set(this.#counters.index("bodies"), this.#records.length);
|
|
@@ -2951,6 +2864,10 @@ var Physics2DRuntime = class {
|
|
|
2951
2864
|
/**
|
|
2952
2865
|
* Reports every obstacle a controller hit and returns the most upward-facing normal.
|
|
2953
2866
|
*
|
|
2867
|
+
* @remarks
|
|
2868
|
+
* Sensors are filtered out of the move (`lite/rapier/character.ts`), so a trigger volume never
|
|
2869
|
+
* shows up here and never contributes a ground normal.
|
|
2870
|
+
*
|
|
2954
2871
|
* @param rapierController - Rapier's controller.
|
|
2955
2872
|
* @param component - The component.
|
|
2956
2873
|
* @returns The ground normal, or `null` when nothing was hit.
|
|
@@ -3250,26 +3167,43 @@ function now() {
|
|
|
3250
3167
|
//#endregion
|
|
3251
3168
|
//#region src/runtime/systems.ts
|
|
3252
3169
|
/**
|
|
3253
|
-
* The three systems `docs/architecture/01-lifecycle-and-time.md` §3
|
|
3254
|
-
*
|
|
3255
|
-
*
|
|
3170
|
+
* The three systems `docs/architecture/01-lifecycle-and-time.md` §3 and `09-physics.md` §1 put
|
|
3171
|
+
* physics in the frame with — identical in 2D, except that Rapier is stepped directly and there is
|
|
3172
|
+
* no Babylon Lite simulation scene:
|
|
3256
3173
|
*
|
|
3257
3174
|
* ```
|
|
3258
3175
|
* Systems(FixedUpdate, -100) restore the authoritative pose of interpolated bodies
|
|
3259
3176
|
* scripts.fixedUpdate(dt) forces, velocities, CharacterController2D.move()
|
|
3260
3177
|
* Systems(FixedUpdate, 100) step Rapier, snapshot poses, dispatch collision and trigger events
|
|
3261
|
-
*
|
|
3178
|
+
* ── the fixed loop ends; time.fixedStepAlpha is final ──
|
|
3179
|
+
* Systems(Update, -900) write lerp(prev, cur, time.fixedStepAlpha)
|
|
3180
|
+
* scripts.update / lateUpdate, animation, rendering — all read the display pose
|
|
3262
3181
|
* ```
|
|
3263
3182
|
*
|
|
3183
|
+
* **Why the display pose is written at the top of `Update` and not in `PreRender`** (2026-09-08).
|
|
3184
|
+
* It used to be written in `PreRender`, after `lateUpdate`. `Camera2DFollow` and every hand-written
|
|
3185
|
+
* follow camera run in `update`/`lateUpdate`, so they framed the character where the last fixed step
|
|
3186
|
+
* left it while the renderer drew it at `lerp(prev, cur, alpha)`: up to one fixed step of relative
|
|
3187
|
+
* judder every frame, which a pixel-perfect camera turns into a visibly shaking sprite because the
|
|
3188
|
+
* two poses are quantised independently. Writing the pose before `scripts.update` means scripts,
|
|
3189
|
+
* animation, camera rigs and the render sync all see the pose the frame presents, which is Unity's
|
|
3190
|
+
* model. The simulation is unaffected: the restore system at `FixedUpdate −100` runs before
|
|
3191
|
+
* `scripts.fixedUpdate`, so `fixedUpdate` and Rapier still see the authoritative pose.
|
|
3192
|
+
*
|
|
3264
3193
|
* Everything here is `@internal`.
|
|
3265
3194
|
*/
|
|
3266
3195
|
/** The `FixedUpdate` order the restore system runs at. */
|
|
3267
3196
|
const PHYSICS_2D_RESTORE_ORDER = -100;
|
|
3268
|
-
/** The `PreRender` order the interpolation system runs at. */
|
|
3269
|
-
const PHYSICS_2D_INTERPOLATE_ORDER = -500;
|
|
3270
3197
|
/**
|
|
3271
|
-
*
|
|
3272
|
-
*
|
|
3198
|
+
* The `Update` order the interpolation system runs at. Negative, so it lands in
|
|
3199
|
+
* `Systems(Update, order < 0)` — after the fixed loop and lifecycle flush B, before
|
|
3200
|
+
* `scripts.update` — and low enough that a game's own `Update` systems can still be ordered in
|
|
3201
|
+
* front of it if they must see the authoritative pose.
|
|
3202
|
+
*/
|
|
3203
|
+
const PHYSICS_2D_INTERPOLATE_ORDER = -900;
|
|
3204
|
+
/**
|
|
3205
|
+
* Undoes the display pose the interpolation system wrote at the top of the previous `Update`, so
|
|
3206
|
+
* `fixedUpdate` and Rapier both see the authoritative pose.
|
|
3273
3207
|
*
|
|
3274
3208
|
* @internal
|
|
3275
3209
|
*/
|
|
@@ -3745,7 +3679,7 @@ const physics2d = defineExtension((options = {}) => {
|
|
|
3745
3679
|
order: 100
|
|
3746
3680
|
});
|
|
3747
3681
|
ctx.registerSystem(new Physics2DInterpolationSystem(host), {
|
|
3748
|
-
phase: Phase.
|
|
3682
|
+
phase: Phase.Update,
|
|
3749
3683
|
order: PHYSICS_2D_INTERPOLATE_ORDER
|
|
3750
3684
|
});
|
|
3751
3685
|
},
|