reze-engine 0.15.0 → 0.16.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.
Files changed (93) hide show
  1. package/README.md +3 -3
  2. package/dist/camera.d.ts +2 -0
  3. package/dist/camera.d.ts.map +1 -1
  4. package/dist/camera.js +8 -3
  5. package/dist/engine.d.ts +19 -4
  6. package/dist/engine.d.ts.map +1 -1
  7. package/dist/engine.js +294 -27
  8. package/dist/gpu-profile.d.ts +19 -0
  9. package/dist/gpu-profile.d.ts.map +1 -0
  10. package/dist/gpu-profile.js +120 -0
  11. package/dist/ik-solver.d.ts.map +1 -1
  12. package/dist/ik-solver.js +13 -7
  13. package/dist/math.d.ts +2 -0
  14. package/dist/math.d.ts.map +1 -1
  15. package/dist/math.js +65 -46
  16. package/dist/model.d.ts +28 -1
  17. package/dist/model.d.ts.map +1 -1
  18. package/dist/model.js +259 -75
  19. package/dist/physics/body.d.ts +2 -0
  20. package/dist/physics/body.d.ts.map +1 -1
  21. package/dist/physics/body.js +30 -0
  22. package/dist/physics/constraint.d.ts +13 -0
  23. package/dist/physics/constraint.d.ts.map +1 -1
  24. package/dist/physics/constraint.js +13 -0
  25. package/dist/physics/contact.d.ts +28 -0
  26. package/dist/physics/contact.d.ts.map +1 -1
  27. package/dist/physics/contact.js +27 -32
  28. package/dist/physics/physics.d.ts +3 -0
  29. package/dist/physics/physics.d.ts.map +1 -1
  30. package/dist/physics/physics.js +57 -8
  31. package/dist/physics/profile.d.ts +18 -0
  32. package/dist/physics/profile.d.ts.map +1 -0
  33. package/dist/physics/profile.js +44 -0
  34. package/dist/physics/solver.d.ts.map +1 -1
  35. package/dist/physics/solver.js +436 -293
  36. package/dist/pmx-loader.d.ts.map +1 -1
  37. package/dist/pmx-loader.js +2 -30
  38. package/dist/shaders/materials/body.d.ts +1 -1
  39. package/dist/shaders/materials/body.d.ts.map +1 -1
  40. package/dist/shaders/materials/body.js +3 -1
  41. package/dist/shaders/materials/cloth_rough.d.ts +1 -1
  42. package/dist/shaders/materials/cloth_rough.d.ts.map +1 -1
  43. package/dist/shaders/materials/cloth_rough.js +3 -1
  44. package/dist/shaders/materials/cloth_smooth.d.ts +1 -1
  45. package/dist/shaders/materials/cloth_smooth.d.ts.map +1 -1
  46. package/dist/shaders/materials/common.d.ts +2 -2
  47. package/dist/shaders/materials/common.d.ts.map +1 -1
  48. package/dist/shaders/materials/common.js +8 -0
  49. package/dist/shaders/materials/default.d.ts +1 -1
  50. package/dist/shaders/materials/default.d.ts.map +1 -1
  51. package/dist/shaders/materials/eye.d.ts +1 -1
  52. package/dist/shaders/materials/eye.d.ts.map +1 -1
  53. package/dist/shaders/materials/face.d.ts +1 -1
  54. package/dist/shaders/materials/face.d.ts.map +1 -1
  55. package/dist/shaders/materials/face.js +4 -1
  56. package/dist/shaders/materials/hair.d.ts +1 -1
  57. package/dist/shaders/materials/hair.d.ts.map +1 -1
  58. package/dist/shaders/materials/metal.d.ts +1 -1
  59. package/dist/shaders/materials/metal.d.ts.map +1 -1
  60. package/dist/shaders/materials/stockings.d.ts +1 -1
  61. package/dist/shaders/materials/stockings.d.ts.map +1 -1
  62. package/dist/shaders/materials/stockings.js +13 -9
  63. package/dist/shaders/passes/composite.d.ts +1 -1
  64. package/dist/shaders/passes/composite.d.ts.map +1 -1
  65. package/dist/shaders/passes/composite.js +18 -12
  66. package/dist/shaders/passes/morph.d.ts +2 -0
  67. package/dist/shaders/passes/morph.d.ts.map +1 -0
  68. package/dist/shaders/passes/morph.js +49 -0
  69. package/package.json +1 -1
  70. package/src/camera.ts +9 -3
  71. package/src/engine.ts +336 -29
  72. package/src/ik-solver.ts +14 -7
  73. package/src/math.ts +41 -51
  74. package/src/model.ts +288 -91
  75. package/src/physics/body.ts +29 -0
  76. package/src/physics/constraint.ts +30 -0
  77. package/src/physics/contact.ts +43 -30
  78. package/src/physics/physics.ts +58 -17
  79. package/src/physics/solver.ts +431 -284
  80. package/src/pmx-loader.ts +2 -35
  81. package/src/shaders/materials/body.ts +3 -1
  82. package/src/shaders/materials/cloth_rough.ts +3 -1
  83. package/src/shaders/materials/common.ts +8 -0
  84. package/src/shaders/materials/face.ts +4 -1
  85. package/src/shaders/materials/stockings.ts +13 -9
  86. package/src/shaders/passes/composite.ts +18 -12
  87. package/src/shaders/passes/morph.ts +50 -0
  88. package/dist/physics-debug.d.ts +0 -30
  89. package/dist/physics-debug.d.ts.map +0 -1
  90. package/dist/physics-debug.js +0 -526
  91. package/dist/shaders/passes/physics-debug.d.ts +0 -2
  92. package/dist/shaders/passes/physics-debug.d.ts.map +0 -1
  93. package/dist/shaders/passes/physics-debug.js +0 -69
@@ -14,23 +14,28 @@ import { RigidbodyShape } from "./types";
14
14
  export const CONTACT_MARGIN = 0.04;
15
15
  function makeContact() {
16
16
  return {
17
- bodyA: 0,
18
- bodyB: 0,
19
- rAx: 0,
20
- rAy: 0,
21
- rAz: 0,
22
- rBx: 0,
23
- rBy: 0,
24
- rBz: 0,
25
- nx: 0,
26
- ny: 0,
27
- nz: 0,
17
+ bodyA: 0, bodyB: 0,
18
+ rAx: 0, rAy: 0, rAz: 0,
19
+ rBx: 0, rBy: 0, rBz: 0,
20
+ nx: 0, ny: 0, nz: 0,
28
21
  depth: 0,
29
22
  friction: 0,
30
23
  restitution: 0,
31
24
  appliedNormalImpulse: 0,
32
25
  appliedFrictionImpulse1: 0,
33
26
  appliedFrictionImpulse2: 0,
27
+ cAxN: 0, cAyN: 0, cAzN: 0,
28
+ cBxN: 0, cByN: 0, cBzN: 0,
29
+ jacInvN: 0,
30
+ bounceVel: 0,
31
+ t1x: 0, t1y: 0, t1z: 0,
32
+ cAxT1: 0, cAyT1: 0, cAzT1: 0,
33
+ cBxT1: 0, cByT1: 0, cBzT1: 0,
34
+ jacInvT1: 0,
35
+ t2x: 0, t2y: 0, t2z: 0,
36
+ cAxT2: 0, cAyT2: 0, cAzT2: 0,
37
+ cBxT2: 0, cByT2: 0, cBzT2: 0,
38
+ jacInvT2: 0,
34
39
  };
35
40
  }
36
41
  // Pool of reusable Contact objects.
@@ -701,28 +706,18 @@ function flipLastNormal(pool) {
701
706
  c.ny = -c.ny;
702
707
  c.nz = -c.nz;
703
708
  }
704
- // O(N²) AABB pair test. For 30–100 PMX bodies that's <5000 checks per
705
- // step; SAP / dynamic AABB tree pay off above ~500 bodies. The pair filter
706
- // uses the AND form `(maskA & groupB) && (maskB & groupA)` — both sides
707
- // must agree, which is what PMX rigs are tuned against.
709
+ // Iterate the prebuilt candidate-pair list and AABB-test each pair. The
710
+ // static-static and group/mask filters were applied once at construction
711
+ // see RigidBodyStore.getCollisionPairs. SAP / dynamic AABB tree pay off
712
+ // above ~500 bodies; below that this flat sweep wins on cache locality.
708
713
  export function findContacts(store, pool) {
709
714
  store.updateAabbs();
710
- const N = store.count;
711
- const invMass = store.invMass;
712
- const group = store.collisionGroup;
713
- const mask = store.willCollideMask;
714
- for (let i = 0; i < N; i++) {
715
- const gi = group[i];
716
- const mi = mask[i];
717
- const dynA = invMass[i] > 0;
718
- for (let j = i + 1; j < N; j++) {
719
- if (!dynA && invMass[j] === 0)
720
- continue; // both static — no resolution
721
- if ((mi & group[j]) === 0 || (mask[j] & gi) === 0)
722
- continue;
723
- if (!aabbOverlap(store, i, j))
724
- continue;
725
- generateContacts(store, i, j, pool);
726
- }
715
+ const pairs = store.getCollisionPairs();
716
+ for (let p = 0; p < pairs.length; p += 2) {
717
+ const i = pairs[p];
718
+ const j = pairs[p + 1];
719
+ if (!aabbOverlap(store, i, j))
720
+ continue;
721
+ generateContacts(store, i, j, pool);
727
722
  }
728
723
  }
@@ -12,7 +12,10 @@ export declare class RezePhysics {
12
12
  private timeAccum;
13
13
  private readonly fixedTimeStep;
14
14
  private readonly maxSubSteps;
15
+ private prevPositions;
16
+ private prevOrientations;
15
17
  constructor(rigidbodies: Rigidbody[], joints?: Joint[]);
18
+ private savePrevState;
16
19
  setGravity(gravity: Vec3): void;
17
20
  getGravity(): Vec3;
18
21
  getRigidbodies(): Rigidbody[];
@@ -1 +1 @@
1
- {"version":3,"file":"physics.d.ts","sourceRoot":"","sources":["../../src/physics/physics.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,SAAS,CAAA;AAC1C,OAAO,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,SAAS,CAAA;AAE/C,OAAO,EAAE,cAAc,EAAE,MAAM,QAAQ,CAAA;AAYvC,qBAAa,WAAW;IACtB,OAAO,CAAC,WAAW,CAAa;IAChC,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,KAAK,CAAgB;IAC7B,OAAO,CAAC,KAAK,CAAO;IACpB,OAAO,CAAC,WAAW,CAA0B;IAC7C,OAAO,CAAC,QAAQ,CAAa;IAC7B,OAAO,CAAC,UAAU,CAAO;IAGzB,OAAO,CAAC,SAAS,CAAI;IACrB,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAS;IACvC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAK;gBAErB,WAAW,EAAE,SAAS,EAAE,EAAE,MAAM,GAAE,KAAK,EAAO;IAS1D,UAAU,CAAC,OAAO,EAAE,IAAI,GAAG,IAAI;IAG/B,UAAU,IAAI,IAAI;IAGlB,cAAc,IAAI,SAAS,EAAE;IAG7B,SAAS,IAAI,KAAK,EAAE;IAGpB,QAAQ,IAAI,cAAc;IAI1B,sBAAsB,IAAI,KAAK,CAAC;QAAE,QAAQ,EAAE,IAAI,CAAC;QAAC,QAAQ,EAAE,IAAI,CAAA;KAAE,CAAC;IAiBnE,KAAK,CAAC,iBAAiB,EAAE,IAAI,EAAE,GAAG,IAAI;IAKtC,IAAI,CAAC,EAAE,EAAE,MAAM,EAAE,iBAAiB,EAAE,IAAI,EAAE,EAAE,uBAAuB,EAAE,YAAY,GAAG,IAAI;IA6BxF,OAAO,CAAC,iBAAiB;IAuCzB,OAAO,CAAC,aAAa;IA6ErB,OAAO,CAAC,oBAAoB;CAiC7B"}
1
+ {"version":3,"file":"physics.d.ts","sourceRoot":"","sources":["../../src/physics/physics.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,SAAS,CAAA;AAC1C,OAAO,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,SAAS,CAAA;AAE/C,OAAO,EAAE,cAAc,EAAE,MAAM,QAAQ,CAAA;AAYvC,qBAAa,WAAW;IACtB,OAAO,CAAC,WAAW,CAAa;IAChC,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,KAAK,CAAgB;IAC7B,OAAO,CAAC,KAAK,CAAO;IACpB,OAAO,CAAC,WAAW,CAA0B;IAC7C,OAAO,CAAC,QAAQ,CAAa;IAC7B,OAAO,CAAC,UAAU,CAAO;IACzB,OAAO,CAAC,SAAS,CAAI;IACrB,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAS;IACvC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAI;IAMhC,OAAO,CAAC,aAAa,CAAc;IACnC,OAAO,CAAC,gBAAgB,CAAc;gBAE1B,WAAW,EAAE,SAAS,EAAE,EAAE,MAAM,GAAE,KAAK,EAAO;IAY1D,OAAO,CAAC,aAAa;IAKrB,UAAU,CAAC,OAAO,EAAE,IAAI,GAAG,IAAI;IAG/B,UAAU,IAAI,IAAI;IAGlB,cAAc,IAAI,SAAS,EAAE;IAG7B,SAAS,IAAI,KAAK,EAAE;IAGpB,QAAQ,IAAI,cAAc;IAI1B,sBAAsB,IAAI,KAAK,CAAC;QAAE,QAAQ,EAAE,IAAI,CAAC;QAAC,QAAQ,EAAE,IAAI,CAAA;KAAE,CAAC;IAiBnE,KAAK,CAAC,iBAAiB,EAAE,IAAI,EAAE,GAAG,IAAI;IAOtC,IAAI,CAAC,EAAE,EAAE,MAAM,EAAE,iBAAiB,EAAE,IAAI,EAAE,EAAE,uBAAuB,EAAE,YAAY,GAAG,IAAI;IAkCxF,OAAO,CAAC,iBAAiB;IAuCzB,OAAO,CAAC,aAAa;IA+ErB,OAAO,CAAC,oBAAoB;CAoD7B"}
@@ -13,17 +13,22 @@ const _scratchQuat = new Quat(0, 0, 0, 1);
13
13
  export class RezePhysics {
14
14
  constructor(rigidbodies, joints = []) {
15
15
  this.firstFrame = true;
16
- // Fixed-timestep accumulator: physics runs at 75 Hz regardless of render
17
- // rate, so spring impulse, damping, and integration are deterministic.
18
16
  this.timeAccum = 0;
19
- this.fixedTimeStep = 1 / 75;
20
- this.maxSubSteps = 10;
17
+ this.fixedTimeStep = 1 / 60;
18
+ this.maxSubSteps = 6;
21
19
  this.rigidbodies = rigidbodies;
22
20
  this.joints = joints;
23
21
  this.store = new RigidBodyStore(rigidbodies);
24
22
  this.world = new World(new Vec3(0, -98, 0));
25
23
  this.constraints = buildConstraints(rigidbodies, joints);
26
24
  this.contacts = new ContactPool();
25
+ this.prevPositions = new Float32Array(this.store.count * 3);
26
+ this.prevOrientations = new Float32Array(this.store.count * 4);
27
+ }
28
+ // Snapshot the current body pose as the interpolation "previous" state.
29
+ savePrevState() {
30
+ this.prevPositions.set(this.store.positions);
31
+ this.prevOrientations.set(this.store.orientations);
27
32
  }
28
33
  setGravity(gravity) {
29
34
  this.world.setGravity(gravity);
@@ -60,6 +65,8 @@ export class RezePhysics {
60
65
  if (this.firstFrame)
61
66
  return;
62
67
  this.snapBodiesToBones(boneWorldMatrices);
68
+ this.savePrevState(); // prev == curr after a snap, so no interpolation jump
69
+ this.timeAccum = 0;
63
70
  }
64
71
  step(dt, boneWorldMatrices, boneInverseBindMatrices) {
65
72
  if (this.firstFrame) {
@@ -67,6 +74,7 @@ export class RezePhysics {
67
74
  // Start at current bone pose, not the PMX bind pose, so animations
68
75
  // that skip frame 0 don't pop bodies on first step.
69
76
  this.snapBodiesToBones(boneWorldMatrices);
77
+ this.savePrevState();
70
78
  this.firstFrame = false;
71
79
  }
72
80
  // Sync once per render frame; kinematic targets don't change between
@@ -74,17 +82,21 @@ export class RezePhysics {
74
82
  // bone-pose delta so joints feel the kinematic motion.
75
83
  this.syncFromBones(boneWorldMatrices, dt);
76
84
  // Fixed-timestep substeps. The maxSubSteps cap prevents runaway after
77
- // a long stall (tab backgrounded, etc.).
85
+ // a long stall (tab backgrounded, etc.). Snapshot the pose before each step so
86
+ // after the loop prevState is one substep behind the live (current) state.
78
87
  this.timeAccum += dt;
79
88
  let sub = 0;
80
89
  while (this.timeAccum >= this.fixedTimeStep && sub < this.maxSubSteps) {
90
+ this.savePrevState();
81
91
  this.world.step(this.store, this.constraints, this.contacts, this.fixedTimeStep);
82
92
  this.timeAccum -= this.fixedTimeStep;
83
93
  sub++;
84
94
  }
85
95
  if (sub === this.maxSubSteps)
86
96
  this.timeAccum = 0;
87
- this.applyDynamicsToBones(boneWorldMatrices);
97
+ // Fraction into the next (not-yet-taken) step; always in [0, 1).
98
+ const alpha = this.fixedTimeStep > 0 ? this.timeAccum / this.fixedTimeStep : 0;
99
+ this.applyDynamicsToBones(boneWorldMatrices, alpha);
88
100
  }
89
101
  // Snap all bone-bound bodies to boneWorld × bodyOffset, zero velocities.
90
102
  snapBodiesToBones(boneWorldMatrices) {
@@ -185,13 +197,18 @@ export class RezePhysics {
185
197
  }
186
198
  // Dynamic bodies write their transform back to the bone matrix:
187
199
  // boneWorld = bodyWorld × bodyOffsetInverse.
188
- applyDynamicsToBones(boneWorldMatrices) {
200
+ // The body pose is the render-interpolated pose between the previous and current
201
+ // substep states (alpha = fraction into the next step), which removes fixed-step judder.
202
+ applyDynamicsToBones(boneWorldMatrices, alpha) {
189
203
  const N = this.store.count;
190
204
  const inv = this.store.bodyOffsetInverse;
191
205
  const positions = this.store.positions;
192
206
  const orientations = this.store.orientations;
207
+ const prevPos = this.prevPositions;
208
+ const prevOri = this.prevOrientations;
193
209
  const types = this.store.type;
194
210
  const boneIdx = this.store.boneIndex;
211
+ const oneMinus = 1 - alpha;
195
212
  for (let i = 0; i < N; i++) {
196
213
  if (types[i] !== RigidbodyType.Dynamic)
197
214
  continue;
@@ -200,7 +217,39 @@ export class RezePhysics {
200
217
  continue;
201
218
  const i3 = i * 3;
202
219
  const i4 = i * 4;
203
- Mat4.fromPositionRotationInto(positions[i3 + 0], positions[i3 + 1], positions[i3 + 2], orientations[i4 + 0], orientations[i4 + 1], orientations[i4 + 2], orientations[i4 + 3], _bodyMat);
220
+ // Position: straight lerp.
221
+ const px = prevPos[i3 + 0] * oneMinus + positions[i3 + 0] * alpha;
222
+ const py = prevPos[i3 + 1] * oneMinus + positions[i3 + 1] * alpha;
223
+ const pz = prevPos[i3 + 2] * oneMinus + positions[i3 + 2] * alpha;
224
+ // Orientation: shortest-arc nlerp (bodies rotate little per fixed step, so nlerp
225
+ // tracks slerp closely and avoids the trig).
226
+ const ax = prevOri[i4 + 0], ay = prevOri[i4 + 1], az = prevOri[i4 + 2], aw = prevOri[i4 + 3];
227
+ let bx = orientations[i4 + 0], by = orientations[i4 + 1], bz = orientations[i4 + 2], bw = orientations[i4 + 3];
228
+ if (ax * bx + ay * by + az * bz + aw * bw < 0) {
229
+ bx = -bx;
230
+ by = -by;
231
+ bz = -bz;
232
+ bw = -bw;
233
+ }
234
+ let qx = ax * oneMinus + bx * alpha;
235
+ let qy = ay * oneMinus + by * alpha;
236
+ let qz = az * oneMinus + bz * alpha;
237
+ let qw = aw * oneMinus + bw * alpha;
238
+ const ql = Math.sqrt(qx * qx + qy * qy + qz * qz + qw * qw);
239
+ if (ql > 0) {
240
+ const invL = 1 / ql;
241
+ qx *= invL;
242
+ qy *= invL;
243
+ qz *= invL;
244
+ qw *= invL;
245
+ }
246
+ else {
247
+ qx = 0;
248
+ qy = 0;
249
+ qz = 0;
250
+ qw = 1;
251
+ }
252
+ Mat4.fromPositionRotationInto(px, py, pz, qx, qy, qz, qw, _bodyMat);
204
253
  Mat4.multiplyArrays(_bodyMat, 0, inv, i * 16, _boneMat, 0);
205
254
  // Sanity gate against NaN / extreme values — silently drop the update.
206
255
  if (Number.isFinite(_boneMat[0]) && Math.abs(_boneMat[0]) < 1e6) {
@@ -0,0 +1,18 @@
1
+ declare class PhysicsProfiler {
2
+ enabled: boolean;
3
+ private sums;
4
+ private counts;
5
+ begin(): number;
6
+ end(label: string, t0: number): void;
7
+ record(label: string, ms: number): void;
8
+ snapshot(): Record<string, {
9
+ avgMs: number;
10
+ calls: number;
11
+ totalMs: number;
12
+ }>;
13
+ reset(): void;
14
+ }
15
+ export declare const profiler: PhysicsProfiler;
16
+ export type ProfileSnapshot = ReturnType<PhysicsProfiler["snapshot"]>;
17
+ export {};
18
+ //# sourceMappingURL=profile.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"profile.d.ts","sourceRoot":"","sources":["../../src/physics/profile.ts"],"names":[],"mappings":"AAIA,cAAM,eAAe;IACnB,OAAO,UAAQ;IACf,OAAO,CAAC,IAAI,CAA8C;IAC1D,OAAO,CAAC,MAAM,CAA8C;IAE5D,KAAK,IAAI,MAAM;IAIf,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,IAAI;IASpC,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,IAAI;IAQvC,QAAQ,IAAI,MAAM,CAAC,MAAM,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;IAU7E,KAAK,IAAI,IAAI;CAId;AAED,eAAO,MAAM,QAAQ,iBAAwB,CAAA;AAC7C,MAAM,MAAM,eAAe,GAAG,UAAU,CAAC,eAAe,CAAC,UAAU,CAAC,CAAC,CAAA"}
@@ -0,0 +1,44 @@
1
+ // Per-phase wall-clock accumulator. Off by default — when `enabled` is false,
2
+ // begin()/end() short-circuit so production has zero overhead. Toggle via
3
+ // `RezePhysics.setProfileEnabled(true)` and read with `getProfile()`.
4
+ class PhysicsProfiler {
5
+ constructor() {
6
+ this.enabled = false;
7
+ this.sums = Object.create(null);
8
+ this.counts = Object.create(null);
9
+ }
10
+ begin() {
11
+ return this.enabled ? performance.now() : 0;
12
+ }
13
+ end(label, t0) {
14
+ if (!this.enabled)
15
+ return;
16
+ const dt = performance.now() - t0;
17
+ this.sums[label] = (this.sums[label] ?? 0) + dt;
18
+ this.counts[label] = (this.counts[label] ?? 0) + 1;
19
+ }
20
+ // Direct accumulator for durations not measured via begin()/end() — used by
21
+ // GPU timestamp readback, which arrives async after the frame has ended.
22
+ record(label, ms) {
23
+ if (!this.enabled)
24
+ return;
25
+ this.sums[label] = (this.sums[label] ?? 0) + ms;
26
+ this.counts[label] = (this.counts[label] ?? 0) + 1;
27
+ }
28
+ // Snapshot since last reset(). avgMs is per call; totalMs is the cumulative
29
+ // window cost — divide by elapsed wall time to get a "% of frame budget".
30
+ snapshot() {
31
+ const out = {};
32
+ for (const k in this.sums) {
33
+ const total = this.sums[k];
34
+ const calls = this.counts[k];
35
+ out[k] = { avgMs: calls > 0 ? total / calls : 0, calls, totalMs: total };
36
+ }
37
+ return out;
38
+ }
39
+ reset() {
40
+ this.sums = Object.create(null);
41
+ this.counts = Object.create(null);
42
+ }
43
+ }
44
+ export const profiler = new PhysicsProfiler();
@@ -1 +1 @@
1
- {"version":3,"file":"solver.d.ts","sourceRoot":"","sources":["../../src/physics/solver.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,QAAQ,CAAA;AAC5C,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,cAAc,CAAA;AAE1D,OAAO,KAAK,EAAW,WAAW,EAAE,MAAM,WAAW,CAAA;AAgBrD,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,cAAc,EACrB,WAAW,EAAE,sBAAsB,EAAE,EACrC,QAAQ,EAAE,WAAW,EACrB,EAAE,EAAE,MAAM,EACV,UAAU,EAAE,MAAM,GACjB,IAAI,CAyNN"}
1
+ {"version":3,"file":"solver.d.ts","sourceRoot":"","sources":["../../src/physics/solver.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,QAAQ,CAAA;AAC5C,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,cAAc,CAAA;AAE1D,OAAO,KAAK,EAAW,WAAW,EAAE,MAAM,WAAW,CAAA;AAWrD,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,cAAc,EACrB,WAAW,EAAE,sBAAsB,EAAE,EACrC,QAAQ,EAAE,WAAW,EACrB,EAAE,EAAE,MAAM,EACV,UAAU,EAAE,MAAM,GACjB,IAAI,CAyBN"}