reze-engine 0.36.1 → 0.38.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/engine.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { Camera } from "./camera";
2
2
  import { Mat4, Quat, Vec3 } from "./math";
3
+ import { MATERIAL_MORPH_MULTIPLY } from "./model";
3
4
  import { MORPH_COMPUTE_WGSL } from "./shaders/passes/morph";
4
5
  import { decodeTga } from "./tga-loader";
5
6
  import { VMDLoader } from "./vmd-loader";
@@ -2457,6 +2458,15 @@ export class Engine {
2457
2458
  setCameraBeta(b) {
2458
2459
  this.camera.beta = b;
2459
2460
  }
2461
+ /** Vertical field of view in radians (default π/4). While a camera VMD
2462
+ * drives the view it animates fov itself; the orbit value set here is
2463
+ * restored when the VMD releases the camera. */
2464
+ getCameraFov() {
2465
+ return this.camera.fov;
2466
+ }
2467
+ setCameraFov(fov) {
2468
+ this.camera.fov = fov;
2469
+ }
2460
2470
  // Step 5: Create lighting buffers
2461
2471
  setupLighting() {
2462
2472
  this.lightUniformBuffer = this.device.createBuffer({
@@ -2639,7 +2649,29 @@ export class Engine {
2639
2649
  await this.addModel(model, pmxPath, name);
2640
2650
  return model;
2641
2651
  }
2642
- async addModel(model, pmxPath, name, assetReader) {
2652
+ /** loadModel's folder/zip path for a stage. Shares the whole prelude — only
2653
+ * what the PMX becomes differs. */
2654
+ async loadStage(name, options) {
2655
+ const { model, pmxKey, reader } = await this.openPmxFromFiles(name, options);
2656
+ await this.addStage(model, pmxKey, { name, transform: options.transform, assetReader: reader });
2657
+ return model;
2658
+ }
2659
+ /** Read a PMX out of a picked folder / expanded zip. Shared by loadModel and
2660
+ * loadStage so the file-map and path handling exist in exactly one place. */
2661
+ async openPmxFromFiles(name, options) {
2662
+ const pmxFile = options.pmxFile ?? findFirstPmxFileInList(options.files);
2663
+ if (!pmxFile)
2664
+ throw new Error("No .pmx file found in the selected folder");
2665
+ const map = fileListToMap(options.files);
2666
+ // `||`, not `??`: flat-picked files carry webkitRelativePath === "" (see
2667
+ // fileListToMap) — `""` must fall through to the filename.
2668
+ const pmxKey = normalizeAssetPath(pmxFile.webkitRelativePath || pmxFile.name);
2669
+ const reader = createFileMapAssetReader(map);
2670
+ const model = await PmxLoader.loadFromReader(reader, pmxKey);
2671
+ model.setName(name);
2672
+ return { model, pmxKey, reader };
2673
+ }
2674
+ async addModel(model, pmxPath, name, assetReader, options) {
2643
2675
  const requested = name ?? model.name;
2644
2676
  let key = requested;
2645
2677
  let n = 1;
@@ -2649,9 +2681,42 @@ export class Engine {
2649
2681
  const reader = assetReader ?? createFetchAssetReader();
2650
2682
  const basePath = deriveBasePathFromPmxPath(pmxPath);
2651
2683
  model.setAssetContext(reader, basePath);
2652
- await this.setupModelInstance(key, model, basePath, reader);
2684
+ await this.setupModelInstance(key, model, basePath, reader, options?.stage ?? false);
2685
+ return key;
2686
+ }
2687
+ /**
2688
+ * Add a PMX as the scene's environment rather than as a character.
2689
+ *
2690
+ * A stage is the same geometry and the same materials — style groups and
2691
+ * shader graphs work on it unchanged, which is the whole reason pure-PMX
2692
+ * stages are worth supporting — but it is not a performer:
2693
+ *
2694
+ * - no physics. A stage's rigidbodies are set dressing for MMD's solver and
2695
+ * cost a full simulation island for scenery that never moves.
2696
+ * - no IK. Nothing drives a stage's chains, and solving them every frame is
2697
+ * pure waste on what is usually the heaviest mesh in the scene.
2698
+ * - no per-frame pose work while it is idle: with no clip and no morph
2699
+ * change there is nothing to recompute, so update is skipped entirely.
2700
+ * - it owns the floor. See groundIsSuppressed — the built-in ground plane
2701
+ * and a stage's own floor both sit at y=0 and z-fight.
2702
+ *
2703
+ * Bone and material morphs still apply, because that is how a stage's doors,
2704
+ * lifts and colour switches are rigged.
2705
+ */
2706
+ async addStage(model, pmxPath, options) {
2707
+ const key = await this.addModel(model, pmxPath, options?.name, options?.assetReader, { stage: true });
2708
+ if (options?.transform)
2709
+ this.setModelTransform(key, options.transform);
2653
2710
  return key;
2654
2711
  }
2712
+ /** True while a stage is in the scene, which is when the built-in ground plane
2713
+ * must not draw. */
2714
+ groundIsSuppressed() {
2715
+ for (const inst of this.modelInstances.values())
2716
+ if (inst.isStage)
2717
+ return true;
2718
+ return false;
2719
+ }
2655
2720
  removeModel(name) {
2656
2721
  const inst = this.modelInstances.get(name);
2657
2722
  if (!inst)
@@ -2699,8 +2764,9 @@ export class Engine {
2699
2764
  * character — its colliders won't scale; scale stages (which are typically physics-free).
2700
2765
  */
2701
2766
  setModelTransform(name, transform) {
2702
- const model = this.modelInstances.get(name)?.model;
2703
- if (!model)
2767
+ const inst = this.modelInstances.get(name);
2768
+ const model = inst?.model;
2769
+ if (!inst || !model)
2704
2770
  return;
2705
2771
  if (transform.position)
2706
2772
  model.setPosition(transform.position);
@@ -2710,6 +2776,11 @@ export class Engine {
2710
2776
  model.setScale(transform.scale);
2711
2777
  if (transform.visible !== undefined)
2712
2778
  model.setVisible(transform.visible);
2779
+ // The root transform is baked into the skin matrices, so moving a model is a
2780
+ // reason to re-upload them even though no pose pass ran. A cast member gets
2781
+ // one every frame anyway; an idle stage would otherwise never see the change
2782
+ // — which is exactly the case this API exists to serve.
2783
+ inst.skinMatricesDirty = true;
2713
2784
  }
2714
2785
  /** Read a model's scene transform (for serialization into a scene descriptor). */
2715
2786
  getModelTransform(name) {
@@ -2858,8 +2929,22 @@ export class Engine {
2858
2929
  let physicsMs = 0;
2859
2930
  this.forEachInstance((inst) => {
2860
2931
  const tAnim = performance.now();
2861
- const verticesChanged = inst.model.update(deltaTime, this.ikEnabled);
2932
+ // A stage never solves IK — nothing drives its chains — and skips the pose
2933
+ // pass entirely while it is idle. Morph changes still come through, since
2934
+ // that is the one thing a stage's controls do move.
2935
+ const stageIdle = inst.isStage && inst.model.isIdle();
2936
+ let verticesChanged = false;
2937
+ if (!stageIdle) {
2938
+ verticesChanged = inst.model.update(deltaTime, inst.isStage ? false : this.ikEnabled);
2939
+ inst.skinMatricesDirty = true;
2940
+ }
2862
2941
  animMs += performance.now() - tAnim;
2942
+ // Material morphs ride the same weight change as vertex morphs but land in
2943
+ // uniform buffers, so they consume their own flag — a model whose only
2944
+ // morphs are material morphs never enters the GPU vertex path below.
2945
+ if (inst.materialMorphTargets && inst.model.consumeAuxMorphDirty()) {
2946
+ this.applyMaterialMorphs(inst);
2947
+ }
2863
2948
  if (inst.gpuMorph) {
2864
2949
  // GPU path: on a weight change, upload effective weights (thresholding tiny values
2865
2950
  // to 0 to match the CPU skip) and flag the compute dispatch for this frame.
@@ -2935,7 +3020,7 @@ export class Engine {
2935
3020
  if (pass)
2936
3021
  pass.end();
2937
3022
  }
2938
- async setupModelInstance(name, model, basePath, assetReader) {
3023
+ async setupModelInstance(name, model, basePath, assetReader, isStage = false) {
2939
3024
  const vertices = model.getVertices();
2940
3025
  const skinning = model.getSkinning();
2941
3026
  const skeleton = model.getSkeleton();
@@ -2975,7 +3060,10 @@ export class Engine {
2975
3060
  });
2976
3061
  this.device.queue.writeBuffer(indexBuffer, 0, indices);
2977
3062
  const rbs = model.getRigidbodies();
2978
- const physics = rbs.length > 0 ? new RezePhysics(rbs, model.getJoints()) : null;
3063
+ // A stage never simulates, so its bodies are never built — constructing the
3064
+ // solver for the heaviest mesh in the scene and dropping it afterwards was
3065
+ // both wasted work and an invariant maintained in the wrong place.
3066
+ const physics = !isStage && rbs.length > 0 ? new RezePhysics(rbs, model.getJoints()) : null;
2979
3067
  // Adopt the scene's air, or a model added mid-session would fall under
2980
3068
  // different gravity from the ones already on stage.
2981
3069
  if (physics) {
@@ -3022,7 +3110,13 @@ export class Engine {
3022
3110
  mainPerInstanceBindGroup,
3023
3111
  pickPerInstanceBindGroup,
3024
3112
  pickDrawCalls: [],
3113
+ isStage,
3114
+ // Seeded true: the bind pose has to reach the GPU once before any frame.
3115
+ skinMatricesDirty: true,
3025
3116
  hiddenMaterials: new Set(),
3117
+ morphHiddenMaterials: new Set(),
3118
+ materialMorphTargets: null,
3119
+ materialMorphByIndex: null,
3026
3120
  physics,
3027
3121
  vertexBufferNeedsUpdate: false,
3028
3122
  gpuMorph,
@@ -3247,9 +3341,28 @@ export class Engine {
3247
3341
  const meshIndices = model.getIndices();
3248
3342
  // 頭 bone index for the eye shader's rear-view gate (-1 when absent).
3249
3343
  const headBoneIndex = model.getSkeleton().bones.findIndex((b) => b.name === "頭");
3344
+ // Materials a type-8 morph can reach. -1 in an offset means "all of them",
3345
+ // so the presence of ANY material morph makes every material a target.
3346
+ const morphedMaterials = new Set();
3347
+ for (const morph of model.getMorphing().morphs) {
3348
+ if (morph.type !== 8 || !morph.materialOffsets)
3349
+ continue;
3350
+ for (const off of morph.materialOffsets) {
3351
+ if (off.materialIndex < 0)
3352
+ for (let i = 0; i < materials.length; i++)
3353
+ morphedMaterials.add(i);
3354
+ else
3355
+ morphedMaterials.add(off.materialIndex);
3356
+ }
3357
+ }
3358
+ const morphTargets = [];
3250
3359
  let currentIndexOffset = 0;
3251
3360
  let materialId = 0;
3361
+ // The PMX index, which is what a material morph points at — distinct from
3362
+ // materialId, which only counts materials that produced a draw.
3363
+ let pmxMaterialIndex = -1;
3252
3364
  for (const mat of materials) {
3365
+ pmxMaterialIndex++;
3253
3366
  const indexCount = mat.vertexCount;
3254
3367
  if (indexCount === 0)
3255
3368
  continue;
@@ -3299,6 +3412,19 @@ export class Engine {
3299
3412
  }
3300
3413
  const materialUniformBuffer = this.createMaterialUniformBuffer(prefix + mat.name, mat, sphereMode, headBoneIndex);
3301
3414
  inst.gpuBuffers.push(materialUniformBuffer);
3415
+ if (morphedMaterials.has(pmxMaterialIndex)) {
3416
+ const base = this.materialUniformData(mat, sphereMode, headBoneIndex);
3417
+ morphTargets.push({
3418
+ pmxIndex: pmxMaterialIndex,
3419
+ materialName: mat.name,
3420
+ buffer: materialUniformBuffer,
3421
+ base,
3422
+ work: new Float32Array(base.length),
3423
+ // Seeded from base: that is what createMaterialUniformBuffer already
3424
+ // uploaded, so an unmorphed material never writes a first time.
3425
+ last: Float32Array.from(base),
3426
+ });
3427
+ }
3302
3428
  const textureView = diffuseTexture.createView();
3303
3429
  const baseBindGroupEntries = [
3304
3430
  { binding: 0, resource: textureView },
@@ -3314,8 +3440,13 @@ export class Engine {
3314
3440
  // its own hull where it is see-through instead of us skipping it here.
3315
3441
  // Drawn interleaved right after this material's color draw (babylon-mmd's
3316
3442
  // per-mesh afterRender outline stage) — see drawMaterials.
3443
+ // Stages get no outline hulls. The inverted hull is a SECOND full draw of
3444
+ // the material's geometry, and stage PMX routinely set the edge flag across
3445
+ // every material — on the heaviest mesh in the scene that doubles the
3446
+ // geometry submitted per frame to draw cartoon outlines around
3447
+ // architecture, which is not the look anyone is after.
3317
3448
  let outline;
3318
- if ((mat.edgeFlag & 0x10) !== 0 && mat.edgeSize > 0) {
3449
+ if (!inst.isStage && (mat.edgeFlag & 0x10) !== 0 && mat.edgeSize > 0) {
3319
3450
  const materialUniformData = new Float32Array([
3320
3451
  mat.edgeColor[0],
3321
3452
  mat.edgeColor[1],
@@ -3371,10 +3502,17 @@ export class Engine {
3371
3502
  // by render-class when groups are assigned. Array.sort is stable → PMX order preserved
3372
3503
  // within a bucket.
3373
3504
  this.sortDrawCalls(inst);
3374
- }
3375
- createMaterialUniformBuffer(label, mat, sphereMode, headBoneIndex) {
3376
- // Matches the WGSL MaterialUniforms struct in common.ts — 64 bytes
3377
- // (diffuse+alpha | ambient+shininess | specular+sphereMode | headIdx+pad).
3505
+ inst.materialMorphTargets = morphTargets.length > 0 ? morphTargets : null;
3506
+ inst.materialMorphByIndex = inst.materialMorphTargets
3507
+ ? new Map(morphTargets.map((t) => [t.pmxIndex, t]))
3508
+ : null;
3509
+ // Seed from the current weights: a scene can open with a switch already on.
3510
+ if (inst.materialMorphTargets)
3511
+ this.applyMaterialMorphs(inst);
3512
+ }
3513
+ /** Matches the WGSL MaterialUniforms struct in common.ts — 64 bytes
3514
+ * (diffuse+alpha | ambient+shininess | specular+sphereMode | headIdx+pad). */
3515
+ materialUniformData(mat, sphereMode, headBoneIndex) {
3378
3516
  const data = new Float32Array(16);
3379
3517
  data[0] = mat.diffuse[0];
3380
3518
  data[1] = mat.diffuse[1];
@@ -3389,7 +3527,102 @@ export class Engine {
3389
3527
  data[10] = mat.specular[2];
3390
3528
  data[11] = sphereMode;
3391
3529
  data[12] = headBoneIndex;
3392
- return this.createUniformBuffer(`material uniform: ${label}`, data);
3530
+ return data;
3531
+ }
3532
+ createMaterialUniformBuffer(label, mat, sphereMode, headBoneIndex) {
3533
+ return this.createUniformBuffer(`material uniform: ${label}`, this.materialUniformData(mat, sphereMode, headBoneIndex));
3534
+ }
3535
+ /**
3536
+ * Re-derive every morph-targeted material's uniform block from base and push
3537
+ * the ones that moved.
3538
+ *
3539
+ * Blend maths follow MMD (and babylon-mmd's _applyMaterialMorph): multiply
3540
+ * lerps from base toward base*morph, add offsets from base. Weight 0 must
3541
+ * therefore land exactly on base, which is why this recomputes rather than
3542
+ * accumulates.
3543
+ *
3544
+ * A material driven to zero alpha is dropped from the draw instead of being
3545
+ * written through: the opaque/transparent bucket is decided at load from the
3546
+ * PMX alpha, so an opaque draw cannot become see-through by uniform alone.
3547
+ * Full-off is the switch stage artists actually ship (帽子消失 and friends);
3548
+ * a partial fade on a material that loaded opaque still will not blend.
3549
+ */
3550
+ applyMaterialMorphs(inst) {
3551
+ const targets = inst.materialMorphTargets;
3552
+ if (!targets)
3553
+ return;
3554
+ const morphs = inst.model.getMorphing().morphs;
3555
+ const weights = inst.model.getEffectiveMorphWeights();
3556
+ for (const target of targets) {
3557
+ target.work.set(target.base);
3558
+ }
3559
+ for (let i = 0; i < morphs.length; i++) {
3560
+ const w = weights[i];
3561
+ if (w < 0.0001)
3562
+ continue;
3563
+ const morph = morphs[i];
3564
+ if (morph.type !== 8 || !morph.materialOffsets)
3565
+ continue;
3566
+ for (const off of morph.materialOffsets) {
3567
+ // A named material resolves in one lookup. Only the -1 wildcard walks
3568
+ // every target — and once any offset uses it, every material in the
3569
+ // model is a target, so scanning per offset would be quadratic on the
3570
+ // large stages this is meant to serve.
3571
+ const hit = off.materialIndex >= 0 ? inst.materialMorphByIndex?.get(off.materialIndex) : undefined;
3572
+ const affected = off.materialIndex >= 0 ? (hit ? [hit] : []) : targets;
3573
+ for (const target of affected) {
3574
+ const d = target.work;
3575
+ if (off.offsetType === MATERIAL_MORPH_MULTIPLY) {
3576
+ d[0] += (d[0] * off.diffuse[0] - d[0]) * w;
3577
+ d[1] += (d[1] * off.diffuse[1] - d[1]) * w;
3578
+ d[2] += (d[2] * off.diffuse[2] - d[2]) * w;
3579
+ d[3] += (d[3] * off.diffuse[3] - d[3]) * w;
3580
+ d[4] += (d[4] * off.ambient[0] - d[4]) * w;
3581
+ d[5] += (d[5] * off.ambient[1] - d[5]) * w;
3582
+ d[6] += (d[6] * off.ambient[2] - d[6]) * w;
3583
+ d[7] += (d[7] * off.shininess - d[7]) * w;
3584
+ d[8] += (d[8] * off.specular[0] - d[8]) * w;
3585
+ d[9] += (d[9] * off.specular[1] - d[9]) * w;
3586
+ d[10] += (d[10] * off.specular[2] - d[10]) * w;
3587
+ }
3588
+ else {
3589
+ d[0] += off.diffuse[0] * w;
3590
+ d[1] += off.diffuse[1] * w;
3591
+ d[2] += off.diffuse[2] * w;
3592
+ d[3] += off.diffuse[3] * w;
3593
+ d[4] += off.ambient[0] * w;
3594
+ d[5] += off.ambient[1] * w;
3595
+ d[6] += off.ambient[2] * w;
3596
+ d[7] += off.shininess * w;
3597
+ d[8] += off.specular[0] * w;
3598
+ d[9] += off.specular[1] * w;
3599
+ d[10] += off.specular[2] * w;
3600
+ }
3601
+ }
3602
+ }
3603
+ }
3604
+ inst.morphHiddenMaterials.clear();
3605
+ for (const target of targets) {
3606
+ const d = target.work;
3607
+ // Alpha is the switch; clamp the rest so a stacked multiply cannot send a
3608
+ // colour negative and light the material from the inside.
3609
+ for (let k = 0; k < 11; k++)
3610
+ if (d[k] < 0)
3611
+ d[k] = 0;
3612
+ if (d[3] < 0.0001)
3613
+ inst.morphHiddenMaterials.add(target.materialName);
3614
+ let changed = false;
3615
+ for (let k = 0; k < 11; k++) {
3616
+ if (d[k] !== target.last[k]) {
3617
+ changed = true;
3618
+ break;
3619
+ }
3620
+ }
3621
+ if (!changed)
3622
+ continue;
3623
+ target.last.set(d);
3624
+ this.device.queue.writeBuffer(target.buffer, 0, d);
3625
+ }
3393
3626
  }
3394
3627
  createUniformBuffer(label, data) {
3395
3628
  const buffer = this.device.createBuffer({
@@ -3401,7 +3634,7 @@ export class Engine {
3401
3634
  return buffer;
3402
3635
  }
3403
3636
  shouldRenderDrawCall(inst, drawCall) {
3404
- return !inst.hiddenMaterials.has(drawCall.materialName);
3637
+ return !inst.hiddenMaterials.has(drawCall.materialName) && !inst.morphHiddenMaterials.has(drawCall.materialName);
3405
3638
  }
3406
3639
  async createTextureFromLogicalPath(inst, logicalPath) {
3407
3640
  const cacheKey = logicalPath;
@@ -3526,6 +3759,12 @@ export class Engine {
3526
3759
  this.device.queue.submit([encoder.finish()]);
3527
3760
  }
3528
3761
  renderGround(pass) {
3762
+ // A stage brings its own floor. Both sit at y=0, so drawing the built-in
3763
+ // plane underneath produces z-fighting across the whole scene — enforced
3764
+ // here rather than left to callers, who cannot see the conflict coming.
3765
+ // hasGround is left alone: remove the stage and the ground comes back.
3766
+ if (this.groundIsSuppressed())
3767
+ return;
3529
3768
  if (!this.hasGround || !this.groundVertexBuffer || !this.groundIndexBuffer || !this.groundDrawCall)
3530
3769
  return;
3531
3770
  pass.setPipeline(this.groundShadowPipeline);
@@ -4594,8 +4833,14 @@ export class Engine {
4594
4833
  }
4595
4834
  updateSkinMatrices() {
4596
4835
  this.forEachInstance((inst) => {
4836
+ // Only a pose pass can change these, and an idle stage did not run one —
4837
+ // re-uploading bones×64 bytes for scenery that never moves is the one
4838
+ // per-frame cost a stage would otherwise still pay in full.
4839
+ if (!inst.skinMatricesDirty)
4840
+ return;
4597
4841
  const skinMatrices = inst.model.getSkinMatrices();
4598
4842
  this.device.queue.writeBuffer(inst.skinMatrixBuffer, 0, skinMatrices.buffer, skinMatrices.byteOffset, skinMatrices.byteLength);
4843
+ inst.skinMatricesDirty = false;
4599
4844
  });
4600
4845
  }
4601
4846
  // frameIntervalMs is the true vsync-to-vsync frame interval (render dt), NOT the CPU
package/dist/index.d.ts CHANGED
@@ -14,7 +14,7 @@ export { BODY_GRAPH } from "./graph/presets/body";
14
14
  export { STOCKINGS_GRAPH } from "./graph/presets/stockings";
15
15
  export { EYE_GRAPH } from "./graph/presets/eye";
16
16
  export { FACE_GRAPH } from "./graph/presets/face";
17
- export { Model, type ClipEventInfo } from "./model";
17
+ export { Model, MATERIAL_MORPH_MULTIPLY, MATERIAL_MORPH_ADD, type ClipEventInfo, type RootMotionProfile, type Morph, type Morphing, type BoneMorphOffset, type MaterialMorphOffset, type UvMorphOffset, } from "./model";
18
18
  export { Vec3, Quat, Mat4, easeInOut, type EulerOrder } from "./math";
19
19
  export type { AnimationClip, AnimationPlayOptions, AnimationProgress, BlendEntry, BoneKeyframe, IkKeyframe, MorphKeyframe, BoneInterpolation, ControlPoint, } from "./animation";
20
20
  export { LocomotionController, type LocomotionClips, type LocomotionOptions, type LocomotionPose, type StrafeClipEntry, type TurnClipEntry, type RunTurnClipEntry, type StopClipEntry, } from "./locomotion";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/dist/index.js CHANGED
@@ -12,7 +12,7 @@ export { BODY_GRAPH } from "./graph/presets/body";
12
12
  export { STOCKINGS_GRAPH } from "./graph/presets/stockings";
13
13
  export { EYE_GRAPH } from "./graph/presets/eye";
14
14
  export { FACE_GRAPH } from "./graph/presets/face";
15
- export { Model } from "./model";
15
+ export { Model, MATERIAL_MORPH_MULTIPLY, MATERIAL_MORPH_ADD, } from "./model";
16
16
  export { Vec3, Quat, Mat4, easeInOut } from "./math";
17
17
  export { LocomotionController, } from "./locomotion";
18
18
  export { AnimationStateMachine } from "./state-machine";
@@ -177,6 +177,13 @@ export declare class LocomotionController {
177
177
  setDrive(forward: number, steer: number, sprint?: boolean): void;
178
178
  /** Place the character (initial spawn or respawn). */
179
179
  teleport(x: number, y: number, z: number, yaw?: number): void;
180
+ /** Teleport PLUS a hard reset of every transient motion commitment: any
181
+ * in-flight authored stop/turn (whose stored start position would otherwise
182
+ * keep driving the root from where it began), the exit ghost, speed and
183
+ * momentum, the heading-hold gate, and the held inputs. For handing the
184
+ * root back after an externally-driven action (a root-motion clip state):
185
+ * wherever the action ended is simply where she now stands. */
186
+ reset(x: number, y: number, z: number, yaw?: number): void;
180
187
  /** Strafe mode: hold the body at this world yaw (a camera forward, a lock-on target)
181
188
  * while setMove's vector drives the directional strafe ring — requires
182
189
  * clips.strafeRun. null returns to turn-toward-movement. */
@@ -1 +1 @@
1
- {"version":3,"file":"locomotion.d.ts","sourceRoot":"","sources":["../src/locomotion.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAA;AAC/B,OAAO,EAAO,KAAK,UAAU,EAAE,MAAM,aAAa,CAAA;AAClD,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAA;AAEnC,MAAM,WAAW,eAAe;IAC9B,gDAAgD;IAChD,IAAI,EAAE,MAAM,CAAA;IACZ,kGAAkG;IAClG,KAAK,EAAE,MAAM,CAAA;IACb,kGAAkG;IAClG,KAAK,EAAE,MAAM,CAAA;CACd;AAED,MAAM,WAAW,aAAa;IAC5B,gDAAgD;IAChD,IAAI,EAAE,MAAM,CAAA;IACZ,8EAA8E;IAC9E,KAAK,EAAE,MAAM,CAAA;IACb,oFAAoF;IACpF,QAAQ,EAAE,MAAM,CAAA;CACjB;AAED,MAAM,WAAW,gBAAgB;IAC/B,gDAAgD;IAChD,IAAI,EAAE,MAAM,CAAA;IACZ,gFAAgF;IAChF,KAAK,EAAE,MAAM,CAAA;IACb,6EAA6E;IAC7E,QAAQ,EAAE,MAAM,CAAA;IAChB;qFACiF;IACjF,OAAO,EAAE,MAAM,EAAE,CAAA;IACjB,IAAI,EAAE,KAAK,GAAG,QAAQ,CAAA;IACtB,IAAI,EAAE,GAAG,GAAG,GAAG,CAAA;CAChB;AAED,MAAM,WAAW,aAAa;IAC5B,gDAAgD;IAChD,IAAI,EAAE,MAAM,CAAA;IACZ,gFAAgF;IAChF,QAAQ,EAAE,MAAM,CAAA;IAChB;8EAC0E;IAC1E,OAAO,EAAE,MAAM,EAAE,CAAA;IACjB,IAAI,EAAE,KAAK,GAAG,QAAQ,CAAA;IACtB,IAAI,EAAE,GAAG,GAAG,GAAG,CAAA;CAChB;AAED,MAAM,WAAW,eAAe;IAC9B,oEAAoE;IACpE,IAAI,EAAE,MAAM,CAAA;IACZ,GAAG,EAAE,MAAM,CAAA;IACX,MAAM,CAAC,EAAE,MAAM,CAAA;IACf;kEAC8D;IAC9D,SAAS,CAAC,EAAE,eAAe,EAAE,CAAA;IAC7B,YAAY,CAAC,EAAE,eAAe,EAAE,CAAA;IAChC;;mFAE+E;IAC/E,WAAW,CAAC,EAAE,aAAa,EAAE,CAAA;IAC7B;;;qCAGiC;IACjC,OAAO,CAAC,EAAE,gBAAgB,EAAE,CAAA;IAC5B;;wEAEoE;IACpE,IAAI,CAAC,EAAE,aAAa,EAAE,CAAA;CACvB;AAED,MAAM,WAAW,iBAAiB;IAChC;;oEAEgE;IAChE,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB;;qDAEiD;IACjD,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,iFAAiF;IACjF,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,sFAAsF;IACtF,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,uEAAuE;IACvE,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB;iFAC6E;IAC7E,oBAAoB,CAAC,EAAE,MAAM,CAAA;IAC7B;+EAC2E;IAC3E,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB;2CACuC;IACvC,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,oEAAoE;IACpE,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB;;0DAEsD;IACtD,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,sEAAsE;IACtE,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,6EAA6E;IAC7E,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB;qEACiE;IACjE,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED;;iEAEiE;AACjE,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,IAAI,CAAA;IACd,wDAAwD;IACxD,GAAG,EAAE,MAAM,CAAA;IACX,uEAAuE;IACvE,QAAQ,EAAE,IAAI,CAAA;IACd,qDAAqD;IACrD,UAAU,EAAE,MAAM,CAAA;CACnB;AAaD,qBAAa,oBAAoB;IAC/B,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAO;IAC7B,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,WAAW,CAA4B;IAC/C,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAiB;IACvC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAQ;IACjC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAQ;IACpC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAQ;IACtC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAQ;IACrC,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAQ;IACzC,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAQ;IACzC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAQ;IACtC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAQ;IACtC,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAQ;IACvC,OAAO,CAAC,OAAO,CAAsD;IACrE,OAAO,CAAC,UAAU,CAOH;IACf;oEACgE;IAChE,OAAO,CAAC,SAAS,CAAmE;IACpF,OAAO,CAAC,WAAW,CAAI;IACvB,OAAO,CAAC,WAAW,CAAI;IACvB,OAAO,CAAC,WAAW,CAAI;IACvB,OAAO,CAAC,QAAQ,CAkBD;IACf,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAc;IAC1C,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAQ;IAElC,OAAO,CAAC,MAAM,CAAI;IAClB,OAAO,CAAC,MAAM,CAAI;IAClB,OAAO,CAAC,WAAW,CAAQ;IAE3B,OAAO,CAAC,QAAQ,CAAQ;IACxB,OAAO,CAAC,YAAY,CAAI;IACxB,OAAO,CAAC,UAAU,CAAI;IACtB,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAQ;IAClC,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAQ;IAEvC,OAAO,CAAC,UAAU,CAAI;IACtB;;4EAEwE;IACxE,OAAO,CAAC,WAAW,CAAI;IACvB,OAAO,CAAC,GAAG,CAAI;IAIf,OAAO,CAAC,IAAI,CAAI;IAChB,OAAO,CAAC,IAAI,CAAI;IAChB,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAoB;IAC7C,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAuB;IAChD,OAAO,CAAC,QAAQ,CAAI;IACpB,OAAO,CAAC,SAAS,CAAI;IAErB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAc;IACtC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAgB;IAErC,OAAO,CAAC,SAAS,CAAsB;IACvC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAA0B;IACpD,OAAO,CAAC,QAAQ,CAAC,YAAY,CAA0B;IACvD,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAc;gBAEhC,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,eAAe,EAAE,OAAO,CAAC,EAAE,iBAAiB;IAmC7E;;yDAEqD;IACrD,OAAO,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,MAAM,UAAQ,GAAG,IAAI;IAYnD;;;2EAGuE;IACvE,QAAQ,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,UAAQ,GAAG,IAAI;IAO9D,sDAAsD;IACtD,QAAQ,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,GAAG,SAAI,GAAG,IAAI;IAKxD;;iEAE6D;IAC7D,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI;IAInC,WAAW,IAAI,IAAI;IAInB,4FAA4F;IAC5F,MAAM,IAAI,IAAI;IAKd;gFAC4E;IAC5E,eAAe,IAAI,UAAU,EAAE,GAAG,IAAI;IAItC,OAAO,CAAC,IAAI;IAKZ,OAAO,CAAC,YAAY;IAKpB;+EAC2E;IAC3E,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,cAAc;IAkPlC;+EAC2E;IAC3E,OAAO,CAAC,QAAQ;IAYhB;;mFAE+E;IAC/E,OAAO,CAAC,YAAY;IA2FpB;;;4EAGwE;IACxE,OAAO,CAAC,cAAc;IA6CtB;;wDAEoD;IACpD,OAAO,CAAC,UAAU;IA4ElB,4EAA4E;IAC5E,OAAO,CAAC,MAAM,CAAC,SAAS;IAQxB;;;kEAG8D;IAC9D,OAAO,CAAC,aAAa;CAkDtB"}
1
+ {"version":3,"file":"locomotion.d.ts","sourceRoot":"","sources":["../src/locomotion.ts"],"names":[],"mappings":"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"}
@@ -113,6 +113,33 @@ export class LocomotionController {
113
113
  this.position.setXYZ(x, y, z);
114
114
  this.yaw = yaw;
115
115
  }
116
+ /** Teleport PLUS a hard reset of every transient motion commitment: any
117
+ * in-flight authored stop/turn (whose stored start position would otherwise
118
+ * keep driving the root from where it began), the exit ghost, speed and
119
+ * momentum, the heading-hold gate, and the held inputs. For handing the
120
+ * root back after an externally-driven action (a root-motion clip state):
121
+ * wherever the action ended is simply where she now stands. */
122
+ reset(x, y, z, yaw = 0) {
123
+ this.position.setXYZ(x, y, z);
124
+ this.yaw = wrapAngle(yaw);
125
+ this.dirX = Math.sin(this.yaw);
126
+ this.dirZ = Math.cos(this.yaw);
127
+ this.speedLevel = 0;
128
+ this.recentSpeed = 0;
129
+ this.headingHold = 0;
130
+ this.headingDirX = 0;
131
+ this.headingDirY = 0;
132
+ this.gaitPhase = 0;
133
+ this.stopping = null;
134
+ this.turning = null;
135
+ this.runTurning = null;
136
+ this.exitGhost = null;
137
+ this.inputX = 0;
138
+ this.inputY = 0;
139
+ this.inputSprint = false;
140
+ this.inputForward = 0;
141
+ this.inputSteer = 0;
142
+ }
116
143
  /** Strafe mode: hold the body at this world yaw (a camera forward, a lock-on target)
117
144
  * while setMove's vector drives the directional strafe ring — requires
118
145
  * clips.strafeRun. null returns to turn-toward-movement. */
package/dist/model.d.ts CHANGED
@@ -9,6 +9,14 @@ export interface ClipEventInfo {
9
9
  /** The clip's blend weight at the moment of firing. */
10
10
  weight: number;
11
11
  }
12
+ /** A clip's authored horizontal root path, lifted off センター by
13
+ * Model.extractRootMotion: per-key offsets from the FIRST key, raw clip
14
+ * units, clip space (rest facing -Z). `frames` are VMD frames (30fps). */
15
+ export interface RootMotionProfile {
16
+ frames: number[];
17
+ x: number[];
18
+ z: number[];
19
+ }
12
20
  export interface Texture {
13
21
  path: string;
14
22
  name: string;
@@ -78,11 +86,44 @@ export interface GroupMorphReference {
78
86
  morphIndex: number;
79
87
  ratio: number;
80
88
  }
89
+ export interface BoneMorphOffset {
90
+ boneIndex: number;
91
+ translation: [number, number, number];
92
+ /** Rotation quaternion (x, y, z, w). */
93
+ rotation: [number, number, number, number];
94
+ }
95
+ /** PMX material-morph blend mode. Multiply lerps toward base*morph; add offsets from base. */
96
+ export declare const MATERIAL_MORPH_MULTIPLY = 0;
97
+ export declare const MATERIAL_MORPH_ADD = 1;
98
+ export interface MaterialMorphOffset {
99
+ /** -1 targets EVERY material in the model, per the PMX spec. */
100
+ materialIndex: number;
101
+ /** MATERIAL_MORPH_MULTIPLY | MATERIAL_MORPH_ADD */
102
+ offsetType: number;
103
+ diffuse: [number, number, number, number];
104
+ specular: [number, number, number];
105
+ shininess: number;
106
+ ambient: [number, number, number];
107
+ edgeColor: [number, number, number, number];
108
+ edgeSize: number;
109
+ textureCoeff: [number, number, number, number];
110
+ sphereCoeff: [number, number, number, number];
111
+ toonCoeff: [number, number, number, number];
112
+ }
113
+ export interface UvMorphOffset {
114
+ vertexIndex: number;
115
+ /** (u, v, z, w) — only u/v apply to the base channel. */
116
+ offset: [number, number, number, number];
117
+ }
81
118
  export interface Morph {
82
119
  name: string;
120
+ /** 0=group, 1=vertex, 2=bone, 3–7=UV, 8=material, 9=flip, 10=impulse. */
83
121
  type: number;
84
122
  vertexOffsets: VertexMorphOffset[];
85
123
  groupReferences?: GroupMorphReference[];
124
+ boneOffsets?: BoneMorphOffset[];
125
+ materialOffsets?: MaterialMorphOffset[];
126
+ uvOffsets?: UvMorphOffset[];
86
127
  }
87
128
  export interface Morphing {
88
129
  morphs: Morph[];
@@ -146,6 +187,14 @@ export declare class Model {
146
187
  private runtimeSkeleton;
147
188
  private runtimeMorph;
148
189
  private morphsDirty;
190
+ private boneMorphPlan;
191
+ private boneMorphBones;
192
+ private boneMorphRestoreR;
193
+ private boneMorphRestoreT;
194
+ private boneMorphApplied;
195
+ /** A full pose pass has run, so the world matrices are real. See isIdle. */
196
+ private posedOnce;
197
+ private auxMorphDirty;
149
198
  private _position;
150
199
  private _rotation;
151
200
  private _scale;
@@ -189,6 +238,33 @@ export declare class Model {
189
238
  private buildDeformOrder;
190
239
  private initializeTweenBuffers;
191
240
  private initializeRuntimeMorph;
241
+ /**
242
+ * Bone morphs (type 2) compose over whatever the pose sources produced, the
243
+ * same way boneRotationOffsets do — they are an offset on the animated local
244
+ * transform, not a replacement for it. Re-applied every frame because each
245
+ * pose source rewrites the locals it touches.
246
+ *
247
+ * Stages are the reason this exists: a door or a lift is rigged as a bone
248
+ * morph and there is no VMD anywhere that drives it.
249
+ */
250
+ private applyBoneMorphs;
251
+ /** True (once) when effective morph weights changed — the engine re-derives
252
+ * material-morph uniforms from it. Separate from the GPU vertex path's flag
253
+ * so both can consume the same change. */
254
+ consumeAuxMorphDirty(): boolean;
255
+ /**
256
+ * Morph indices this model can actually act on, so a UI never offers a control
257
+ * that moves nothing.
258
+ *
259
+ * Driven directly: vertex (1), bone (2), material (8). Excluded: UV (3–7),
260
+ * which are parsed and kept but not yet applied; flip (9) and impulse (10),
261
+ * which are PMX 2.1 and would need the rigidbody solver.
262
+ *
263
+ * A group morph (0) is only as alive as what it points at — one referencing
264
+ * nothing but UV morphs is just as dead as the UV morphs themselves, so it is
265
+ * resolved rather than assumed.
266
+ */
267
+ getSupportedMorphIndices(): number[];
192
268
  private updateTweens;
193
269
  getVertices(): Float32Array<ArrayBuffer>;
194
270
  getTextures(): Texture[];
@@ -239,6 +315,24 @@ export declare class Model {
239
315
  resetAllBones(): void;
240
316
  resetAllMorphs(): void;
241
317
  getClip(name: string): AnimationClip | null;
318
+ /** Lift a clip's authored horizontal root path off センター so the host can
319
+ * drive the MODEL ROOT along it — game-style root motion. The clip keeps
320
+ * its vertical bob but its horizontal センター flattens to `rest` (default
321
+ * 0,0): a displaced first frame — common in per-pose game exports — would
322
+ * otherwise ride the whole clip as a constant and visibly slide off during
323
+ * the exit crossfade. Pass the pack's NEUTRAL standing offset (the idle
324
+ * clip's first センター key) as `rest` so the flattened pose blends into
325
+ * the surrounding states without even a micro-slide. The removed path
326
+ * returns RELATIVE TO THE FIRST KEY, raw clip units, clip space (rest
327
+ * facing -Z). Leg-IK target position tracks lose the same path so feet
328
+ * keep oscillating around the body. Call once per clip, after
329
+ * loadVmd/loadClip; null if the clip or its センター track doesn't exist. */
330
+ extractRootMotion(name: string, rest?: {
331
+ x?: number;
332
+ z?: number;
333
+ }): RootMotionProfile | null;
334
+ /** Linear sample of a keyed scalar track at fractional frame f. */
335
+ private static sampleTrack;
242
336
  exportVmd(name: string): ArrayBuffer;
243
337
  play(): void;
244
338
  play(name: string): boolean;
@@ -326,6 +420,19 @@ export declare class Model {
326
420
  * animationState, already ticked by update), shape the weight with easeInOut,
327
421
  * and hand both to the blend sampler. Holds in place while paused. */
328
422
  private applyCrossfade;
423
+ /**
424
+ * Nothing can have moved this frame: no clip, no blend, no live tween, no
425
+ * morph weight change.
426
+ *
427
+ * Environment geometry is in this state almost every frame, so the engine
428
+ * skips the whole pose pass — sampling, world matrices, and the skin-matrix
429
+ * upload — for a stage that reports idle. A stage is usually the heaviest mesh
430
+ * in the scene and the one that never moves; paying a full pose pass for it
431
+ * every frame is the thing worth not doing.
432
+ */
433
+ isIdle(): boolean;
434
+ /** Any live rotation / translation / morph tween. */
435
+ private hasActiveTweens;
329
436
  update(deltaTime: number, ikEnabled?: boolean): boolean;
330
437
  private solveIKChains;
331
438
  private ikComputedSet;