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/README.md +28 -5
- package/dist/animation.d.ts +26 -13
- package/dist/animation.d.ts.map +1 -1
- package/dist/animation.js +95 -35
- package/dist/engine.d.ts +5 -1
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +78 -23
- package/dist/ik-solver.d.ts.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/model.d.ts +8 -11
- package/dist/model.d.ts.map +1 -1
- package/dist/model.js +55 -38
- package/package.json +1 -1
- package/src/animation.ts +124 -40
- package/src/engine.ts +121 -69
- package/src/ik-solver.ts +7 -7
- package/src/index.ts +9 -5
- package/src/model.ts +64 -42
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 (
|
|
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
|
|
849
|
+
let maxFrame = 0
|
|
851
850
|
for (const frames of boneTracks.values()) {
|
|
852
|
-
if (frames.length > 0)
|
|
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)
|
|
854
|
+
if (frames.length > 0) maxFrame = Math.max(maxFrame, frames[frames.length - 1].frame)
|
|
856
855
|
}
|
|
857
|
-
return { boneTracks, morphTracks,
|
|
856
|
+
return { boneTracks, morphTracks, frameCount: maxFrame }
|
|
858
857
|
}
|
|
859
858
|
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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?:
|
|
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
|
-
|
|
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
|
-
|
|
928
|
-
|
|
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(
|
|
933
|
-
this.animationState.seek(
|
|
944
|
+
seekAnimation(seconds: number): void {
|
|
945
|
+
this.animationState.seek(seconds)
|
|
934
946
|
}
|
|
935
947
|
|
|
936
|
-
getAnimationProgress():
|
|
948
|
+
getAnimationProgress(): AnimationProgress {
|
|
937
949
|
const p = this.animationState.getProgress()
|
|
938
|
-
return {
|
|
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 {
|
|
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].
|
|
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 {
|
|
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
|
|
957
|
-
const
|
|
958
|
-
if (
|
|
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(
|
|
982
|
+
const idx = Model.upperBound(frame, keyFrames, 0) - 1
|
|
963
983
|
return idx
|
|
964
984
|
}
|
|
965
985
|
|
|
966
|
-
private applyPoseFromClip(clip: AnimationClip | null,
|
|
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
|
|
979
|
-
const idx = this.findKeyframeIndex(
|
|
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
|
|
1001
|
-
const gradient = (
|
|
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
|
|
1029
|
-
const idx = this.findKeyframeIndex(
|
|
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
|
-
(
|
|
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
|
|
1084
|
+
const frame = this.animationState.getCurrentFrame()
|
|
1063
1085
|
if (clip !== null) {
|
|
1064
|
-
this.applyPoseFromClip(clip,
|
|
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
|
-
|
|
1248
|
+
computeWorldMatrices(): void {
|
|
1227
1249
|
const bones = this.skeleton.bones
|
|
1228
1250
|
const localRot = this.runtimeSkeleton.localRotations
|
|
1229
1251
|
const localTrans = this.runtimeSkeleton.localTranslations
|