reze-engine 0.15.0 → 0.15.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Reze Engine
2
2
 
3
- **Zero-runtime-dependency** WebGPU engine for real-time MMD/PMX rendering. Pure TypeScript — renderer, animation, IK, physics, all hand-written. The only `dependencies` entry is `@webgpu/types` and that's types-only; no JS at runtime.
3
+ **Zero-runtime-dependency** WebGPU engine for real-time MMD/PMX rendering. Renderer, animation, IK, and physics all in TypeScript.
4
4
 
5
5
  ![screenshot](./screenshot.png)
6
6
 
@@ -306,7 +306,7 @@ Note the asymmetry: rotation uses `rotateBones({ name, q }, 0)` (the tween-based
306
306
 
307
307
  ## Physics
308
308
 
309
- Hand-written sequential-impulse rigid-body solver, no external physics dependency. Targets PMX rigs (sphere / box / capsule colliders, 6DOF spring joints) at quality comparable to Bullet's defaults but in ~1.5k lines of TypeScript.
309
+ In-house sequential-impulse rigid-body solver, no external physics dependency. Targets PMX rigs (sphere / box / capsule colliders, 6DOF spring joints) at quality comparable to Bullet's defaults in ~1.5k lines of TypeScript.
310
310
 
311
311
  ### Pipeline
312
312
 
@@ -317,7 +317,7 @@ predict velocities → broadphase + narrowphase → solve constraints (10 iter
317
317
  → split-impulse position correction → integrate transforms
318
318
  ```
319
319
 
320
- A fixed-timestep accumulator runs the substep at a constant **75 Hz** regardless of render rate, with up to 10 substeps per render frame. Constant `dt` keeps spring impulse, damping, and integration deterministic — without it, coupled cloth chains never reach steady state.
320
+ A fixed-timestep accumulator runs the substep at a constant **60 Hz** regardless of render rate, with up to 6 substeps per render frame. Constant `dt` keeps spring impulse, damping, and integration deterministic — without it, coupled cloth chains never reach steady state.
321
321
 
322
322
  ### Implementation notes
323
323
 
@@ -0,0 +1,19 @@
1
+ export declare class GpuTimestamp {
2
+ enabled: boolean;
3
+ readonly supported: boolean;
4
+ private device;
5
+ private querySet;
6
+ private resolveBuffer;
7
+ private readbackBuffer;
8
+ private nextSlot;
9
+ private frameLabels;
10
+ private inFlight;
11
+ private pendingLabels;
12
+ constructor(device: GPUDevice);
13
+ private ensureCreated;
14
+ beginFrame(): void;
15
+ wrap(desc: GPURenderPassDescriptor, label: string): GPURenderPassDescriptor;
16
+ endFrame(encoder: GPUCommandEncoder): void;
17
+ afterSubmit(): void;
18
+ }
19
+ //# sourceMappingURL=gpu-profile.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gpu-profile.d.ts","sourceRoot":"","sources":["../src/gpu-profile.ts"],"names":[],"mappings":"AAoBA,qBAAa,YAAY;IACvB,OAAO,UAAQ;IACf,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAA;IAC3B,OAAO,CAAC,MAAM,CAAW;IACzB,OAAO,CAAC,QAAQ,CAA2B;IAC3C,OAAO,CAAC,aAAa,CAAyB;IAC9C,OAAO,CAAC,cAAc,CAAyB;IAC/C,OAAO,CAAC,QAAQ,CAAI;IACpB,OAAO,CAAC,WAAW,CAAe;IAClC,OAAO,CAAC,QAAQ,CAAQ;IAKxB,OAAO,CAAC,aAAa,CAAwB;gBAEjC,MAAM,EAAE,SAAS;IAK7B,OAAO,CAAC,aAAa;IAiBrB,UAAU,IAAI,IAAI;IAWlB,IAAI,CAAC,IAAI,EAAE,uBAAuB,EAAE,KAAK,EAAE,MAAM,GAAG,uBAAuB;IAqB3E,QAAQ,CAAC,OAAO,EAAE,iBAAiB,GAAG,IAAI;IAW1C,WAAW,IAAI,IAAI;CAwBpB"}
@@ -0,0 +1,120 @@
1
+ // WebGPU timestamp-query profiling. Records pass durations into the same
2
+ // profiler module as CPU phases (so the snapshot table mixes both with a
3
+ // "gpu." prefix). Read-only — no effect on rendering output.
4
+ //
5
+ // Pattern (per spec):
6
+ // 1. Each render pass declares timestampWrites { begin, end } slot indices.
7
+ // 2. After all passes, encoder.resolveQuerySet writes uint64 ns timestamps
8
+ // into a QUERY_RESOLVE buffer.
9
+ // 3. We copy the resolve buffer into a MAP_READ readback buffer.
10
+ // 4. Submit. After the GPU finishes, mapAsync resolves; we read durations
11
+ // and record them.
12
+ //
13
+ // One readback buffer with an in-flight gate: if mapAsync hasn't resolved
14
+ // from a previous frame, we skip the copy this frame (no contention, just
15
+ // one frame of dropped sampling).
16
+ import { profiler } from "./physics/profile";
17
+ const MAX_PASSES_PER_FRAME = 64;
18
+ export class GpuTimestamp {
19
+ constructor(device) {
20
+ this.enabled = false;
21
+ this.querySet = null;
22
+ this.resolveBuffer = null;
23
+ this.readbackBuffer = null;
24
+ this.nextSlot = 0;
25
+ this.frameLabels = [];
26
+ this.inFlight = false;
27
+ // Set inside endFrame() when a copy was queued this frame; consumed by
28
+ // afterSubmit() to kick mapAsync. mapAsync must run AFTER queue.submit —
29
+ // calling it before submit puts the buffer into "mapping pending" state,
30
+ // which makes the copy in the encoder illegal.
31
+ this.pendingLabels = null;
32
+ this.device = device;
33
+ this.supported = device.features.has("timestamp-query");
34
+ }
35
+ ensureCreated() {
36
+ if (this.querySet || !this.supported)
37
+ return;
38
+ const slots = MAX_PASSES_PER_FRAME * 2;
39
+ this.querySet = this.device.createQuerySet({ type: "timestamp", count: slots });
40
+ this.resolveBuffer = this.device.createBuffer({
41
+ label: "gpu-timestamp-resolve",
42
+ size: slots * 8,
43
+ usage: GPUBufferUsage.QUERY_RESOLVE | GPUBufferUsage.COPY_SRC,
44
+ });
45
+ this.readbackBuffer = this.device.createBuffer({
46
+ label: "gpu-timestamp-readback",
47
+ size: slots * 8,
48
+ usage: GPUBufferUsage.MAP_READ | GPUBufferUsage.COPY_DST,
49
+ });
50
+ }
51
+ // Called once at the top of each render() before any pass wrap.
52
+ beginFrame() {
53
+ if (!this.enabled || !this.supported)
54
+ return;
55
+ this.ensureCreated();
56
+ this.nextSlot = 0;
57
+ this.frameLabels = [];
58
+ }
59
+ // Wrap a render-pass descriptor with timestampWrites for `label`. If
60
+ // profiling is off / unsupported / out of slots, returns desc unchanged.
61
+ // The same label called multiple times (e.g. each bloom mip) is fine —
62
+ // the profiler accumulates by label.
63
+ wrap(desc, label) {
64
+ if (!this.enabled || !this.querySet)
65
+ return desc;
66
+ if (this.nextSlot + 2 > MAX_PASSES_PER_FRAME * 2)
67
+ return desc;
68
+ const begin = this.nextSlot;
69
+ const end = this.nextSlot + 1;
70
+ this.nextSlot += 2;
71
+ this.frameLabels.push(label);
72
+ return {
73
+ ...desc,
74
+ timestampWrites: {
75
+ querySet: this.querySet,
76
+ beginningOfPassWriteIndex: begin,
77
+ endOfPassWriteIndex: end,
78
+ },
79
+ };
80
+ }
81
+ // Call on the encoder after all passes, BEFORE encoder.finish(). Adds
82
+ // resolveQuerySet + copyBufferToBuffer commands. mapAsync is deferred to
83
+ // afterSubmit() — calling it before submit transitions the readback buffer
84
+ // into "mapping pending" state, which makes the queued copy illegal.
85
+ endFrame(encoder) {
86
+ if (!this.enabled || !this.querySet || this.frameLabels.length === 0)
87
+ return;
88
+ const count = this.frameLabels.length * 2;
89
+ encoder.resolveQuerySet(this.querySet, 0, count, this.resolveBuffer, 0);
90
+ if (this.inFlight)
91
+ return;
92
+ encoder.copyBufferToBuffer(this.resolveBuffer, 0, this.readbackBuffer, 0, count * 8);
93
+ this.pendingLabels = this.frameLabels.slice();
94
+ }
95
+ // Call AFTER device.queue.submit(). Kicks mapAsync if a copy was queued
96
+ // this frame; the .then() reads durations and unmaps.
97
+ afterSubmit() {
98
+ const labels = this.pendingLabels;
99
+ if (!labels)
100
+ return;
101
+ this.pendingLabels = null;
102
+ this.inFlight = true;
103
+ const readback = this.readbackBuffer;
104
+ void readback.mapAsync(GPUMapMode.READ).then(() => {
105
+ const arr = new BigInt64Array(readback.getMappedRange());
106
+ for (let i = 0; i < labels.length; i++) {
107
+ const start = arr[i * 2];
108
+ const end = arr[i * 2 + 1];
109
+ // Per spec, timestamps are nanoseconds.
110
+ const ms = Number(end - start) / 1e6;
111
+ if (ms >= 0 && ms < 1000)
112
+ profiler.record("gpu." + labels[i], ms);
113
+ }
114
+ readback.unmap();
115
+ this.inFlight = false;
116
+ }, () => {
117
+ this.inFlight = false;
118
+ });
119
+ }
120
+ }
@@ -22,9 +22,11 @@ export declare class RigidBodyStore {
22
22
  readonly bodyOffsetMatrix: Float32Array;
23
23
  readonly bodyOffsetInverse: Float32Array;
24
24
  private boneOffsetsReady;
25
+ private collisionPairs;
25
26
  constructor(rigidbodies: Rigidbody[]);
26
27
  updateAabbs(margin?: number): void;
27
28
  computeBoneOffsets(boneInverseBindMatrices: Float32Array): void;
28
29
  isBoneOffsetsReady(): boolean;
30
+ getCollisionPairs(): Uint16Array;
29
31
  }
30
32
  //# sourceMappingURL=body.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"body.d.ts","sourceRoot":"","sources":["../../src/physics/body.ts"],"names":[],"mappings":"AACA,OAAO,EAAiC,KAAK,SAAS,EAAE,MAAM,SAAS,CAAA;AAIvE,qBAAa,cAAc;IACzB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IAEtB,QAAQ,CAAC,SAAS,EAAE,YAAY,CAAA;IAChC,QAAQ,CAAC,YAAY,EAAE,YAAY,CAAA;IACnC,QAAQ,CAAC,gBAAgB,EAAE,YAAY,CAAA;IACvC,QAAQ,CAAC,iBAAiB,EAAE,YAAY,CAAA;IAExC,QAAQ,CAAC,OAAO,EAAE,YAAY,CAAA;IAI9B,QAAQ,CAAC,UAAU,EAAE,YAAY,CAAA;IACjC,QAAQ,CAAC,aAAa,EAAE,YAAY,CAAA;IACpC,QAAQ,CAAC,cAAc,EAAE,YAAY,CAAA;IACrC,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAA;IACzB,QAAQ,CAAC,SAAS,EAAE,UAAU,CAAA;IAC9B,QAAQ,CAAC,QAAQ,EAAE,YAAY,CAAA;IAC/B,QAAQ,CAAC,WAAW,EAAE,YAAY,CAAA;IAIlC,QAAQ,CAAC,cAAc,EAAE,WAAW,CAAA;IACpC,QAAQ,CAAC,eAAe,EAAE,WAAW,CAAA;IAErC,QAAQ,CAAC,KAAK,EAAE,UAAU,CAAA;IAC1B,QAAQ,CAAC,IAAI,EAAE,YAAY,CAAA;IAE3B,QAAQ,CAAC,OAAO,EAAE,YAAY,CAAA;IAC9B,QAAQ,CAAC,OAAO,EAAE,YAAY,CAAA;IAI9B,QAAQ,CAAC,gBAAgB,EAAE,YAAY,CAAA;IACvC,QAAQ,CAAC,iBAAiB,EAAE,YAAY,CAAA;IACxC,OAAO,CAAC,gBAAgB,CAAQ;gBAEpB,WAAW,EAAE,SAAS,EAAE;IA4DpC,WAAW,CAAC,MAAM,SAAM,GAAG,IAAI;IA2F/B,kBAAkB,CAAC,uBAAuB,EAAE,YAAY,GAAG,IAAI;IAqD/D,kBAAkB,IAAI,OAAO;CAG9B"}
1
+ {"version":3,"file":"body.d.ts","sourceRoot":"","sources":["../../src/physics/body.ts"],"names":[],"mappings":"AACA,OAAO,EAAiC,KAAK,SAAS,EAAE,MAAM,SAAS,CAAA;AAIvE,qBAAa,cAAc;IACzB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IAEtB,QAAQ,CAAC,SAAS,EAAE,YAAY,CAAA;IAChC,QAAQ,CAAC,YAAY,EAAE,YAAY,CAAA;IACnC,QAAQ,CAAC,gBAAgB,EAAE,YAAY,CAAA;IACvC,QAAQ,CAAC,iBAAiB,EAAE,YAAY,CAAA;IAExC,QAAQ,CAAC,OAAO,EAAE,YAAY,CAAA;IAI9B,QAAQ,CAAC,UAAU,EAAE,YAAY,CAAA;IACjC,QAAQ,CAAC,aAAa,EAAE,YAAY,CAAA;IACpC,QAAQ,CAAC,cAAc,EAAE,YAAY,CAAA;IACrC,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAA;IACzB,QAAQ,CAAC,SAAS,EAAE,UAAU,CAAA;IAC9B,QAAQ,CAAC,QAAQ,EAAE,YAAY,CAAA;IAC/B,QAAQ,CAAC,WAAW,EAAE,YAAY,CAAA;IAIlC,QAAQ,CAAC,cAAc,EAAE,WAAW,CAAA;IACpC,QAAQ,CAAC,eAAe,EAAE,WAAW,CAAA;IAErC,QAAQ,CAAC,KAAK,EAAE,UAAU,CAAA;IAC1B,QAAQ,CAAC,IAAI,EAAE,YAAY,CAAA;IAE3B,QAAQ,CAAC,OAAO,EAAE,YAAY,CAAA;IAC9B,QAAQ,CAAC,OAAO,EAAE,YAAY,CAAA;IAI9B,QAAQ,CAAC,gBAAgB,EAAE,YAAY,CAAA;IACvC,QAAQ,CAAC,iBAAiB,EAAE,YAAY,CAAA;IACxC,OAAO,CAAC,gBAAgB,CAAQ;IAMhC,OAAO,CAAC,cAAc,CAA2B;gBAErC,WAAW,EAAE,SAAS,EAAE;IA4DpC,WAAW,CAAC,MAAM,SAAM,GAAG,IAAI;IA2F/B,kBAAkB,CAAC,uBAAuB,EAAE,YAAY,GAAG,IAAI;IAqD/D,kBAAkB,IAAI,OAAO;IAM7B,iBAAiB,IAAI,WAAW;CAoBjC"}
@@ -5,6 +5,11 @@ import { RigidbodyType, RigidbodyShape } from "./types";
5
5
  export class RigidBodyStore {
6
6
  constructor(rigidbodies) {
7
7
  this.boneOffsetsReady = false;
8
+ // Flat list of (i, j) pairs that survive the static-static + group/mask
9
+ // filter. None of those inputs change after construction, so building this
10
+ // once collapses 60k pair tests/step (349 bodies) down to a few thousand.
11
+ // Built lazily on first access.
12
+ this.collisionPairs = null;
8
13
  const N = rigidbodies.length;
9
14
  this.count = N;
10
15
  this.positions = new Float32Array(N * 3);
@@ -159,6 +164,31 @@ export class RigidBodyStore {
159
164
  isBoneOffsetsReady() {
160
165
  return this.boneOffsetsReady;
161
166
  }
167
+ // Pair-filter inputs (invMass, group, mask) are immutable post-construction,
168
+ // so build the candidate-pair list once and reuse every step.
169
+ getCollisionPairs() {
170
+ if (this.collisionPairs !== null)
171
+ return this.collisionPairs;
172
+ const N = this.count;
173
+ const invMass = this.invMass;
174
+ const group = this.collisionGroup;
175
+ const mask = this.willCollideMask;
176
+ const buf = [];
177
+ for (let i = 0; i < N; i++) {
178
+ const gi = group[i];
179
+ const mi = mask[i];
180
+ const dynA = invMass[i] > 0;
181
+ for (let j = i + 1; j < N; j++) {
182
+ if (!dynA && invMass[j] === 0)
183
+ continue;
184
+ if ((mi & group[j]) === 0 || (mask[j] & gi) === 0)
185
+ continue;
186
+ buf.push(i, j);
187
+ }
188
+ }
189
+ this.collisionPairs = new Uint16Array(buf);
190
+ return this.collisionPairs;
191
+ }
162
192
  }
163
193
  const _scratchA = new Float32Array(16);
164
194
  const _scratchB = new Float32Array(16);
@@ -11,6 +11,19 @@ export interface SixDofSpringConstraint {
11
11
  springEnabled: Uint8Array;
12
12
  springStiffness: Float32Array;
13
13
  equilibriumPoint: Float32Array;
14
+ cacheSkip: boolean;
15
+ cacheLeverA: Float32Array;
16
+ cacheLeverB: Float32Array;
17
+ cacheLinAxes: Float32Array;
18
+ cacheLinCrossA: Float32Array;
19
+ cacheLinCrossB: Float32Array;
20
+ cacheLinJacInv: Float32Array;
21
+ cacheLinTargetVel: Float32Array;
22
+ cacheLinActive: Uint8Array;
23
+ cacheAngAxes: Float32Array;
24
+ cacheAngTargetVel: Float32Array;
25
+ cacheAngActive: Uint8Array;
26
+ cacheAngJacInv: number;
14
27
  }
15
28
  export declare const STOP_ERP = 0.475;
16
29
  export declare function buildConstraints(rigidbodies: Rigidbody[], joints: Joint[]): SixDofSpringConstraint[];
@@ -1 +1 @@
1
- {"version":3,"file":"constraint.d.ts","sourceRoot":"","sources":["../../src/physics/constraint.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,SAAS,CAAA;AAW/C,MAAM,WAAW,sBAAsB;IACrC,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,CAAA;IAEb,MAAM,EAAE,YAAY,CAAA;IACpB,MAAM,EAAE,YAAY,CAAA;IAGpB,SAAS,EAAE,YAAY,CAAA;IACvB,SAAS,EAAE,YAAY,CAAA;IACvB,UAAU,EAAE,YAAY,CAAA;IACxB,UAAU,EAAE,YAAY,CAAA;IAExB,aAAa,EAAE,UAAU,CAAA;IACzB,eAAe,EAAE,YAAY,CAAA;IAC7B,gBAAgB,EAAE,YAAY,CAAA;CAC/B;AAED,eAAO,MAAM,QAAQ,QAAQ,CAAA;AAM7B,wBAAgB,gBAAgB,CAC9B,WAAW,EAAE,SAAS,EAAE,EACxB,MAAM,EAAE,KAAK,EAAE,GACd,sBAAsB,EAAE,CAqE1B"}
1
+ {"version":3,"file":"constraint.d.ts","sourceRoot":"","sources":["../../src/physics/constraint.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,SAAS,CAAA;AAW/C,MAAM,WAAW,sBAAsB;IACrC,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,CAAA;IAEb,MAAM,EAAE,YAAY,CAAA;IACpB,MAAM,EAAE,YAAY,CAAA;IAGpB,SAAS,EAAE,YAAY,CAAA;IACvB,SAAS,EAAE,YAAY,CAAA;IACvB,UAAU,EAAE,YAAY,CAAA;IACxB,UAAU,EAAE,YAAY,CAAA;IAExB,aAAa,EAAE,UAAU,CAAA;IACzB,eAAe,EAAE,YAAY,CAAA;IAC7B,gBAAgB,EAAE,YAAY,CAAA;IAK9B,SAAS,EAAE,OAAO,CAAA;IAClB,WAAW,EAAE,YAAY,CAAA;IACzB,WAAW,EAAE,YAAY,CAAA;IACzB,YAAY,EAAE,YAAY,CAAA;IAC1B,cAAc,EAAE,YAAY,CAAA;IAC5B,cAAc,EAAE,YAAY,CAAA;IAC5B,cAAc,EAAE,YAAY,CAAA;IAC5B,iBAAiB,EAAE,YAAY,CAAA;IAC/B,cAAc,EAAE,UAAU,CAAA;IAC1B,YAAY,EAAE,YAAY,CAAA;IAC1B,iBAAiB,EAAE,YAAY,CAAA;IAC/B,cAAc,EAAE,UAAU,CAAA;IAC1B,cAAc,EAAE,MAAM,CAAA;CACvB;AAED,eAAO,MAAM,QAAQ,QAAQ,CAAA;AAM7B,wBAAgB,gBAAgB,CAC9B,WAAW,EAAE,SAAS,EAAE,EACxB,MAAM,EAAE,KAAK,EAAE,GACd,sBAAsB,EAAE,CAkF1B"}
@@ -64,6 +64,19 @@ export function buildConstraints(rigidbodies, joints) {
64
64
  springEnabled,
65
65
  springStiffness,
66
66
  equilibriumPoint: new Float32Array(6),
67
+ cacheSkip: false,
68
+ cacheLeverA: new Float32Array(3),
69
+ cacheLeverB: new Float32Array(3),
70
+ cacheLinAxes: new Float32Array(9),
71
+ cacheLinCrossA: new Float32Array(9),
72
+ cacheLinCrossB: new Float32Array(9),
73
+ cacheLinJacInv: new Float32Array(3),
74
+ cacheLinTargetVel: new Float32Array(3),
75
+ cacheLinActive: new Uint8Array(3),
76
+ cacheAngAxes: new Float32Array(9),
77
+ cacheAngTargetVel: new Float32Array(3),
78
+ cacheAngActive: new Uint8Array(3),
79
+ cacheAngJacInv: 0,
67
80
  });
68
81
  }
69
82
  return out;
@@ -18,6 +18,34 @@ export interface Contact {
18
18
  appliedNormalImpulse: number;
19
19
  appliedFrictionImpulse1: number;
20
20
  appliedFrictionImpulse2: number;
21
+ cAxN: number;
22
+ cAyN: number;
23
+ cAzN: number;
24
+ cBxN: number;
25
+ cByN: number;
26
+ cBzN: number;
27
+ jacInvN: number;
28
+ bounceVel: number;
29
+ t1x: number;
30
+ t1y: number;
31
+ t1z: number;
32
+ cAxT1: number;
33
+ cAyT1: number;
34
+ cAzT1: number;
35
+ cBxT1: number;
36
+ cByT1: number;
37
+ cBzT1: number;
38
+ jacInvT1: number;
39
+ t2x: number;
40
+ t2y: number;
41
+ t2z: number;
42
+ cAxT2: number;
43
+ cAyT2: number;
44
+ cAzT2: number;
45
+ cBxT2: number;
46
+ cByT2: number;
47
+ cBzT2: number;
48
+ jacInvT2: number;
21
49
  }
22
50
  export declare class ContactPool {
23
51
  private pool;
@@ -1 +1 @@
1
- {"version":3,"file":"contact.d.ts","sourceRoot":"","sources":["../../src/physics/contact.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,QAAQ,CAAA;AAO5C,eAAO,MAAM,cAAc,OAAO,CAAA;AAElC,MAAM,WAAW,OAAO;IACtB,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,CAAA;IAEb,GAAG,EAAE,MAAM,CAAA;IACX,GAAG,EAAE,MAAM,CAAA;IACX,GAAG,EAAE,MAAM,CAAA;IACX,GAAG,EAAE,MAAM,CAAA;IACX,GAAG,EAAE,MAAM,CAAA;IACX,GAAG,EAAE,MAAM,CAAA;IAEX,EAAE,EAAE,MAAM,CAAA;IACV,EAAE,EAAE,MAAM,CAAA;IACV,EAAE,EAAE,MAAM,CAAA;IACV,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,EAAE,MAAM,CAAA;IAChB,WAAW,EAAE,MAAM,CAAA;IAEnB,oBAAoB,EAAE,MAAM,CAAA;IAC5B,uBAAuB,EAAE,MAAM,CAAA;IAC/B,uBAAuB,EAAE,MAAM,CAAA;CAChC;AAyBD,qBAAa,WAAW;IACtB,OAAO,CAAC,IAAI,CAAgB;IAC5B,KAAK,SAAI;IAET,OAAO,IAAI,OAAO;IAelB,KAAK,IAAI,IAAI;IAGb,GAAG,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO;CAGxB;AASD,wBAAgB,WAAW,CAAC,KAAK,EAAE,cAAc,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,OAAO,CAahF;AAkxBD,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,cAAc,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,WAAW,GAAG,IAAI,CAuCrG;AA4BD,wBAAgB,YAAY,CAAC,KAAK,EAAE,cAAc,EAAE,IAAI,EAAE,WAAW,GAAG,IAAI,CAkB3E"}
1
+ {"version":3,"file":"contact.d.ts","sourceRoot":"","sources":["../../src/physics/contact.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,QAAQ,CAAA;AAO5C,eAAO,MAAM,cAAc,OAAO,CAAA;AAElC,MAAM,WAAW,OAAO;IACtB,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,CAAA;IAEb,GAAG,EAAE,MAAM,CAAA;IACX,GAAG,EAAE,MAAM,CAAA;IACX,GAAG,EAAE,MAAM,CAAA;IACX,GAAG,EAAE,MAAM,CAAA;IACX,GAAG,EAAE,MAAM,CAAA;IACX,GAAG,EAAE,MAAM,CAAA;IAEX,EAAE,EAAE,MAAM,CAAA;IACV,EAAE,EAAE,MAAM,CAAA;IACV,EAAE,EAAE,MAAM,CAAA;IACV,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,EAAE,MAAM,CAAA;IAChB,WAAW,EAAE,MAAM,CAAA;IAEnB,oBAAoB,EAAE,MAAM,CAAA;IAC5B,uBAAuB,EAAE,MAAM,CAAA;IAC/B,uBAAuB,EAAE,MAAM,CAAA;IAI/B,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;IACxC,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;IACxC,OAAO,EAAE,MAAM,CAAA;IACf,SAAS,EAAE,MAAM,CAAA;IAEjB,GAAG,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;IACrC,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;IAC3C,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;IAC3C,QAAQ,EAAE,MAAM,CAAA;IAEhB,GAAG,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;IACrC,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;IAC3C,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;IAC3C,QAAQ,EAAE,MAAM,CAAA;CACjB;AA8BD,qBAAa,WAAW;IACtB,OAAO,CAAC,IAAI,CAAgB;IAC5B,KAAK,SAAI;IAET,OAAO,IAAI,OAAO;IAelB,KAAK,IAAI,IAAI;IAGb,GAAG,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO;CAGxB;AASD,wBAAgB,WAAW,CAAC,KAAK,EAAE,cAAc,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,OAAO,CAahF;AAkxBD,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,cAAc,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,WAAW,GAAG,IAAI,CAuCrG;AA4BD,wBAAgB,YAAY,CAAC,KAAK,EAAE,cAAc,EAAE,IAAI,EAAE,WAAW,GAAG,IAAI,CAS3E"}
@@ -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
  }
@@ -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;gBAEpB,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"}
@@ -13,11 +13,9 @@ 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);
@@ -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"}