reze-engine 0.6.6 → 0.7.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 +83 -6
- package/dist/animation.d.ts +41 -0
- package/dist/animation.d.ts.map +1 -0
- package/dist/animation.js +62 -0
- package/dist/engine.d.ts +3 -0
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +6 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/math.d.ts +0 -10
- package/dist/math.d.ts.map +1 -1
- package/dist/math.js +0 -36
- package/dist/model.d.ts +9 -5
- package/dist/model.d.ts.map +1 -1
- package/dist/model.js +92 -87
- package/package.json +1 -1
- package/src/animation.ts +109 -0
- package/src/engine.ts +10 -0
- package/src/index.ts +2 -0
- package/src/math.ts +0 -42
- package/src/model.ts +116 -129
- package/dist/runtime-bone.d.ts +0 -49
- package/dist/runtime-bone.d.ts.map +0 -1
- package/dist/runtime-bone.js +0 -121
package/src/model.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
import { Mat4, Quat, Vec3
|
|
1
|
+
import { Mat4, Quat, Vec3 } from "./math"
|
|
2
2
|
import { Rigidbody, Joint, Physics } from "./physics"
|
|
3
3
|
import { IKSolverSystem } from "./ik-solver"
|
|
4
|
-
import {
|
|
4
|
+
import { VMDLoader } from "./vmd-loader"
|
|
5
|
+
import { AnimationData, BoneKeyframe, MorphKeyframe, BoneInterpolation, interpolateControlPoints, rawInterpolationToBoneInterpolation } from "./animation"
|
|
5
6
|
|
|
7
|
+
const VMD_FPS = 30
|
|
6
8
|
const VERTEX_STRIDE = 8
|
|
7
9
|
|
|
8
10
|
export interface Texture {
|
|
@@ -181,9 +183,10 @@ export class Model {
|
|
|
181
183
|
private tweenTimeMs: number = 0 // Time tracking for tweens (milliseconds)
|
|
182
184
|
|
|
183
185
|
// Animation runtime
|
|
184
|
-
private
|
|
185
|
-
private
|
|
186
|
-
private
|
|
186
|
+
private _hasAnimation: boolean = false
|
|
187
|
+
private _animationData: AnimationData | null = null
|
|
188
|
+
private boneTracks: Map<string, Array<{ boneName: string; frame: number; rotation: Quat; translation: Vec3; interpolation: BoneInterpolation; time: number }>> = new Map()
|
|
189
|
+
private morphTracks: Map<string, Array<{ morphName: string; frame: number; weight: number; time: number }>> = new Map()
|
|
187
190
|
private animationDuration: number = 0
|
|
188
191
|
private isPlaying: boolean = false
|
|
189
192
|
private isPaused: boolean = false
|
|
@@ -792,11 +795,89 @@ export class Model {
|
|
|
792
795
|
* Load VMD animation file
|
|
793
796
|
*/
|
|
794
797
|
async loadVmd(vmdUrl: string): Promise<void> {
|
|
795
|
-
|
|
798
|
+
const vmdKeyFrames = await VMDLoader.load(vmdUrl)
|
|
799
|
+
|
|
800
|
+
// Convert VMDKeyFrame[] to AnimationData
|
|
801
|
+
const boneTracks: Record<string, BoneKeyframe[]> = {}
|
|
802
|
+
const morphTracks: Record<string, MorphKeyframe[]> = {}
|
|
803
|
+
|
|
804
|
+
for (const keyFrame of vmdKeyFrames) {
|
|
805
|
+
for (const bf of keyFrame.boneFrames) {
|
|
806
|
+
if (!boneTracks[bf.boneName]) boneTracks[bf.boneName] = []
|
|
807
|
+
boneTracks[bf.boneName].push({
|
|
808
|
+
frame: bf.frame,
|
|
809
|
+
rotation: bf.rotation,
|
|
810
|
+
translation: bf.translation,
|
|
811
|
+
interpolation: rawInterpolationToBoneInterpolation(bf.interpolation),
|
|
812
|
+
})
|
|
813
|
+
}
|
|
814
|
+
for (const mf of keyFrame.morphFrames) {
|
|
815
|
+
if (!morphTracks[mf.morphName]) morphTracks[mf.morphName] = []
|
|
816
|
+
morphTracks[mf.morphName].push({
|
|
817
|
+
frame: mf.frame,
|
|
818
|
+
weight: mf.weight,
|
|
819
|
+
})
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
this.loadAnimationData({ boneTracks, morphTracks })
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
/**
|
|
827
|
+
* Load animation from structured keyframe data.
|
|
828
|
+
* This is the primary way to set animation data — loadVmd delegates to this.
|
|
829
|
+
*/
|
|
830
|
+
loadAnimationData(data: AnimationData): void {
|
|
831
|
+
this._animationData = data
|
|
796
832
|
this.resetAllBones()
|
|
797
833
|
this.resetAllMorphs()
|
|
798
|
-
|
|
799
|
-
|
|
834
|
+
|
|
835
|
+
this.boneTracks = new Map()
|
|
836
|
+
for (const name in data.boneTracks) {
|
|
837
|
+
const keyframes = data.boneTracks[name]
|
|
838
|
+
const sorted = [...keyframes].sort((a, b) => a.frame - b.frame)
|
|
839
|
+
this.boneTracks.set(
|
|
840
|
+
name,
|
|
841
|
+
sorted.map((kf) => ({
|
|
842
|
+
boneName: name,
|
|
843
|
+
frame: kf.frame,
|
|
844
|
+
rotation: kf.rotation,
|
|
845
|
+
translation: kf.translation,
|
|
846
|
+
interpolation: kf.interpolation,
|
|
847
|
+
time: kf.frame / VMD_FPS,
|
|
848
|
+
}))
|
|
849
|
+
)
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
this.morphTracks = new Map()
|
|
853
|
+
for (const name in data.morphTracks) {
|
|
854
|
+
const keyframes = data.morphTracks[name]
|
|
855
|
+
const sorted = [...keyframes].sort((a, b) => a.frame - b.frame)
|
|
856
|
+
this.morphTracks.set(
|
|
857
|
+
name,
|
|
858
|
+
sorted.map((kf) => ({
|
|
859
|
+
morphName: name,
|
|
860
|
+
frame: kf.frame,
|
|
861
|
+
weight: kf.weight,
|
|
862
|
+
time: kf.frame / VMD_FPS,
|
|
863
|
+
}))
|
|
864
|
+
)
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
this.boneTrackIndices.clear()
|
|
868
|
+
this.morphTrackIndices.clear()
|
|
869
|
+
|
|
870
|
+
// Calculate duration
|
|
871
|
+
let maxTime = 0
|
|
872
|
+
for (const frames of this.boneTracks.values()) {
|
|
873
|
+
if (frames.length > 0) maxTime = Math.max(maxTime, frames[frames.length - 1].time)
|
|
874
|
+
}
|
|
875
|
+
for (const frames of this.morphTracks.values()) {
|
|
876
|
+
if (frames.length > 0) maxTime = Math.max(maxTime, frames[frames.length - 1].time)
|
|
877
|
+
}
|
|
878
|
+
this.animationDuration = maxTime
|
|
879
|
+
|
|
880
|
+
this._hasAnimation = true
|
|
800
881
|
this.animationTime = 0
|
|
801
882
|
this.getPoseAtTime(0)
|
|
802
883
|
|
|
@@ -847,71 +928,8 @@ export class Model {
|
|
|
847
928
|
this.physicsEnabled = enabled
|
|
848
929
|
}
|
|
849
930
|
|
|
850
|
-
/**
|
|
851
|
-
* Process frames into tracks
|
|
852
|
-
*/
|
|
853
|
-
private processFrames(): void {
|
|
854
|
-
if (!this.animationData) return
|
|
855
|
-
|
|
856
|
-
// Helper to group frames by name and sort by time
|
|
857
|
-
const groupFrames = <T>(
|
|
858
|
-
items: Array<{ item: T; name: string; time: number }>
|
|
859
|
-
): Map<string, Array<{ item: T; time: number }>> => {
|
|
860
|
-
const tracks = new Map<string, Array<{ item: T; time: number }>>()
|
|
861
|
-
for (const { item, name, time } of items) {
|
|
862
|
-
if (!tracks.has(name)) tracks.set(name, [])
|
|
863
|
-
tracks.get(name)!.push({ item, time })
|
|
864
|
-
}
|
|
865
|
-
for (const keyFrames of tracks.values()) {
|
|
866
|
-
keyFrames.sort((a, b) => a.time - b.time)
|
|
867
|
-
}
|
|
868
|
-
return tracks
|
|
869
|
-
}
|
|
870
|
-
|
|
871
|
-
// Collect all bone and morph frames
|
|
872
|
-
const boneItems: Array<{ item: BoneFrame; name: string; time: number }> = []
|
|
873
|
-
const morphItems: Array<{ item: MorphFrame; name: string; time: number }> = []
|
|
874
|
-
|
|
875
|
-
for (const keyFrame of this.animationData) {
|
|
876
|
-
for (const boneFrame of keyFrame.boneFrames) {
|
|
877
|
-
boneItems.push({ item: boneFrame, name: boneFrame.boneName, time: keyFrame.time })
|
|
878
|
-
}
|
|
879
|
-
for (const morphFrame of keyFrame.morphFrames) {
|
|
880
|
-
morphItems.push({ item: morphFrame, name: morphFrame.morphName, time: keyFrame.time })
|
|
881
|
-
}
|
|
882
|
-
}
|
|
883
|
-
|
|
884
|
-
// Transform to expected format
|
|
885
|
-
this.boneTracks = new Map()
|
|
886
|
-
for (const [name, frames] of groupFrames(boneItems).entries()) {
|
|
887
|
-
this.boneTracks.set(
|
|
888
|
-
name,
|
|
889
|
-
frames.map((f) => ({ boneFrame: f.item, time: f.time }))
|
|
890
|
-
)
|
|
891
|
-
}
|
|
892
|
-
|
|
893
|
-
this.morphTracks = new Map()
|
|
894
|
-
for (const [name, frames] of groupFrames(morphItems).entries()) {
|
|
895
|
-
this.morphTracks.set(
|
|
896
|
-
name,
|
|
897
|
-
frames.map((f) => ({ morphFrame: f.item, time: f.time }))
|
|
898
|
-
)
|
|
899
|
-
}
|
|
900
|
-
|
|
901
|
-
// Reset cached indices when tracks change
|
|
902
|
-
this.boneTrackIndices.clear()
|
|
903
|
-
this.morphTrackIndices.clear()
|
|
904
|
-
|
|
905
|
-
// Calculate duration from all tracks
|
|
906
|
-
const allTracks = [...this.boneTracks.values(), ...this.morphTracks.values()]
|
|
907
|
-
this.animationDuration = allTracks.reduce((max, keyFrames) => {
|
|
908
|
-
const lastTime = keyFrames[keyFrames.length - 1]?.time ?? 0
|
|
909
|
-
return Math.max(max, lastTime)
|
|
910
|
-
}, 0)
|
|
911
|
-
}
|
|
912
|
-
|
|
913
931
|
playAnimation(): void {
|
|
914
|
-
if (!this.
|
|
932
|
+
if (!this._hasAnimation) return
|
|
915
933
|
|
|
916
934
|
this.isPaused = false
|
|
917
935
|
this.isPlaying = true
|
|
@@ -934,7 +952,7 @@ export class Model {
|
|
|
934
952
|
}
|
|
935
953
|
|
|
936
954
|
seekAnimation(time: number): void {
|
|
937
|
-
if (!this.
|
|
955
|
+
if (!this._hasAnimation) return
|
|
938
956
|
const clampedTime = Math.max(0, Math.min(time, this.animationDuration))
|
|
939
957
|
this.animationTime = clampedTime
|
|
940
958
|
}
|
|
@@ -942,6 +960,10 @@ export class Model {
|
|
|
942
960
|
/**
|
|
943
961
|
* Get current animation progress
|
|
944
962
|
*/
|
|
963
|
+
getAnimationData(): AnimationData | null {
|
|
964
|
+
return this._animationData
|
|
965
|
+
}
|
|
966
|
+
|
|
945
967
|
getAnimationProgress(): { current: number; duration: number; percentage: number } {
|
|
946
968
|
const duration = this.animationDuration
|
|
947
969
|
const percentage = duration > 0 ? (this.animationTime / duration) * 100 : 0
|
|
@@ -994,9 +1016,7 @@ export class Model {
|
|
|
994
1016
|
* Optimized for per-frame performance
|
|
995
1017
|
*/
|
|
996
1018
|
private getPoseAtTime(time: number): void {
|
|
997
|
-
if (!this.
|
|
998
|
-
|
|
999
|
-
const INV_127 = 1 / 127 // Pre-compute division constant
|
|
1019
|
+
if (!this._hasAnimation) return
|
|
1000
1020
|
|
|
1001
1021
|
// Process bone tracks
|
|
1002
1022
|
for (const [boneName, keyFrames] of this.boneTracks.entries()) {
|
|
@@ -1008,11 +1028,10 @@ export class Model {
|
|
|
1008
1028
|
|
|
1009
1029
|
if (idx < 0) continue
|
|
1010
1030
|
|
|
1011
|
-
// Update cache
|
|
1012
1031
|
this.boneTrackIndices.set(boneName, idx)
|
|
1013
1032
|
|
|
1014
|
-
const frameA = keyFrames[idx]
|
|
1015
|
-
const frameB = keyFrames[idx + 1]
|
|
1033
|
+
const frameA = keyFrames[idx]
|
|
1034
|
+
const frameB = keyFrames[idx + 1]
|
|
1016
1035
|
|
|
1017
1036
|
const boneIdx = this.runtimeSkeleton.nameIndex[boneName]
|
|
1018
1037
|
if (boneIdx === undefined) continue
|
|
@@ -1021,61 +1040,30 @@ export class Model {
|
|
|
1021
1040
|
const localTrans = this.runtimeSkeleton.localTranslations[boneIdx]
|
|
1022
1041
|
|
|
1023
1042
|
if (!frameB) {
|
|
1024
|
-
// No interpolation needed - direct assignment
|
|
1025
|
-
// Use animation frame's rotation for translation conversion to ensure consistency
|
|
1026
|
-
// This prevents conflicts when IK later modifies the rotation
|
|
1027
1043
|
const frameRotation = frameA.rotation
|
|
1028
1044
|
localRot.set(frameRotation)
|
|
1029
|
-
// Convert VMD relative translation to local translation using animation rotation
|
|
1030
1045
|
const localTranslation = this.convertVMDTranslationToLocal(boneIdx, frameA.translation, frameRotation)
|
|
1031
1046
|
localTrans.set(localTranslation)
|
|
1032
1047
|
} else {
|
|
1033
|
-
const
|
|
1034
|
-
const
|
|
1035
|
-
const timeDelta = timeB - timeA
|
|
1036
|
-
const gradient = (clampedTime - timeA) / timeDelta
|
|
1048
|
+
const timeDelta = frameB.time - frameA.time
|
|
1049
|
+
const gradient = (clampedTime - frameA.time) / timeDelta
|
|
1037
1050
|
const interp = frameB.interpolation
|
|
1038
1051
|
|
|
1039
|
-
|
|
1040
|
-
const rotT = bezierInterpolate(
|
|
1041
|
-
interp[0] * INV_127,
|
|
1042
|
-
interp[1] * INV_127,
|
|
1043
|
-
interp[2] * INV_127,
|
|
1044
|
-
interp[3] * INV_127,
|
|
1045
|
-
gradient
|
|
1046
|
-
)
|
|
1047
|
-
|
|
1048
|
-
// Use Quat.slerp to interpolate rotation
|
|
1052
|
+
const rotT = interpolateControlPoints(interp.rotation, gradient)
|
|
1049
1053
|
const rotation = Quat.slerp(frameA.rotation, frameB.rotation, rotT)
|
|
1050
1054
|
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
const
|
|
1054
|
-
|
|
1055
|
-
interp[offset] * INV_127,
|
|
1056
|
-
interp[offset + 8] * INV_127,
|
|
1057
|
-
interp[offset + 4] * INV_127,
|
|
1058
|
-
interp[offset + 12] * INV_127,
|
|
1059
|
-
gradient
|
|
1060
|
-
)
|
|
1061
|
-
|
|
1062
|
-
const txWeight = getWeight(0)
|
|
1063
|
-
const tyWeight = getWeight(16)
|
|
1064
|
-
const tzWeight = getWeight(32)
|
|
1065
|
-
|
|
1066
|
-
// Interpolate VMD relative translations (relative to bind pose world position)
|
|
1055
|
+
const txWeight = interpolateControlPoints(interp.translationX, gradient)
|
|
1056
|
+
const tyWeight = interpolateControlPoints(interp.translationY, gradient)
|
|
1057
|
+
const tzWeight = interpolateControlPoints(interp.translationZ, gradient)
|
|
1058
|
+
|
|
1067
1059
|
const interpolatedVMDTranslation = new Vec3(
|
|
1068
1060
|
frameA.translation.x + (frameB.translation.x - frameA.translation.x) * txWeight,
|
|
1069
1061
|
frameA.translation.y + (frameB.translation.y - frameA.translation.y) * tyWeight,
|
|
1070
1062
|
frameA.translation.z + (frameB.translation.z - frameA.translation.z) * tzWeight
|
|
1071
1063
|
)
|
|
1072
1064
|
|
|
1073
|
-
// Convert interpolated VMD translation to local translation using animation rotation
|
|
1074
|
-
// This ensures translation is computed for the animation rotation, not the runtime rotation
|
|
1075
|
-
// that will be modified by IK, preventing conflicts
|
|
1076
1065
|
const localTranslation = this.convertVMDTranslationToLocal(boneIdx, interpolatedVMDTranslation, rotation)
|
|
1077
1066
|
|
|
1078
|
-
// Direct property writes to avoid object allocation
|
|
1079
1067
|
localRot.set(rotation)
|
|
1080
1068
|
localTrans.set(localTranslation)
|
|
1081
1069
|
}
|
|
@@ -1091,19 +1079,18 @@ export class Model {
|
|
|
1091
1079
|
|
|
1092
1080
|
if (idx < 0) continue
|
|
1093
1081
|
|
|
1094
|
-
// Update cache
|
|
1095
1082
|
this.morphTrackIndices.set(morphName, idx)
|
|
1096
1083
|
|
|
1097
|
-
const frameA = keyFrames[idx]
|
|
1098
|
-
const frameB = keyFrames[idx + 1]
|
|
1084
|
+
const frameA = keyFrames[idx]
|
|
1085
|
+
const frameB = keyFrames[idx + 1]
|
|
1099
1086
|
|
|
1100
1087
|
const morphIdx = this.runtimeMorph.nameIndex[morphName]
|
|
1101
1088
|
if (morphIdx === undefined) continue
|
|
1102
1089
|
|
|
1103
1090
|
const weight = frameB
|
|
1104
1091
|
? frameA.weight +
|
|
1105
|
-
|
|
1106
|
-
|
|
1092
|
+
(frameB.weight - frameA.weight) *
|
|
1093
|
+
((clampedTime - keyFrames[idx].time) / (keyFrames[idx + 1].time - keyFrames[idx].time))
|
|
1107
1094
|
: frameA.weight
|
|
1108
1095
|
|
|
1109
1096
|
this.runtimeMorph.weights[morphIdx] = weight
|
|
@@ -1125,7 +1112,7 @@ export class Model {
|
|
|
1125
1112
|
const tweensChangedMorphs = this.updateTweens()
|
|
1126
1113
|
|
|
1127
1114
|
// Apply animation if playing or paused (always apply pose if animation data exists and we have a time set)
|
|
1128
|
-
if (this.
|
|
1115
|
+
if (this._hasAnimation) {
|
|
1129
1116
|
if (this.isPlaying && !this.isPaused) {
|
|
1130
1117
|
this.animationTime += deltaTime
|
|
1131
1118
|
|
|
@@ -1177,7 +1164,7 @@ export class Model {
|
|
|
1177
1164
|
// Recompute ALL world matrices before each solver starts
|
|
1178
1165
|
// This ensures each solver sees the effects of previous solvers on localRotations
|
|
1179
1166
|
this.computeWorldMatrices()
|
|
1180
|
-
|
|
1167
|
+
|
|
1181
1168
|
// Clear computed set for this solver's pass
|
|
1182
1169
|
this.ikComputedSet.clear()
|
|
1183
1170
|
|
|
@@ -1242,9 +1229,9 @@ export class Model {
|
|
|
1242
1229
|
|
|
1243
1230
|
// Handle append transformations (same logic as computeWorldMatrices)
|
|
1244
1231
|
const appendParentIdx = b.appendParentIndex
|
|
1245
|
-
const hasAppend = b.appendRotate &&
|
|
1246
|
-
appendParentIdx !== undefined &&
|
|
1247
|
-
appendParentIdx >= 0 &&
|
|
1232
|
+
const hasAppend = b.appendRotate &&
|
|
1233
|
+
appendParentIdx !== undefined &&
|
|
1234
|
+
appendParentIdx >= 0 &&
|
|
1248
1235
|
appendParentIdx < bones.length
|
|
1249
1236
|
|
|
1250
1237
|
if (hasAppend) {
|
|
@@ -1261,7 +1248,7 @@ export class Model {
|
|
|
1261
1248
|
// Compute append parent's world matrix for dependency order, but use base rotation for append
|
|
1262
1249
|
this.computeSingleBoneWorldMatrix(appendParentIdx, applyIK)
|
|
1263
1250
|
}
|
|
1264
|
-
|
|
1251
|
+
|
|
1265
1252
|
// Use append parent's base local rotation only (IK rotations are applied after solving)
|
|
1266
1253
|
let appendRot = localRot[appendParentIdx]
|
|
1267
1254
|
|
|
@@ -1269,7 +1256,7 @@ export class Model {
|
|
|
1269
1256
|
const aw = appendRot.w
|
|
1270
1257
|
const absRatio = ratio < 0 ? -ratio : ratio
|
|
1271
1258
|
if (ratio < 0) { ax = -ax; ay = -ay; az = -az }
|
|
1272
|
-
|
|
1259
|
+
|
|
1273
1260
|
const appendQuat = new Quat(ax, ay, az, aw)
|
|
1274
1261
|
const result = Quat.slerp(Quat.identity(), appendQuat, absRatio)
|
|
1275
1262
|
rotateM = Mat4.fromQuat(result.x, result.y, result.z, result.w).multiply(rotateM)
|
package/dist/runtime-bone.d.ts
DELETED
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
import { Quat, Vec3, Mat4 } from "./math";
|
|
2
|
-
import type { Bone } from "./model";
|
|
3
|
-
/**
|
|
4
|
-
* Runtime bone state - encapsulates bone pose and world matrix
|
|
5
|
-
* Similar to babylon-mmd's IMmdRuntimeBone approach
|
|
6
|
-
*/
|
|
7
|
-
export declare class RuntimeBone {
|
|
8
|
-
localRotation: Quat;
|
|
9
|
-
localTranslation: Vec3;
|
|
10
|
-
worldMatrix: Mat4;
|
|
11
|
-
readonly bone: Bone;
|
|
12
|
-
readonly index: number;
|
|
13
|
-
ikRotation?: Quat;
|
|
14
|
-
constructor(bone: Bone, index: number);
|
|
15
|
-
/**
|
|
16
|
-
* Set local rotation
|
|
17
|
-
*/
|
|
18
|
-
setRotation(rotation: Quat): void;
|
|
19
|
-
/**
|
|
20
|
-
* Set local translation
|
|
21
|
-
*/
|
|
22
|
-
setTranslation(translation: Vec3): void;
|
|
23
|
-
/**
|
|
24
|
-
* Get world position from world matrix
|
|
25
|
-
*/
|
|
26
|
-
getWorldPosition(): Vec3;
|
|
27
|
-
/**
|
|
28
|
-
* Update world matrix based on local rotation, translation, and parent's world matrix
|
|
29
|
-
* Handles append rotations and translations
|
|
30
|
-
*
|
|
31
|
-
* @param parentWorldMatrix Parent's world matrix (null if root bone)
|
|
32
|
-
* @param allBones Array of all runtime bones (for append parent lookup)
|
|
33
|
-
* @param applyIK Whether to apply IK rotation (default: true). Set to false when computing initial world matrices before IK solving.
|
|
34
|
-
*/
|
|
35
|
-
updateWorldMatrix(parentWorldMatrix: Mat4 | null, allBones: RuntimeBone[], applyIK?: boolean): void;
|
|
36
|
-
/**
|
|
37
|
-
* Reset IK rotation to identity
|
|
38
|
-
*/
|
|
39
|
-
resetIKRotation(): void;
|
|
40
|
-
/**
|
|
41
|
-
* Set IK rotation delta (accumulated during IK solving)
|
|
42
|
-
*/
|
|
43
|
-
setIKRotation(ikRotation: Quat): void;
|
|
44
|
-
/**
|
|
45
|
-
* Apply IK rotation to local rotation and clear IK rotation
|
|
46
|
-
*/
|
|
47
|
-
applyIKRotation(): void;
|
|
48
|
-
}
|
|
49
|
-
//# sourceMappingURL=runtime-bone.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"runtime-bone.d.ts","sourceRoot":"","sources":["../src/runtime-bone.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAA;AACzC,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,SAAS,CAAA;AAEnC;;;GAGG;AACH,qBAAa,WAAW;IACf,aAAa,EAAE,IAAI,CAAA;IACnB,gBAAgB,EAAE,IAAI,CAAA;IACtB,WAAW,EAAE,IAAI,CAAA;IACxB,SAAgB,IAAI,EAAE,IAAI,CAAA;IAC1B,SAAgB,KAAK,EAAE,MAAM,CAAA;IAGtB,UAAU,CAAC,EAAE,IAAI,CAAA;gBAEZ,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM;IAQrC;;OAEG;IACH,WAAW,CAAC,QAAQ,EAAE,IAAI,GAAG,IAAI;IAIjC;;OAEG;IACH,cAAc,CAAC,WAAW,EAAE,IAAI,GAAG,IAAI;IAIvC;;OAEG;IACH,gBAAgB,IAAI,IAAI;IAKxB;;;;;;;OAOG;IACH,iBAAiB,CAAC,iBAAiB,EAAE,IAAI,GAAG,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,EAAE,OAAO,GAAE,OAAc,GAAG,IAAI;IA2EzG;;OAEG;IACH,eAAe,IAAI,IAAI;IAIvB;;OAEG;IACH,aAAa,CAAC,UAAU,EAAE,IAAI,GAAG,IAAI;IAIrC;;OAEG;IACH,eAAe,IAAI,IAAI;CAMxB"}
|
package/dist/runtime-bone.js
DELETED
|
@@ -1,121 +0,0 @@
|
|
|
1
|
-
import { Quat, Vec3, Mat4 } from "./math";
|
|
2
|
-
/**
|
|
3
|
-
* Runtime bone state - encapsulates bone pose and world matrix
|
|
4
|
-
* Similar to babylon-mmd's IMmdRuntimeBone approach
|
|
5
|
-
*/
|
|
6
|
-
export class RuntimeBone {
|
|
7
|
-
constructor(bone, index) {
|
|
8
|
-
this.bone = bone;
|
|
9
|
-
this.index = index;
|
|
10
|
-
this.localRotation = Quat.identity();
|
|
11
|
-
this.localTranslation = new Vec3(0, 0, 0);
|
|
12
|
-
this.worldMatrix = Mat4.identity();
|
|
13
|
-
}
|
|
14
|
-
/**
|
|
15
|
-
* Set local rotation
|
|
16
|
-
*/
|
|
17
|
-
setRotation(rotation) {
|
|
18
|
-
this.localRotation.set(rotation);
|
|
19
|
-
}
|
|
20
|
-
/**
|
|
21
|
-
* Set local translation
|
|
22
|
-
*/
|
|
23
|
-
setTranslation(translation) {
|
|
24
|
-
this.localTranslation.set(translation);
|
|
25
|
-
}
|
|
26
|
-
/**
|
|
27
|
-
* Get world position from world matrix
|
|
28
|
-
*/
|
|
29
|
-
getWorldPosition() {
|
|
30
|
-
const m = this.worldMatrix.values;
|
|
31
|
-
return new Vec3(m[12], m[13], m[14]);
|
|
32
|
-
}
|
|
33
|
-
/**
|
|
34
|
-
* Update world matrix based on local rotation, translation, and parent's world matrix
|
|
35
|
-
* Handles append rotations and translations
|
|
36
|
-
*
|
|
37
|
-
* @param parentWorldMatrix Parent's world matrix (null if root bone)
|
|
38
|
-
* @param allBones Array of all runtime bones (for append parent lookup)
|
|
39
|
-
* @param applyIK Whether to apply IK rotation (default: true). Set to false when computing initial world matrices before IK solving.
|
|
40
|
-
*/
|
|
41
|
-
updateWorldMatrix(parentWorldMatrix, allBones, applyIK = true) {
|
|
42
|
-
// Start with local rotation
|
|
43
|
-
let finalRot = this.localRotation;
|
|
44
|
-
// Apply IK rotation if present and applyIK is true
|
|
45
|
-
if (applyIK && this.ikRotation) {
|
|
46
|
-
finalRot = this.ikRotation.multiply(finalRot).normalize();
|
|
47
|
-
}
|
|
48
|
-
// Build rotation matrix
|
|
49
|
-
let rotateM = Mat4.fromQuat(finalRot.x, finalRot.y, finalRot.z, finalRot.w);
|
|
50
|
-
let addLocalTx = 0, addLocalTy = 0, addLocalTz = 0;
|
|
51
|
-
// Handle append rotation and translation
|
|
52
|
-
const appendParentIdx = this.bone.appendParentIndex;
|
|
53
|
-
const hasAppend = this.bone.appendRotate && appendParentIdx !== undefined && appendParentIdx >= 0 && appendParentIdx < allBones.length;
|
|
54
|
-
if (hasAppend) {
|
|
55
|
-
const ratio = this.bone.appendRatio === undefined ? 1 : Math.max(-1, Math.min(1, this.bone.appendRatio));
|
|
56
|
-
const hasRatio = Math.abs(ratio) > 1e-6;
|
|
57
|
-
if (hasRatio) {
|
|
58
|
-
if (this.bone.appendRotate) {
|
|
59
|
-
const appendParent = allBones[appendParentIdx];
|
|
60
|
-
const appendRot = appendParent.localRotation;
|
|
61
|
-
let ax = appendRot.x;
|
|
62
|
-
let ay = appendRot.y;
|
|
63
|
-
let az = appendRot.z;
|
|
64
|
-
const aw = appendRot.w;
|
|
65
|
-
const absRatio = ratio < 0 ? -ratio : ratio;
|
|
66
|
-
if (ratio < 0) {
|
|
67
|
-
ax = -ax;
|
|
68
|
-
ay = -ay;
|
|
69
|
-
az = -az;
|
|
70
|
-
}
|
|
71
|
-
const appendQuat = new Quat(ax, ay, az, aw);
|
|
72
|
-
const result = Quat.slerp(Quat.identity(), appendQuat, absRatio);
|
|
73
|
-
rotateM = Mat4.fromQuat(result.x, result.y, result.z, result.w).multiply(rotateM);
|
|
74
|
-
}
|
|
75
|
-
if (this.bone.appendMove) {
|
|
76
|
-
const appendParent = allBones[appendParentIdx];
|
|
77
|
-
const appendTrans = appendParent.localTranslation;
|
|
78
|
-
const appendRatio = this.bone.appendRatio ?? 1;
|
|
79
|
-
addLocalTx = appendTrans.x * appendRatio;
|
|
80
|
-
addLocalTy = appendTrans.y * appendRatio;
|
|
81
|
-
addLocalTz = appendTrans.z * appendRatio;
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
// Build local matrix: bind translation, then rotation, then local translation + append translation
|
|
86
|
-
const localTx = this.localTranslation.x + addLocalTx;
|
|
87
|
-
const localTy = this.localTranslation.y + addLocalTy;
|
|
88
|
-
const localTz = this.localTranslation.z + addLocalTz;
|
|
89
|
-
const bindMat = Mat4.identity().translateInPlace(this.bone.bindTranslation[0], this.bone.bindTranslation[1], this.bone.bindTranslation[2]);
|
|
90
|
-
const transMat = Mat4.identity().translateInPlace(localTx, localTy, localTz);
|
|
91
|
-
const localM = bindMat.multiply(rotateM).multiply(transMat);
|
|
92
|
-
// Compute world matrix
|
|
93
|
-
if (parentWorldMatrix) {
|
|
94
|
-
this.worldMatrix = parentWorldMatrix.multiply(localM);
|
|
95
|
-
}
|
|
96
|
-
else {
|
|
97
|
-
this.worldMatrix = localM;
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
/**
|
|
101
|
-
* Reset IK rotation to identity
|
|
102
|
-
*/
|
|
103
|
-
resetIKRotation() {
|
|
104
|
-
this.ikRotation = undefined;
|
|
105
|
-
}
|
|
106
|
-
/**
|
|
107
|
-
* Set IK rotation delta (accumulated during IK solving)
|
|
108
|
-
*/
|
|
109
|
-
setIKRotation(ikRotation) {
|
|
110
|
-
this.ikRotation = ikRotation;
|
|
111
|
-
}
|
|
112
|
-
/**
|
|
113
|
-
* Apply IK rotation to local rotation and clear IK rotation
|
|
114
|
-
*/
|
|
115
|
-
applyIKRotation() {
|
|
116
|
-
if (this.ikRotation) {
|
|
117
|
-
this.localRotation = this.ikRotation.multiply(this.localRotation).normalize();
|
|
118
|
-
this.ikRotation = undefined;
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
}
|