reze-engine 0.9.4 → 0.10.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/src/model.ts CHANGED
@@ -5,6 +5,8 @@ import { IKSolverSystem } from "./ik-solver"
5
5
  import { VMDLoader, type VMDKeyFrame } from "./vmd-loader"
6
6
  import {
7
7
  AnimationClip,
8
+ AnimationPlayOptions,
9
+ AnimationProgress,
8
10
  AnimationState,
9
11
  BoneInterpolation,
10
12
  BoneKeyframe,
@@ -13,7 +15,6 @@ import {
13
15
  rawInterpolationToBoneInterpolation,
14
16
  } from "./animation"
15
17
 
16
- const VMD_FPS = 30
17
18
  const VERTEX_STRIDE = 8
18
19
 
19
20
  export interface Texture {
@@ -480,7 +481,7 @@ export class Model {
480
481
  return this.runtimeMorph.weights
481
482
  }
482
483
 
483
- // ------- Bone helpers (public API) -------
484
+ // ------- Bone helpers (API) -------
484
485
 
485
486
  rotateBones(boneRotations: Record<string, Quat>, durationMs?: number): void {
486
487
  const state = this.tweenState
@@ -822,7 +823,6 @@ export class Model {
822
823
  rotation: kf.rotation,
823
824
  translation: kf.translation,
824
825
  interpolation: kf.interpolation,
825
- time: kf.frame / VMD_FPS,
826
826
  }))
827
827
  )
828
828
  }
@@ -843,27 +843,33 @@ export class Model {
843
843
  morphName: name,
844
844
  frame: kf.frame,
845
845
  weight: kf.weight,
846
- time: kf.frame / VMD_FPS,
847
846
  }))
848
847
  )
849
848
  }
850
- let maxTime = 0
849
+ let maxFrame = 0
851
850
  for (const frames of boneTracks.values()) {
852
- if (frames.length > 0) maxTime = Math.max(maxTime, frames[frames.length - 1].time)
851
+ if (frames.length > 0) maxFrame = Math.max(maxFrame, frames[frames.length - 1].frame)
853
852
  }
854
853
  for (const frames of morphTracks.values()) {
855
- if (frames.length > 0) maxTime = Math.max(maxTime, frames[frames.length - 1].time)
854
+ if (frames.length > 0) maxFrame = Math.max(maxFrame, frames[frames.length - 1].frame)
856
855
  }
857
- return { boneTracks, morphTracks, duration: maxTime }
856
+ return { boneTracks, morphTracks, frameCount: maxFrame }
858
857
  }
859
858
 
860
- async loadAnimation(animationName: string, vmdUrl: string): Promise<void> {
861
- const vmdKeyFrames = await VMDLoader.load(vmdUrl)
862
- const clip = this.buildClipFromVmdKeyFrames(vmdKeyFrames)
863
- this.animationState.loadAnimation(animationName, clip)
859
+ loadAnimation(animationName: string, source: string): Promise<void>
860
+ loadAnimation(animationName: string, source: AnimationClip): void
861
+ loadAnimation(animationName: string, source: string | AnimationClip): Promise<void> | void {
862
+ if (typeof source !== "string") {
863
+ this.animationState.loadAnimation(animationName, source)
864
+ return
865
+ }
866
+ return VMDLoader.load(source).then((vmdKeyFrames) => {
867
+ const clip = this.buildClipFromVmdKeyFrames(vmdKeyFrames)
868
+ this.animationState.loadAnimation(animationName, clip)
869
+ })
864
870
  }
865
871
 
866
- public resetAllBones(): void {
872
+ resetAllBones(): void {
867
873
  for (let boneIdx = 0; boneIdx < this.skeleton.bones.length; boneIdx++) {
868
874
  const localRot = this.runtimeSkeleton.localRotations[boneIdx]
869
875
  const localTrans = this.runtimeSkeleton.localTranslations[boneIdx]
@@ -874,7 +880,7 @@ export class Model {
874
880
  this.computeWorldMatrices()
875
881
  }
876
882
 
877
- public resetAllMorphs(): void {
883
+ resetAllMorphs(): void {
878
884
  for (let morphIdx = 0; morphIdx < this.morphing.morphs.length; morphIdx++) {
879
885
  const morphName = this.morphing.morphs[morphIdx].name
880
886
  this.setMorphWeight(morphName, 0)
@@ -883,21 +889,26 @@ export class Model {
883
889
  this.applyMorphs()
884
890
  }
885
891
 
886
- getAnimationState(): AnimationState {
887
- return this.animationState
892
+ getAnimationClip(name: string): AnimationClip | null {
893
+ return this.animationState.getAnimationClip(name)
888
894
  }
889
895
 
890
896
  play(): void
891
897
  play(name: string): boolean
892
- play(name?: string): void | boolean {
898
+ play(name: string, options?: AnimationPlayOptions): boolean
899
+ play(name?: string, options?: AnimationPlayOptions): void | boolean {
893
900
  if (name === undefined) {
894
901
  this.animationState.play()
895
902
  return
896
903
  }
897
- return this.animationState.play(name)
904
+ this.resetAllBones()
905
+ this.resetAllMorphs()
906
+ return this.animationState.play(name, options)
898
907
  }
899
908
 
900
909
  show(name: string): void {
910
+ this.resetAllBones()
911
+ this.resetAllMorphs()
901
912
  this.animationState.show(name)
902
913
  }
903
914
 
@@ -924,46 +935,55 @@ export class Model {
924
935
  this.animationState.stop()
925
936
  }
926
937
 
927
- seek(time: number): void {
928
- this.animationState.seek(time)
938
+ // Seek by absolute timeline seconds, not frame index.
939
+ seek(seconds: number): void {
940
+ this.animationState.seek(seconds)
929
941
  }
930
942
 
931
943
  // @deprecated Use model.seek()
932
- seekAnimation(time: number): void {
933
- this.animationState.seek(time)
944
+ seekAnimation(seconds: number): void {
945
+ this.animationState.seek(seconds)
934
946
  }
935
947
 
936
- getAnimationProgress(): { current: number; duration: number; percentage: number; animationName: string | null } {
948
+ getAnimationProgress(): AnimationProgress {
937
949
  const p = this.animationState.getProgress()
938
- return { current: p.current, duration: p.duration, percentage: p.percentage, animationName: p.animationName }
950
+ return {
951
+ current: p.current,
952
+ duration: p.duration,
953
+ percentage: p.percentage,
954
+ animationName: p.animationName,
955
+ looping: p.looping,
956
+ playing: p.playing,
957
+ paused: p.paused,
958
+ }
939
959
  }
940
960
 
941
- private static upperBound<T extends { time: number }>(time: number, keyFrames: T[], startIdx: number = 0): number {
961
+ private static upperBound<T extends { frame: number }>(frame: number, keyFrames: T[], startIdx: number = 0): number {
942
962
  let left = startIdx,
943
963
  right = keyFrames.length
944
964
  while (left < right) {
945
965
  const mid = Math.floor((left + right) / 2)
946
- if (keyFrames[mid].time <= time) left = mid + 1
966
+ if (keyFrames[mid].frame <= frame) left = mid + 1
947
967
  else right = mid
948
968
  }
949
969
  return left
950
970
  }
951
971
 
952
- private findKeyframeIndex<T extends { time: number }>(time: number, keyFrames: T[], cachedIdx: number): number {
972
+ private findKeyframeIndex<T extends { frame: number }>(frame: number, keyFrames: T[], cachedIdx: number): number {
953
973
  if (keyFrames.length === 0) return -1
954
974
 
955
975
  if (cachedIdx >= 0 && cachedIdx < keyFrames.length) {
956
- const frameTime = keyFrames[cachedIdx].time
957
- const nextFrameTime = cachedIdx + 1 < keyFrames.length ? keyFrames[cachedIdx + 1].time : Infinity
958
- if (time >= frameTime && time < nextFrameTime) {
976
+ const currentFrame = keyFrames[cachedIdx].frame
977
+ const nextFrame = cachedIdx + 1 < keyFrames.length ? keyFrames[cachedIdx + 1].frame : Infinity
978
+ if (frame >= currentFrame && frame < nextFrame) {
959
979
  return cachedIdx
960
980
  }
961
981
  }
962
- const idx = Model.upperBound(time, keyFrames, 0) - 1
982
+ const idx = Model.upperBound(frame, keyFrames, 0) - 1
963
983
  return idx
964
984
  }
965
985
 
966
- private applyPoseFromClip(clip: AnimationClip | null, time: number): void {
986
+ private applyPoseFromClip(clip: AnimationClip | null, frame: number): void {
967
987
  if (!clip) return
968
988
  if (clip !== this.lastAppliedClip) {
969
989
  this.boneTrackIndices.clear()
@@ -975,8 +995,8 @@ export class Model {
975
995
  if (keyFrames.length === 0) continue
976
996
 
977
997
  const cachedIdx = this.boneTrackIndices.get(boneName) ?? -1
978
- const clampedTime = Math.max(keyFrames[0].time, Math.min(keyFrames[keyFrames.length - 1].time, time))
979
- const idx = this.findKeyframeIndex(clampedTime, keyFrames, cachedIdx)
998
+ const clampedFrame = Math.max(keyFrames[0].frame, Math.min(keyFrames[keyFrames.length - 1].frame, frame))
999
+ const idx = this.findKeyframeIndex(clampedFrame, keyFrames, cachedIdx)
980
1000
 
981
1001
  if (idx < 0) continue
982
1002
 
@@ -997,8 +1017,8 @@ export class Model {
997
1017
  const localTranslation = this.convertVMDTranslationToLocal(boneIdx, frameA.translation, frameRotation)
998
1018
  localTrans.set(localTranslation)
999
1019
  } else {
1000
- const timeDelta = frameB.time - frameA.time
1001
- const gradient = (clampedTime - frameA.time) / timeDelta
1020
+ const frameDelta = frameB.frame - frameA.frame
1021
+ const gradient = frameDelta > 0 ? (clampedFrame - frameA.frame) / frameDelta : 0
1002
1022
  const interp = frameB.interpolation
1003
1023
 
1004
1024
  const rotT = interpolateControlPoints(interp.rotation, gradient)
@@ -1025,8 +1045,8 @@ export class Model {
1025
1045
  if (keyFrames.length === 0) continue
1026
1046
 
1027
1047
  const cachedIdx = this.morphTrackIndices.get(morphName) ?? -1
1028
- const clampedTime = Math.max(keyFrames[0].time, Math.min(keyFrames[keyFrames.length - 1].time, time))
1029
- const idx = this.findKeyframeIndex(clampedTime, keyFrames, cachedIdx)
1048
+ const clampedFrame = Math.max(keyFrames[0].frame, Math.min(keyFrames[keyFrames.length - 1].frame, frame))
1049
+ const idx = this.findKeyframeIndex(clampedFrame, keyFrames, cachedIdx)
1030
1050
 
1031
1051
  if (idx < 0) continue
1032
1052
 
@@ -1041,7 +1061,9 @@ export class Model {
1041
1061
  const weight = frameB
1042
1062
  ? frameA.weight +
1043
1063
  (frameB.weight - frameA.weight) *
1044
- ((clampedTime - keyFrames[idx].time) / (keyFrames[idx + 1].time - keyFrames[idx].time))
1064
+ (keyFrames[idx + 1].frame > keyFrames[idx].frame
1065
+ ? (clampedFrame - keyFrames[idx].frame) / (keyFrames[idx + 1].frame - keyFrames[idx].frame)
1066
+ : 0)
1045
1067
  : frameA.weight
1046
1068
 
1047
1069
  this.runtimeMorph.weights[morphIdx] = weight
@@ -1059,9 +1081,9 @@ export class Model {
1059
1081
 
1060
1082
  this.animationState.update(deltaTime)
1061
1083
  const clip = this.animationState.getCurrentClip()
1062
- const time = this.animationState.getCurrentTime()
1084
+ const frame = this.animationState.getCurrentFrame()
1063
1085
  if (clip !== null) {
1064
- this.applyPoseFromClip(clip, time)
1086
+ this.applyPoseFromClip(clip, frame)
1065
1087
  }
1066
1088
 
1067
1089
  // Apply morphs if tweens changed morphs or animation changed morphs
@@ -1223,7 +1245,7 @@ export class Model {
1223
1245
  }
1224
1246
  }
1225
1247
 
1226
- public computeWorldMatrices(): void {
1248
+ computeWorldMatrices(): void {
1227
1249
  const bones = this.skeleton.bones
1228
1250
  const localRot = this.runtimeSkeleton.localRotations
1229
1251
  const localTrans = this.runtimeSkeleton.localTranslations