reze-engine 0.54.16 → 0.55.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.
Files changed (59) hide show
  1. package/dist/camera.d.ts +13 -0
  2. package/dist/camera.d.ts.map +1 -1
  3. package/dist/camera.js +53 -2
  4. package/dist/engine.d.ts +317 -0
  5. package/dist/engine.d.ts.map +1 -1
  6. package/dist/engine.js +1263 -27
  7. package/dist/graph/presets/pool_floor.d.ts +3 -0
  8. package/dist/graph/presets/pool_floor.d.ts.map +1 -0
  9. package/dist/graph/presets/pool_floor.js +42 -0
  10. package/dist/graph/presets/water.d.ts +3 -0
  11. package/dist/graph/presets/water.d.ts.map +1 -0
  12. package/dist/graph/presets/water.js +31 -0
  13. package/dist/index.d.ts +3 -0
  14. package/dist/index.d.ts.map +1 -1
  15. package/dist/index.js +8 -0
  16. package/dist/model.d.ts.map +1 -1
  17. package/dist/model.js +10 -0
  18. package/dist/overlay.d.ts +200 -0
  19. package/dist/overlay.d.ts.map +1 -0
  20. package/dist/overlay.js +806 -0
  21. package/dist/physics/autofit.d.ts +147 -0
  22. package/dist/physics/autofit.d.ts.map +1 -0
  23. package/dist/physics/autofit.js +501 -0
  24. package/dist/pmx-document.d.ts +218 -0
  25. package/dist/pmx-document.d.ts.map +1 -0
  26. package/dist/pmx-document.js +685 -0
  27. package/dist/shaders/passes/composite.d.ts.map +1 -1
  28. package/dist/shaders/passes/composite.js +18 -1
  29. package/dist/shaders/passes/field-blit.d.ts +26 -0
  30. package/dist/shaders/passes/field-blit.d.ts.map +1 -0
  31. package/dist/shaders/passes/field-blit.js +65 -0
  32. package/dist/shaders/passes/ground-noise.d.ts +7 -0
  33. package/dist/shaders/passes/ground-noise.d.ts.map +1 -0
  34. package/dist/shaders/passes/ground-noise.js +88 -0
  35. package/dist/shaders/passes/ground.d.ts.map +1 -1
  36. package/dist/shaders/passes/ground.js +63 -17
  37. package/dist/shaders/passes/overlay.d.ts +3 -0
  38. package/dist/shaders/passes/overlay.d.ts.map +1 -0
  39. package/dist/shaders/passes/overlay.js +164 -0
  40. package/dist/shaders/passes/sim.d.ts +34 -0
  41. package/dist/shaders/passes/sim.d.ts.map +1 -0
  42. package/dist/shaders/passes/sim.js +169 -0
  43. package/dist/shaders/passes/wireframe.d.ts +2 -0
  44. package/dist/shaders/passes/wireframe.d.ts.map +1 -0
  45. package/dist/shaders/passes/wireframe.js +107 -0
  46. package/dist/shaders/score-api.d.ts +10 -0
  47. package/dist/shaders/score-api.d.ts.map +1 -0
  48. package/dist/shaders/score-api.js +114 -0
  49. package/package.json +1 -1
  50. package/src/camera.ts +60 -2
  51. package/src/engine.ts +1359 -25
  52. package/src/index.ts +50 -0
  53. package/src/model.ts +9 -0
  54. package/src/overlay.ts +1000 -0
  55. package/src/pmx-document.ts +882 -0
  56. package/src/shaders/passes/composite.ts +18 -1
  57. package/src/shaders/passes/ground.ts +65 -17
  58. package/src/shaders/passes/overlay.ts +166 -0
  59. package/src/shaders/passes/wireframe.ts +108 -0
package/dist/camera.d.ts CHANGED
@@ -35,6 +35,19 @@ export declare class Camera {
35
35
  maxZ: number;
36
36
  lowerBetaLimit: number;
37
37
  upperBetaLimit: number;
38
+ /**
39
+ * Orbit roll, radians — the lean the orbit itself cannot state.
40
+ *
41
+ * alpha and beta are a yaw and a pitch about an upright axis, and lookAt is
42
+ * handed world up, so an orbiting shot is level by construction. This tips
43
+ * that up vector about the eye→target line, which leaves WHERE the camera is
44
+ * and WHAT it looks at exactly as they were.
45
+ *
46
+ * That is the whole reason it lives here rather than in a pose pushed from
47
+ * outside: a rolled shot still follows a bone, still orbits, still zooms. A
48
+ * pose replaces all of that with one frozen answer.
49
+ */
50
+ roll: number;
38
51
  private _viewMat;
39
52
  private _projMat;
40
53
  vmdDriven: boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"camera.d.ts","sourceRoot":"","sources":["../src/camera.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAQ,IAAI,EAAE,MAAM,QAAQ,CAAA;AACzC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAA;AAyBpD,qBAAa,MAAM;IACjB,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,IAAI,CAAA;IACZ,GAAG,EAAE,MAAM,CAAA;IACX,MAAM,EAAE,MAAM,CAAI;IAClB;;;oFAGgF;IAChF,IAAI,EAAE,MAAM,CAAiB;IAC7B,GAAG,EAAE,MAAM,CAAU;IACrB;+EAC2E;IAC3E,SAAS,UAAQ;IAGjB,OAAO,CAAC,MAAM,CAAiC;IAC/C,OAAO,CAAC,WAAW,CAAiB;IACpC,OAAO,CAAC,UAAU,CAAiB;IACnC,OAAO,CAAC,WAAW,CAAsB;IACzC,OAAO,CAAC,YAAY,CAAiB;IACrC,OAAO,CAAC,YAAY,CAAiB;IACrC,OAAO,CAAC,eAAe,CAAsB;IAC7C,OAAO,CAAC,UAAU,CAAiB;IACnC,OAAO,CAAC,iBAAiB,CAAY;IACrC,OAAO,CAAC,iBAAiB,CAAiB;IAC1C,OAAO,CAAC,oBAAoB,CAAY;IAGxC,kBAAkB,EAAE,MAAM,CAAQ;IAClC,cAAc,EAAE,MAAM,CAAS;IAC/B,cAAc,EAAE,MAAM,CAAO;IAC7B,cAAc,EAAE,MAAM,CAAO;IAC7B,IAAI,EAAE,MAAM,CAAO;IACnB,IAAI,EAAE,MAAM,CAAU;IACtB,cAAc,EAAE,MAAM,CAAQ;IAC9B,cAAc,EAAE,MAAM,CAAkB;IAGxC,OAAO,CAAC,QAAQ,CAAiC;IACjD,OAAO,CAAC,QAAQ,CAAiC;IAKjD,SAAS,EAAE,OAAO,CAAQ;IAC1B,OAAO,CAAC,UAAU,CAAoB;IACtC,OAAO,CAAC,YAAY,CAAoB;IACxC,OAAO,CAAC,YAAY,CAAM;IAC1B,OAAO,CAAC,SAAS,CAAc;IAC/B,OAAO,CAAC,YAAY,CAAuB;gBAE/B,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,GAAE,MAAoB;IAmBhG,WAAW,IAAI,IAAI;IAQnB;wFACoF;IACpF,YAAY,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI;IAOpC,0FAA0F;IAC1F;;;;;;;;;;OAUG;IACH,OAAO,IAAI,UAAU;IAoBrB,UAAU,CAAC,IAAI,EAAE,UAAU,GAAG,IAAI;IAelC,OAAO,CAAC,MAAM;IAUd;;;;;;;;;;;OAWG;IACH,cAAc,IAAI,IAAI;IAItB,aAAa,IAAI,IAAI;IA2BrB,OAAO,CAAC,gBAAgB;IA0CxB,OAAO,CAAC,SAAS;IAiBjB,kFAAkF;IAClF,OAAO,CAAC,mBAAmB;IAK3B;;;;;;;;;;;;;;;OAeG;IACH,OAAO,CAAC,oBAAoB;IAM5B,mBAAmB,IAAI,IAAI;IAW3B,aAAa,CAAC,MAAM,EAAE,iBAAiB;IAiBvC,aAAa;IAkBb,cAAc,CAAC,MAAM,EAAE,OAAO;IAS9B,OAAO,CAAC,WAAW;IAOnB,OAAO,CAAC,WAAW;IAsBnB,OAAO,CAAC,SAAS;IAKjB,OAAO,CAAC,OAAO;IAYf,OAAO,CAAC,aAAa;IAIrB,OAAO,CAAC,YAAY;IA8BpB,OAAO,CAAC,WAAW;IAuFnB,OAAO,CAAC,UAAU;CA+BnB"}
1
+ {"version":3,"file":"camera.d.ts","sourceRoot":"","sources":["../src/camera.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAQ,IAAI,EAAE,MAAM,QAAQ,CAAA;AACzC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAA;AAyBpD,qBAAa,MAAM;IACjB,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,IAAI,CAAA;IACZ,GAAG,EAAE,MAAM,CAAA;IACX,MAAM,EAAE,MAAM,CAAI;IAClB;;;oFAGgF;IAChF,IAAI,EAAE,MAAM,CAAiB;IAC7B,GAAG,EAAE,MAAM,CAAU;IACrB;+EAC2E;IAC3E,SAAS,UAAQ;IAGjB,OAAO,CAAC,MAAM,CAAiC;IAC/C,OAAO,CAAC,WAAW,CAAiB;IACpC,OAAO,CAAC,UAAU,CAAiB;IACnC,OAAO,CAAC,WAAW,CAAsB;IACzC,OAAO,CAAC,YAAY,CAAiB;IACrC,OAAO,CAAC,YAAY,CAAiB;IACrC,OAAO,CAAC,eAAe,CAAsB;IAC7C,OAAO,CAAC,UAAU,CAAiB;IACnC,OAAO,CAAC,iBAAiB,CAAY;IACrC,OAAO,CAAC,iBAAiB,CAAiB;IAC1C,OAAO,CAAC,oBAAoB,CAAY;IAGxC,kBAAkB,EAAE,MAAM,CAAQ;IAClC,cAAc,EAAE,MAAM,CAAS;IAC/B,cAAc,EAAE,MAAM,CAAO;IAC7B,cAAc,EAAE,MAAM,CAAO;IAC7B,IAAI,EAAE,MAAM,CAAO;IACnB,IAAI,EAAE,MAAM,CAAU;IACtB,cAAc,EAAE,MAAM,CAAQ;IAC9B,cAAc,EAAE,MAAM,CAAkB;IACxC;;;;;;;;;;;OAWG;IACH,IAAI,EAAE,MAAM,CAAI;IAGhB,OAAO,CAAC,QAAQ,CAAiC;IACjD,OAAO,CAAC,QAAQ,CAAiC;IAKjD,SAAS,EAAE,OAAO,CAAQ;IAC1B,OAAO,CAAC,UAAU,CAAoB;IACtC,OAAO,CAAC,YAAY,CAAoB;IACxC,OAAO,CAAC,YAAY,CAAM;IAC1B,OAAO,CAAC,SAAS,CAAc;IAC/B,OAAO,CAAC,YAAY,CAAuB;gBAE/B,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,GAAE,MAAoB;IAmBhG,WAAW,IAAI,IAAI;IAQnB;wFACoF;IACpF,YAAY,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI;IAOpC,0FAA0F;IAC1F;;;;;;;;;;OAUG;IACH,OAAO,IAAI,UAAU;IAuBrB,UAAU,CAAC,IAAI,EAAE,UAAU,GAAG,IAAI;IAelC,OAAO,CAAC,MAAM;IAUd;;;;;;;;;;;OAWG;IACH,cAAc,IAAI,IAAI;IAItB,aAAa,IAAI,IAAI;IAqErB,OAAO,CAAC,gBAAgB;IA0CxB,OAAO,CAAC,SAAS;IAiBjB,kFAAkF;IAClF,OAAO,CAAC,mBAAmB;IAK3B;;;;;;;;;;;;;;;OAeG;IACH,OAAO,CAAC,oBAAoB;IAM5B,mBAAmB,IAAI,IAAI;IAW3B,aAAa,CAAC,MAAM,EAAE,iBAAiB;IAiBvC,aAAa;IAkBb,cAAc,CAAC,MAAM,EAAE,OAAO;IAS9B,OAAO,CAAC,WAAW;IAOnB,OAAO,CAAC,WAAW;IAsBnB,OAAO,CAAC,SAAS;IAKjB,OAAO,CAAC,OAAO;IAYf,OAAO,CAAC,aAAa;IAIrB,OAAO,CAAC,YAAY;IA8BpB,OAAO,CAAC,WAAW;IAuFnB,OAAO,CAAC,UAAU;CA+BnB"}
package/dist/camera.js CHANGED
@@ -54,6 +54,19 @@ export class Camera {
54
54
  this.maxZ = FAR_CAP;
55
55
  this.lowerBetaLimit = 0.001;
56
56
  this.upperBetaLimit = Math.PI - 0.001;
57
+ /**
58
+ * Orbit roll, radians — the lean the orbit itself cannot state.
59
+ *
60
+ * alpha and beta are a yaw and a pitch about an upright axis, and lookAt is
61
+ * handed world up, so an orbiting shot is level by construction. This tips
62
+ * that up vector about the eye→target line, which leaves WHERE the camera is
63
+ * and WHAT it looks at exactly as they were.
64
+ *
65
+ * That is the whole reason it lives here rather than in a pose pushed from
66
+ * outside: a rolled shot still follows a bone, still orbits, still zooms. A
67
+ * pose replaces all of that with one frozen answer.
68
+ */
69
+ this.roll = 0;
57
70
  // Reused each frame so getViewMatrix/getProjectionMatrix don't allocate a Mat4 per call.
58
71
  this._viewMat = new Mat4(new Float32Array(16));
59
72
  this._projMat = new Mat4(new Float32Array(16));
@@ -126,7 +139,10 @@ export class Camera {
126
139
  // NEGATIVE to match: in a VMD the camera sits behind its target.
127
140
  return {
128
141
  target: new Vec3(this.target.x, this.target.y, this.target.z),
129
- rotation: new Vec3(this.beta - Math.PI / 2, -this.alpha, 0),
142
+ // z carries the roll, so a rolled orbit exports and reads back as the
143
+ // same shot rather than a level one — the AE rig and the VMD writer both
144
+ // take this channel.
145
+ rotation: new Vec3(this.beta - Math.PI / 2, -this.alpha, this.roll),
130
146
  distance: -this.radius,
131
147
  fov: this.fov,
132
148
  };
@@ -199,7 +215,42 @@ export class Camera {
199
215
  }
200
216
  const eye = this.getPosition();
201
217
  const t = this.target;
202
- Mat4.lookAtInto(this._viewMat.values, eye.x, eye.y, eye.z, t.x, t.y, t.z, 0, 1, 0);
218
+ if (this.roll === 0) {
219
+ Mat4.lookAtInto(this._viewMat.values, eye.x, eye.y, eye.z, t.x, t.y, t.z, 0, 1, 0);
220
+ return this._viewMat;
221
+ }
222
+ // Roll = the up vector, turned about the view axis. Build the shot's own
223
+ // basis first (forward, then right, then a true up), because world up is
224
+ // only the camera's up while the shot is level — which is the thing this is
225
+ // about to stop being.
226
+ let fx = t.x - eye.x;
227
+ let fy = t.y - eye.y;
228
+ let fz = t.z - eye.z;
229
+ const fl = Math.hypot(fx, fy, fz) || 1;
230
+ fx /= fl;
231
+ fy /= fl;
232
+ fz /= fl;
233
+ // right = forward × worldUp, with worldUp = (0,1,0), which is (−fz, 0, fx).
234
+ //
235
+ // WRITTEN THE OTHER WAY ROUND ONCE, and it did not fail quietly: that is
236
+ // worldUp × forward, so `right` pointed left, `up` below came out as right ×
237
+ // forward = DOWN, and the camera turned upside down the moment roll left
238
+ // zero. Degenerate only when the shot looks straight up or down, where
239
+ // beta's own limits already keep it from arriving.
240
+ let rx = -fz;
241
+ let ry = 0;
242
+ let rz = fx;
243
+ const rl = Math.hypot(rx, ry, rz) || 1;
244
+ rx /= rl;
245
+ ry /= rl;
246
+ rz /= rl;
247
+ // up = right × forward
248
+ const ux = ry * fz - rz * fy;
249
+ const uy = rz * fx - rx * fz;
250
+ const uz = rx * fy - ry * fx;
251
+ const c = Math.cos(this.roll);
252
+ const sn = Math.sin(this.roll);
253
+ Mat4.lookAtInto(this._viewMat.values, eye.x, eye.y, eye.z, t.x, t.y, t.z, ux * c + rx * sn, uy * c + ry * sn, uz * c + rz * sn);
203
254
  return this._viewMat;
204
255
  }
205
256
  // Get camera's right and up vectors for panning
package/dist/engine.d.ts CHANGED
@@ -9,6 +9,7 @@ import { type ParamKey } from "./param-track";
9
9
  import { type EffectWindow } from "./effect-schedule";
10
10
  import { type HdrImage } from "./hdr";
11
11
  import { type LyricLine, type LyricRect } from "./shaders/lyrics-api";
12
+ import { type BoneOverlayOptions, type JointOverlayOptions, type OverlayPrimitive, type RigidbodyOverlayOptions } from "./overlay";
12
13
  import { type CompileOptions, type StyleSlot } from "./graph/compile";
13
14
  import type { AlphaMode, RenderClass } from "./graph/render-class";
14
15
  import type { ApplyStyleGroupResult, ApplyStyleGroupsResult, StyleGroup } from "./graph/style-group";
@@ -428,6 +429,19 @@ interface ModelInstance {
428
429
  materialMorphTargets: MaterialMorphTarget[] | null;
429
430
  /** The same targets by PMX material index, so a named offset is one lookup. */
430
431
  materialMorphByIndex: Map<number, MaterialMorphTarget> | null;
432
+ /** The mesh's unique edges as a line-list index buffer, built on first use.
433
+ * Deduplicated: an interior edge belongs to two triangles, so drawing the
434
+ * triangle list's edges directly would draw most of the mesh twice.
435
+ *
436
+ * Keyed by the material the edges were cut from, "" for the whole mesh. A
437
+ * material is a consecutive index run, so scoping is a slice of the same
438
+ * build — and both stay cached, because narrowing to one material and
439
+ * widening back out is the loop somebody auditing a model is in. */
440
+ wireEdges: Map<string, {
441
+ buffer: GPUBuffer;
442
+ count: number;
443
+ bindGroup: GPUBindGroup;
444
+ } | null>;
431
445
  physics: RezePhysics | null;
432
446
  vertexBufferNeedsUpdate: boolean;
433
447
  gpuMorph: GpuMorph | null;
@@ -530,7 +544,67 @@ export declare class Engine {
530
544
  private selectionEdgeUniformBuffer;
531
545
  private selectionEdgePassDescriptor;
532
546
  private selectionSampler;
547
+ private overlayVertexBuffer;
548
+ private overlayInstanceBuffer;
549
+ private overlayInstanceCapacity;
550
+ private overlayPipeline;
551
+ private overlaySolidPipeline;
552
+ private overlayBindGroup;
553
+ private overlayGeometry;
554
+ private overlayPassDescriptor;
555
+ private overlayDepthTexture;
556
+ private overlayMsaaTexture;
557
+ private overlayResolveTexture;
558
+ private overlayUniformBuffer;
559
+ private overlayUniformData;
560
+ private overlayCompositePipeline;
561
+ private overlayCompositeLayout;
562
+ private overlayCompositeBindGroup;
563
+ private overlayCompositePassDescriptor;
564
+ private overlayTargetSize;
565
+ /** The overlay renders multisampled into its own layer; the scene's own depth
566
+ * is discarded before the composite (see the depthRead note in render), so it
567
+ * could not have shared either that or the single-sample swapchain. */
568
+ private static readonly OVERLAY_SAMPLE_COUNT;
569
+ /** Dash period in device pixels — dashes are geometry, so this is only the
570
+ * reference the dashedLine shape is cut against. */
571
+ private static readonly OVERLAY_DASH_PERIOD_PX;
572
+ private overlayLayers;
573
+ private overlayBones;
574
+ private overlayBodies;
575
+ private overlayJoints;
576
+ private overlayVertices;
577
+ private wireframePipeline;
578
+ private wireframeDepthPipeline;
579
+ private wireframeUniformBuffer;
580
+ private wireframeBindGroup;
581
+ /** The seam pass draws in the same frame at a different stroke and alpha, and
582
+ * every queue write lands before the command buffer runs — writing the one
583
+ * buffer twice would give both draws the second value. */
584
+ private wireframeSeamUniformBuffer;
585
+ private wireframeSeamBindGroup;
586
+ /** Same reason: the hovered material draws its own stroke, self-occluding, in
587
+ * the same frame as the base mesh and the seams. */
588
+ private wireframeHoverUniformBuffer;
589
+ private wireframeHoverBindGroup;
590
+ private wireframeSkinLayout;
591
+ private wireframeColorData;
592
+ /** Rebuilt every frame into these, grouped by shape so each shape is one draw. */
593
+ private overlayByShape;
594
+ private overlayScratch;
595
+ private bonePickScratch;
596
+ private overlayInstanceData;
533
597
  private selectedBone;
598
+ /** The material a pointer is currently over, or null. Cheap and separate from
599
+ * setVertexOverlay on purpose — the same split setSelectedBone takes from
600
+ * setBoneOverlay — because this is written every frame the pointer moves and
601
+ * the overlay's own option object is not something to reconstruct that often. */
602
+ private hoverMaterial;
603
+ /** The transform gizmo follows setSelectedBone, which is also what selects a
604
+ * bone to INSPECT. A model editor selects bones constantly and poses them
605
+ * rarely, so the two need separating: off leaves selection working and takes
606
+ * the handles away. */
607
+ private gizmoEnabled;
534
608
  private gizmoVertexBuffer;
535
609
  private gizmoTransformBuffer;
536
610
  private gizmoPipeline;
@@ -1094,6 +1168,28 @@ export declare class Engine {
1094
1168
  setColorGrading(patch: Partial<ColorGradingOptions>): void;
1095
1169
  /** Current grade (for serialization into a scene descriptor). */
1096
1170
  getColorGrading(): ColorGradingOptions;
1171
+ /** Sensor grain: how much, and whether it moves. */
1172
+ private grain;
1173
+ /**
1174
+ * Film grain over the rendered scene, 0–1.
1175
+ *
1176
+ * A property of a SENSOR, so it belongs to the camera rather than to any one
1177
+ * subject, and it lands on what the engine drew and on nothing else — never on
1178
+ * a background image or a backdrop video, which arrived with grain of their
1179
+ * own and would be graded rather than matched by a second helping.
1180
+ *
1181
+ * `animated` false freezes it. A still photograph's grain does not move, and
1182
+ * noise crawling over a frozen picture makes the rendering look more alive
1183
+ * than the thing it is standing in.
1184
+ *
1185
+ * Costs one hash per pixel in a pass that already runs, and nothing at all at
1186
+ * zero — the branch is on a uniform.
1187
+ */
1188
+ setFilmGrain(amount: number, animated?: boolean): void;
1189
+ getFilmGrain(): Readonly<{
1190
+ amount: number;
1191
+ animated: boolean;
1192
+ }>;
1097
1193
  setViewTransformOptions(patch: Partial<ViewTransformOptions>): void;
1098
1194
  /**
1099
1195
  * Whether bloom will actually reach the frame this frame.
@@ -1643,6 +1739,7 @@ export declare class Engine {
1643
1739
  setRenderSize(size: null): void;
1644
1740
  private handleResize;
1645
1741
  private setupGizmo;
1742
+ private setupOverlay;
1646
1743
  private setupCamera;
1647
1744
  /** Set static camera look-at / orbit center. Clears any model follow binding. */
1648
1745
  setCameraTarget(v: Vec3): void;
@@ -1650,6 +1747,32 @@ export declare class Engine {
1650
1747
  setCameraTarget(model: Model | null, boneName: string, offset?: Vec3): void;
1651
1748
  /** Souls-style follow cam: orbit center tracks a model bone each frame. Shorthand for setCameraTarget(model, boneName, offset). */
1652
1749
  setCameraFollow(model: Model | null, boneName?: string, offset?: Vec3, smoothing?: number): void;
1750
+ /** Whether a loaded camera track is allowed to drive (setCameraVmdEnabled).
1751
+ * Held separately from `camera.vmdDriven` because that flag now answers to
1752
+ * two sources, and a track switched off must stay off when the other one
1753
+ * releases the camera. */
1754
+ private cameraVmdEnabled;
1755
+ /** A pose pushed in from outside — see setCameraPose. Reapplied every frame,
1756
+ * so it outranks the orbit AND a loaded track for as long as it is set. */
1757
+ private cameraPoseOverride;
1758
+ /** The one place that decides who is holding the camera. An external pose
1759
+ * wins; a track drives when it is loaded and enabled; otherwise orbit. */
1760
+ private refreshCameraDrive;
1761
+ /**
1762
+ * Aim the camera from outside — a solved match-move, a saved shot, a rig
1763
+ * driving the view from the host's own clock.
1764
+ *
1765
+ * The exact partner of `getCameraPose`, and the same five channels: the shot
1766
+ * as MMD states it, roll included. Orbit cannot express roll, so this is the
1767
+ * only way a tilted camera reaches the engine.
1768
+ *
1769
+ * Reapplied every frame while set, which makes it authoritative rather than
1770
+ * advisory — nothing the transport or a loaded track does moves it. Pass null
1771
+ * to release, and whatever was driving before takes the camera back.
1772
+ */
1773
+ setCameraPose(pose: CameraPose | null): void;
1774
+ /** The pose currently forced from outside, or null when nothing is. */
1775
+ getCameraPoseOverride(): CameraPose | null;
1653
1776
  /** Load a camera VMD (dedicated camera file, or any VMD's camera block) and drive the shot
1654
1777
  * from it. Default-on once a non-empty track loads; toggle with setCameraVmdEnabled. */
1655
1778
  loadCameraVmd(url: string): Promise<void>;
@@ -1774,12 +1897,42 @@ export declare class Engine {
1774
1897
  private transportTime;
1775
1898
  /** Current orbit eye position (spherical coords resolved to a point). */
1776
1899
  getCameraPosition(): Vec3;
1900
+ /**
1901
+ * The live orbit, read in ONE call.
1902
+ *
1903
+ * A host that stores the shot has to be able to ask where the camera actually
1904
+ * IS, because a drag on the canvas moves this and nothing else — and a
1905
+ * document that never asks will happily write back the angle it last set,
1906
+ * discarding whatever the person just did with the mouse. Reading the four
1907
+ * separately invites a torn set across a frame boundary; this cannot tear.
1908
+ *
1909
+ * `target` is the orbit's own centre. While the engine is following a bone
1910
+ * that point rides the bone, so a caller storing a FOLLOW offset must keep its
1911
+ * own and take only the angles from here.
1912
+ */
1913
+ getCameraOrbit(): {
1914
+ alpha: number;
1915
+ beta: number;
1916
+ distance: number;
1917
+ target: Vec3;
1918
+ };
1777
1919
  getCameraDistance(): number;
1778
1920
  setCameraDistance(d: number): void;
1779
1921
  getCameraAlpha(): number;
1780
1922
  setCameraAlpha(a: number): void;
1781
1923
  getCameraBeta(): number;
1782
1924
  setCameraBeta(b: number): void;
1925
+ /**
1926
+ * Roll the orbiting shot, radians — the lean alpha and beta cannot state.
1927
+ *
1928
+ * Tips the up vector about the eye→target line, so the camera stays exactly
1929
+ * where it was and keeps looking at exactly what it looked at. Everything the
1930
+ * orbit does still works underneath it: following a bone, dragging, zooming.
1931
+ *
1932
+ * A camera VMD carries its own roll and ignores this while it drives.
1933
+ */
1934
+ setCameraRoll(r: number): void;
1935
+ getCameraRoll(): number;
1783
1936
  /** Vertical field of view in radians (default π/4). While a camera VMD
1784
1937
  * drives the view it animates fov itself; the orbit value set here is
1785
1938
  * restored when the VMD releases the camera. */
@@ -1836,6 +1989,16 @@ export declare class Engine {
1836
1989
  /** Mirror softness, 0–1: 0 a polished mirror, 1 the softest blur level,
1837
1990
  * scaled by how far the reflected geometry sits behind the surface. */
1838
1991
  mirrorBlur?: number;
1992
+ /** How soft the received shadow's edge is, 0–1. 0 (default) is the sharp
1993
+ * kernel this has always used, to the bit; 1 spreads the taps fourteen
1994
+ * times as wide, which is the edge an overcast sky throws.
1995
+ *
1996
+ * A property of the LIGHT, applied where the light is received: the sun
1997
+ * in a scene is either a point source with a hard edge or a sky with
1998
+ * none, and a floor that always answers "hard" can only match one of
1999
+ * them. Above 0 the taps go from nine to sixteen, so leave it at 0 for
2000
+ * scenes that want the sharp edge and pay nothing. */
2001
+ shadowSoftness?: number;
1839
2002
  }): void;
1840
2003
  /**
1841
2004
  * The scene's positional lights — an ADDITIVE layer over the sun, which stays
@@ -1862,6 +2025,12 @@ export declare class Engine {
1862
2025
  }[] | null): void;
1863
2026
  /** How many positional lights the scene is carrying. */
1864
2027
  getLightCount(): number;
2028
+ /** Guarded, unlike most private writers here, because its callers are not:
2029
+ * setWorld/setSun are public and can be called before init() finishes
2030
+ * assigning `device` — a scene-settings effect firing on mount races the
2031
+ * engine's own async setup. The state write still lands immediately either
2032
+ * way; only the GPU upload defers, and setupLighting's own writeWorld/
2033
+ * writeSun calls during init pick up whatever was already set. */
1865
2034
  private updateLightBuffer;
1866
2035
  getStats(): EngineStats;
1867
2036
  runRenderLoop(callback?: () => void): void;
@@ -2001,7 +2170,100 @@ export declare class Engine {
2001
2170
  getModelTransform(name: string): ModelTransform | null;
2002
2171
  markVertexBufferDirty(modelNameOrModel?: string | Model): void;
2003
2172
  setSelectedMaterial(modelName: string | null, materialName: string | null): void;
2173
+ /** Show the transform gizmo on the selected bone. On by default. */
2174
+ setGizmoEnabled(on: boolean): void;
2175
+ /** A pointer-driven preview of a pick, not a pick itself — see pickMaterial
2176
+ * for the click that actually selects one. Cheap: a field write, nothing
2177
+ * rebuilt, safe to call every frame the pointer is over the canvas. */
2178
+ setHoveredMaterial(modelName: string | null, materialName: string | null): void;
2004
2179
  setSelectedBone(modelName: string | null, boneName: string | null): void;
2180
+ /**
2181
+ * Replace one named layer of overlay primitives. World space, drawn as given
2182
+ * until it is replaced. An empty list removes the layer.
2183
+ */
2184
+ setOverlay(layer: string, primitives: OverlayPrimitive[]): void;
2185
+ /** Drop one named layer, or every one. Live layers keep drawing. */
2186
+ clearOverlay(layer?: string): void;
2187
+ /**
2188
+ * The bone whose marker is nearest a point on the canvas, or null.
2189
+ *
2190
+ * On the CPU, and exact. A few hundred bones with known world positions is a
2191
+ * loop, not a render pass — and having the answer synchronously is what makes
2192
+ * cycling through overlapping bones possible at all. Only VERTICES justify GPU
2193
+ * picking, at tens of thousands.
2194
+ *
2195
+ * `x`/`y` are CSS pixels relative to the canvas, which is what a MouseEvent
2196
+ * gives once getBoundingClientRect is subtracted.
2197
+ *
2198
+ * It projects boneMarkerPositions, the same points the overlay draws markers
2199
+ * at, so the hit box cannot drift away from the circle you are aiming at.
2200
+ */
2201
+ pickBone(x: number, y: number, options?: {
2202
+ radiusPx?: number;
2203
+ modelName?: string;
2204
+ }): {
2205
+ modelName: string;
2206
+ boneName: string;
2207
+ boneIndex: number;
2208
+ } | null;
2209
+ /** Skinned positions for picking, grown on demand. One click's worth of work
2210
+ * reused across clicks — a model's vertex count does not change. */
2211
+ private materialPickScratch;
2212
+ /**
2213
+ * The material under a point on the canvas, or null for a miss.
2214
+ *
2215
+ * On the CPU, like pickBone, and for the same reason: a click (or a hover) is
2216
+ * rare and an answer you have synchronously is worth more than one that
2217
+ * arrives a frame later. Tens of thousands of triangles is a loop that costs
2218
+ * a few milliseconds ONCE, against a GPU id pass that costs an attachment and
2219
+ * a readback every frame whether anyone is pointing at the model or not.
2220
+ *
2221
+ * Skinned on the CPU with getSkinMatrices — the same matrices the vertex
2222
+ * shader uses — so the pick lands on the POSED mesh. Bind-pose geometry would
2223
+ * be right on a T-posed model and wrong on every animated one, which is
2224
+ * exactly when someone is clicking around a costume.
2225
+ *
2226
+ * Morph offsets are NOT applied: they move a face, never move it into another
2227
+ * material, and reading them back per click would cost more than the pick.
2228
+ *
2229
+ * `x`/`y` are CSS pixels relative to the canvas, as pickBone takes them.
2230
+ */
2231
+ pickMaterial(x: number, y: number, options?: {
2232
+ modelName?: string;
2233
+ }): {
2234
+ modelName: string;
2235
+ materialName: string;
2236
+ materialIndex: number;
2237
+ } | null;
2238
+ /** Draw an octahedron per bone of `modelName`, rebuilt each frame. Null off. */
2239
+ setBoneOverlay(modelName: string | null, options?: BoneOverlayOptions): void;
2240
+ /** Draw every rigidbody of `modelName` where the simulation has it, rebuilt
2241
+ * each frame. Null off. */
2242
+ setRigidbodyOverlay(modelName: string | null, options?: RigidbodyOverlayOptions): void;
2243
+ /** Draw a cross per joint of `modelName` plus dashed lines to the bodies it
2244
+ * holds together, rebuilt each frame. Null off. */
2245
+ setJointOverlay(modelName: string | null, options?: JointOverlayOptions): void;
2246
+ /**
2247
+ * Draw `modelName`'s mesh as a wireframe — its vertices and its topology.
2248
+ *
2249
+ * Skinned on the GPU from the model's own vertex buffer and skin matrices, so
2250
+ * it sits on the POSED mesh. The loader's CPU-side positions are bind pose: a
2251
+ * wireframe built from those looks right on a T-posed model and slides off
2252
+ * every animated one, which is exactly the state a user is in while looking at
2253
+ * weights.
2254
+ *
2255
+ * The edge list is deduplicated and built once, on the first frame this is on.
2256
+ *
2257
+ * `material` narrows the wireframe to one material's faces. The mesh still
2258
+ * writes depth in full, so the material reads as part of the body rather than
2259
+ * as a shell floating in front of it — which is the point of scoping it: you
2260
+ * are asking where this material's faces ARE, and an answer that ignores the
2261
+ * torso in front of them is not one.
2262
+ */
2263
+ setVertexOverlay(modelName: string | null, options?: {
2264
+ xray?: boolean;
2265
+ material?: string | null;
2266
+ }): void;
2005
2267
  /** A group's uniform buffer and its maps have the same lifetime — freeing one
2006
2268
  * without the other is how a re-apply leaks GPU memory a frame at a time. */
2007
2269
  private destroyInstall;
@@ -2021,6 +2283,15 @@ export declare class Engine {
2021
2283
  */
2022
2284
  setIKEnabled(enabled: boolean): void;
2023
2285
  getIKEnabled(): boolean;
2286
+ /**
2287
+ * Run the solver, or stop it.
2288
+ *
2289
+ * Turning it OFF snaps every body back onto its bone. Merely halting the step
2290
+ * leaves hair and skirts hanging wherever the simulation happened to be — a
2291
+ * pose nothing in the document describes, which is the opposite of what "off"
2292
+ * is asked for: you switch physics off to see what the RIG does, and a frozen
2293
+ * mid-swing is still the solver's answer, just a stale one.
2294
+ */
2024
2295
  setPhysicsEnabled(enabled: boolean): void;
2025
2296
  getPhysicsEnabled(): boolean;
2026
2297
  /**
@@ -2342,9 +2613,55 @@ export declare class Engine {
2342
2613
  private handleCanvasTouch;
2343
2614
  private performRaycast;
2344
2615
  private renderSelectionPasses;
2616
+ /** The index run `material` owns, or the whole list when it is null. Materials
2617
+ * are consecutive runs in declaration order, so the offset is a prefix sum —
2618
+ * the same walk the draw list does. Returns null for a name the model does
2619
+ * not have, which is what a stale selection looks like after a reload. */
2620
+ private materialIndexRange;
2621
+ /** A cache key no material can collide with — a PMX name is never empty and
2622
+ * never contains a NUL. */
2623
+ private static readonly SEAM_KEY;
2624
+ /**
2625
+ * @param material one material's own edges, or null for the whole mesh
2626
+ * @param seams every material's OUTLINE instead — the borders between them
2627
+ */
2628
+ private ensureEdgeBuffer;
2629
+ private renderWireframe;
2630
+ /** The bone overlay's options with `selected` filled in from setSelectedBone,
2631
+ * so clicking a bone highlights it without the host mirroring the state. An
2632
+ * explicit `selected` in the options still wins. */
2633
+ private boneOverlayOptions;
2634
+ private boneOptionsScratch;
2635
+ private overlayActive;
2636
+ private overlayModel;
2637
+ /** The primitives a live layer would draw right now. Same list the pass uses,
2638
+ * so a host can show it as data, diff it, or hit-test it on the CPU. */
2639
+ getOverlayPrimitives(layer: "bones" | "rigidbodies" | "joints"): OverlayPrimitive[];
2640
+ private ensureOverlayTargets;
2641
+ private ensureOverlayInstanceCapacity;
2642
+ private renderOverlayPass;
2345
2643
  private renderGizmoPass;
2346
2644
  private rotateVec3ByQuat;
2347
2645
  private unproject;
2646
+ /**
2647
+ * Where a point on the canvas lands on a horizontal plane.
2648
+ *
2649
+ * `px,py` are canvas-relative pixels, top-left origin — what a pointer event
2650
+ * gives you after subtracting the element's rect. Returns null when the ray
2651
+ * cannot reach the plane: parallel to it, or pointing the other way, which is
2652
+ * what a click on the sky above the horizon is.
2653
+ *
2654
+ * The one primitive a placement UI needs. Dragging a thing across the floor is
2655
+ * otherwise three sliders in world units, which asks someone to guess numbers
2656
+ * that have no visible relation to the picture they are looking at — and it
2657
+ * throws away the property that makes pointing work at all: under perspective,
2658
+ * moving something further away makes it smaller by exactly the right amount,
2659
+ * so position and size stop being two controls to tune against each other.
2660
+ */
2661
+ groundPointAt(px: number, py: number, planeY?: number): Vec3 | null;
2662
+ /** Hand the pointer to something else — a placement drag, a gizmo, a host's own
2663
+ * overlay — so the orbit does not also act on it. */
2664
+ setCameraInputLocked(locked: boolean): void;
2348
2665
  private buildMouseRay;
2349
2666
  private hitTestGizmo;
2350
2667
  private distSegmentRay;