@laplace.live/pixijs-live2d 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -3240,6 +3240,14 @@ export declare enum MotionPreloadStrategy {
3240
3240
  /** No preload. */
3241
3241
  NONE = "NONE"
3242
3242
  }
3243
+ /**
3244
+ * What plays when nothing else does: a motion to loop, `'idle-group'` for a random motion of
3245
+ * the idle group (the default), or `null` to hold still.
3246
+ */
3247
+ export type IdleMotion = {
3248
+ group: string;
3249
+ index: number;
3250
+ } | 'idle-group' | null;
3243
3251
  /**
3244
3252
  * Handles the motion playback for Live2D models.
3245
3253
  * Responsible for loading, playing, and managing motion states and audio.
@@ -3322,6 +3330,17 @@ export declare abstract class MotionManager<Motion = unknown, MotionSpec = unkno
3322
3330
  * @type {boolean}
3323
3331
  */
3324
3332
  destroyed: boolean;
3333
+ /** The idle selection; see {@link setIdleMotion}. */
3334
+ idleMotion: IdleMotion;
3335
+ /**
3336
+ * Choose what plays when nothing else does. A running idle gives way at once and a loading one
3337
+ * is dropped; a higher-priority motion in flight hands over to the new selection when it ends.
3338
+ */
3339
+ setIdleMotion(motion: IdleMotion): void;
3340
+ /** Stop the idle so the next update starts the selection. Cubism eases its parameters home too. */
3341
+ protected releaseIdle(): void;
3342
+ /** Start the idle selection at IDLE priority; a chosen motion loops regardless of its own flag. */
3343
+ protected startIdleMotion(): Promise<boolean>;
3325
3344
  /**
3326
3345
  * Reference to the parent InternalModel.
3327
3346
  * @type {InternalModel}
@@ -6917,6 +6936,10 @@ export declare class CubismMotionManager extends MotionManager<CubismMotion, Mot
6917
6936
  expressionManager?: CubismExpressionManager;
6918
6937
  eyeBlinkIds: string[];
6919
6938
  lipSyncIds: string[];
6939
+ /** What each motion's curves write, read once when it is created. */
6940
+ private readonly meta;
6941
+ /** Eases the parameters a released idle animated back to rest; see {@link releaseIdle}. */
6942
+ private release;
6920
6943
  constructor(parent: CubismInternalModel);
6921
6944
  protected init(options?: MotionManagerOptions): void;
6922
6945
  isFinished(): boolean;
@@ -6926,6 +6949,15 @@ export declare class CubismMotionManager extends MotionManager<CubismMotion, Mot
6926
6949
  getMotionFile(definition: Motion): string;
6927
6950
  protected getMotionName(definition: Motion): string;
6928
6951
  protected getSoundFile(definition: Motion): string | undefined;
6952
+ /** Whether the playing motion animates the eyes itself, so the engine's auto-blink must yield to it. */
6953
+ currentMotionBlinks(): boolean;
6954
+ /** The parameters the playing motion writes, for a host driver handing them over to it; null when none plays. */
6955
+ currentMotionParameters(): ReadonlySet<string> | null;
6956
+ private currentMotion;
6957
+ private currentMeta;
6958
+ protected releaseIdle(): void;
6959
+ /** One frame of the ease-to-rest, ahead of the motions so a new idle's fade-in blends from it. */
6960
+ private updateRelease;
6929
6961
  protected updateParameters(model: CubismModel, now: DOMHighResTimeStamp): boolean;
6930
6962
  destroy(): void;
6931
6963
  motionLastFrame(group: string, index: number, { expression }?: {
package/dist/index.js CHANGED
@@ -3567,6 +3567,10 @@ var MotionState = class {
3567
3567
  */
3568
3568
  start(motion, group, index, priority) {
3569
3569
  if (priority === 1) {
3570
+ if (group !== this.reservedIdleGroup || index !== this.reservedIdleIndex) {
3571
+ logger.log(this.tag, "Cannot start idle motion because it is no longer reserved.", this.dump(group, index));
3572
+ return false;
3573
+ }
3570
3574
  this.setReservedIdle(void 0, void 0);
3571
3575
  if (this.currentPriority !== 0) {
3572
3576
  logger.log(this.tag, "Cannot start idle motion because another motion is playing.", this.dump(group, index));
@@ -3912,6 +3916,10 @@ let MotionPreloadStrategy = /* @__PURE__ */ function(MotionPreloadStrategy) {
3912
3916
  MotionPreloadStrategy["NONE"] = "NONE";
3913
3917
  return MotionPreloadStrategy;
3914
3918
  }({});
3919
+ function sameIdleMotion(a, b) {
3920
+ if (typeof a !== "object" || typeof b !== "object" || a === null || b === null) return a === b;
3921
+ return a.group === b.group && a.index === b.index;
3922
+ }
3915
3923
  /**
3916
3924
  * Handles the motion playback for Live2D models.
3917
3925
  * Responsible for loading, playing, and managing motion states and audio.
@@ -3966,6 +3974,29 @@ var MotionManager = class extends EventEmitter {
3966
3974
  * @type {boolean}
3967
3975
  */
3968
3976
  destroyed = false;
3977
+ /** The idle selection; see {@link setIdleMotion}. */
3978
+ idleMotion = "idle-group";
3979
+ /**
3980
+ * Choose what plays when nothing else does. A running idle gives way at once and a loading one
3981
+ * is dropped; a higher-priority motion in flight hands over to the new selection when it ends.
3982
+ */
3983
+ setIdleMotion(motion) {
3984
+ if (sameIdleMotion(this.idleMotion, motion)) return;
3985
+ this.idleMotion = motion;
3986
+ this.state.setReservedIdle(void 0, void 0);
3987
+ if (this.state.currentPriority === 1) this.releaseIdle();
3988
+ }
3989
+ /** Stop the idle so the next update starts the selection. Cubism eases its parameters home too. */
3990
+ releaseIdle() {
3991
+ this.stopAllMotions();
3992
+ }
3993
+ /** Start the idle selection at IDLE priority; a chosen motion loops regardless of its own flag. */
3994
+ startIdleMotion() {
3995
+ const idle = this.idleMotion;
3996
+ if (idle === null) return Promise.resolve(false);
3997
+ if (idle === "idle-group") return this.startRandomMotion(this.groups.idle, 1);
3998
+ return this.startMotion(idle.group, idle.index, 1, { loop: true });
3999
+ }
3969
4000
  /**
3970
4001
  * Reference to the parent InternalModel.
3971
4002
  * @type {InternalModel}
@@ -4264,7 +4295,7 @@ var MotionManager = class extends EventEmitter {
4264
4295
  }
4265
4296
  if (this.state.shouldOverrideExpression()) this.expressionManager?.restoreExpression();
4266
4297
  this.state.complete();
4267
- if (this.state.shouldRequestIdleMotion()) this.startRandomMotion(this.groups.idle, 1);
4298
+ if (this.idleMotion !== null && this.state.shouldRequestIdleMotion()) this.startIdleMotion();
4268
4299
  }
4269
4300
  return this.updateParameters(model, now);
4270
4301
  }
@@ -14693,6 +14724,10 @@ var CubismMotionManager = class extends MotionManager {
14693
14724
  expressionManager;
14694
14725
  eyeBlinkIds;
14695
14726
  lipSyncIds = ["ParamMouthOpenY"];
14727
+ /** What each motion's curves write, read once when it is created. */
14728
+ meta = /* @__PURE__ */ new WeakMap();
14729
+ /** Eases the parameters a released idle animated back to rest; see {@link releaseIdle}. */
14730
+ release = null;
14696
14731
  constructor(parent) {
14697
14732
  super(parent);
14698
14733
  this.definitions = parent.settings.motions ?? {};
@@ -14722,6 +14757,7 @@ var CubismMotionManager = class extends MotionManager {
14722
14757
  });
14723
14758
  motion._motionData.curves = filtered;
14724
14759
  motion._motionData.curveCount = filtered.length;
14760
+ this.meta.set(motion, motionMeta(motion, this.eyeBlinkIds));
14725
14761
  }
14726
14762
  this.queueManager.stopAllMotions();
14727
14763
  return this.queueManager.startMotion(motion, false);
@@ -14734,6 +14770,7 @@ var CubismMotionManager = class extends MotionManager {
14734
14770
  const motion = CubismMotion.create(data, data.byteLength, void 0, void 0, shouldCheckMotionConsistency);
14735
14771
  const json = new CubismMotionJson(data, data.byteLength);
14736
14772
  motion.setLoop(json.isMotionLoop());
14773
+ this.meta.set(motion, motionMeta(motion, this.eyeBlinkIds));
14737
14774
  const defaultFadingDuration = (group === this.groups.idle ? config.idleMotionFadingDuration : config.motionFadingDuration) / 1e3;
14738
14775
  if (json.getMotionFadeInTime() === void 0) motion.setFadeInTime(definition.FadeInTime > 0 ? definition.FadeInTime : defaultFadingDuration);
14739
14776
  if (json.getMotionFadeOutTime() === void 0) motion.setFadeOutTime(definition.FadeOutTime > 0 ? definition.FadeOutTime : defaultFadingDuration);
@@ -14749,7 +14786,57 @@ var CubismMotionManager = class extends MotionManager {
14749
14786
  getSoundFile(definition) {
14750
14787
  return definition.Sound;
14751
14788
  }
14789
+ /** Whether the playing motion animates the eyes itself, so the engine's auto-blink must yield to it. */
14790
+ currentMotionBlinks() {
14791
+ return this.currentMeta()?.blinks ?? false;
14792
+ }
14793
+ /** The parameters the playing motion writes, for a host driver handing them over to it; null when none plays. */
14794
+ currentMotionParameters() {
14795
+ return this.currentMeta()?.animated ?? null;
14796
+ }
14797
+ currentMotion() {
14798
+ const { currentGroup, currentIndex } = this.state;
14799
+ if (currentGroup === void 0 || currentIndex === void 0) return null;
14800
+ return this.motionGroups[currentGroup]?.[currentIndex] ?? null;
14801
+ }
14802
+ currentMeta() {
14803
+ const motion = this.currentMotion();
14804
+ return motion ? this.meta.get(motion) ?? null : null;
14805
+ }
14806
+ releaseIdle() {
14807
+ const motion = this.currentMotion();
14808
+ super.releaseIdle();
14809
+ if (motion) this.release = {
14810
+ ids: this.meta.get(motion)?.parameterIds ?? [],
14811
+ seconds: motion.getFadeOutTime(),
14812
+ ramp: null
14813
+ };
14814
+ }
14815
+ /** One frame of the ease-to-rest, ahead of the motions so a new idle's fade-in blends from it. */
14816
+ updateRelease(model, now) {
14817
+ const r = this.release;
14818
+ if (!r) return;
14819
+ if (r.ramp === null) {
14820
+ const count = model.getParameterCount();
14821
+ const indices = r.ids.map((id) => model.getParameterIndex(id)).filter((index) => index < count);
14822
+ r.ramp = {
14823
+ indices,
14824
+ from: indices.map((index) => model.getParameterValueByIndex(index)),
14825
+ startedAt: now
14826
+ };
14827
+ }
14828
+ const { indices, from, startedAt } = r.ramp;
14829
+ const t = r.seconds > 0 ? Math.min(1, (now - startedAt) / r.seconds) : 1;
14830
+ const w = CubismMath.getEasingSine(t);
14831
+ for (let i = 0; i < indices.length; i++) {
14832
+ const index = indices[i];
14833
+ const start = from[i];
14834
+ model.setParameterValueByIndex(index, start + (model.getParameterDefaultValue(index) - start) * w);
14835
+ }
14836
+ if (t >= 1) this.release = null;
14837
+ }
14752
14838
  updateParameters(model, now) {
14839
+ this.updateRelease(model, now);
14753
14840
  return this.queueManager.doUpdateMotion(model, now);
14754
14841
  }
14755
14842
  destroy() {
@@ -14770,6 +14857,23 @@ var CubismMotionManager = class extends MotionManager {
14770
14857
  return ids.map((id) => idManager.getId(id));
14771
14858
  }
14772
14859
  };
14860
+ function motionMeta(motion, eyeBlinkIds) {
14861
+ const parameterIds = [];
14862
+ const animated = /* @__PURE__ */ new Set();
14863
+ let blinks = false;
14864
+ for (const curve of motion._motionData.curves) {
14865
+ const id = getCurveId$1(curve);
14866
+ if (curve.type === 1) {
14867
+ parameterIds.push(curve.id);
14868
+ if (id !== void 0) animated.add(id);
14869
+ } else if (curve.type === 0 && id === "EyeBlink") blinks = true;
14870
+ }
14871
+ return {
14872
+ parameterIds,
14873
+ animated,
14874
+ blinks: blinks || eyeBlinkIds.some((id) => animated.has(id))
14875
+ };
14876
+ }
14773
14877
  function getCurveId$1(curve) {
14774
14878
  if (!curve || typeof curve !== "object") return;
14775
14879
  const id = curve.id;
@@ -15322,10 +15426,10 @@ var CubismInternalModel = class extends InternalModel {
15322
15426
  dt /= 1e3;
15323
15427
  now /= 1e3;
15324
15428
  const model = this.coreModel;
15325
- const motionUpdated = this.updateMotions(model, now);
15429
+ this.updateMotions(model, now);
15326
15430
  model.saveParameters();
15327
15431
  this.motionManager.expressionManager?.update(model, now);
15328
- if (!motionUpdated) this.eyeBlink?.updateParameters?.(model, dt);
15432
+ if (!this.motionManager.currentMotionBlinks()) this.eyeBlink?.updateParameters?.(model, dt);
15329
15433
  this.updateFocus();
15330
15434
  this.updateNaturalMovements(dt * 1e3, now * 1e3);
15331
15435
  if (this.lipSync && this.motionManager.currentAudio) {