// function is similar to part of CSkelMeshInstance::SetMesh() static void BuildSkeleton(TArray<CCoords> &Coords, const TArray<RJoint> &Bones, const TArray<AnalogTrack> &Anim) { guard(BuildSkeleton); int numBones = Anim.Num(); Coords.Empty(numBones); Coords.AddZeroed(numBones); for (int i = 0; i < numBones; i++) { const AnalogTrack &A = Anim[i]; const RJoint &B = Bones[i]; CCoords &BC = Coords[i]; // compute reference bone coords CVec3 BP; CQuat BO; // get default pose BP = CVT(A.KeyPos[0]); BO = CVT(A.KeyQuat[0]); if (!i) BO.Conjugate(); BC.origin = BP; BO.ToAxis(BC.axis); // move bone position to global coordinate space if (i) // do not rotate root bone { assert(B.parent >= 0); Coords[B.parent].UnTransformCoords(BC, BC); } } unguard; }
// function is similar to part of CSkelMeshInstance::SetMesh() and Rune mesh loader static void BuildSkeleton(TArray<CCoords> &Coords, const TArray<CSkelMeshBone> &Bones) { guard(BuildSkeleton); int numBones = Bones.Num(); Coords.Empty(numBones); Coords.AddZeroed(numBones); for (int i = 0; i < numBones; i++) { const CSkelMeshBone &B = Bones[i]; CCoords &BC = Coords[i]; // compute reference bone coords CVec3 BP; CQuat BO; // get default pose BP = B.Position; BO = B.Orientation; #if MIRROR_MESH BP[1] *= -1; // y BO.y *= -1; BO.w *= -1; #endif if (!i) BO.Conjugate(); // root bone BC.origin = BP; BO.ToAxis(BC.axis); // move bone position to global coordinate space if (i) // do not rotate root bone { assert(B.ParentIndex < i); Coords[B.ParentIndex].UnTransformCoords(BC, BC); } #if 0 //!! if (i == 32) { appNotify("Bone %d (%8.3f %8.3f %8.3f) - (%8.3f %8.3f %8.3f %8.3f)", i, VECTOR_ARG(BP), QUAT_ARG(BO)); #define C BC appNotify("REF : o=%8.3f %8.3f %8.3f", VECTOR_ARG(C.origin )); appNotify(" 0=%8.3f %8.3f %8.3f", VECTOR_ARG(C.axis[0])); appNotify(" 1=%8.3f %8.3f %8.3f", VECTOR_ARG(C.axis[1])); appNotify(" 2=%8.3f %8.3f %8.3f", VECTOR_ARG(C.axis[2])); #undef C } #endif } unguard; }
void ExportMd5Anim(const CAnimSet *Anim) { guard(ExportMd5Anim); int numBones = Anim->TrackBoneNames.Num(); UObject *OriginalAnim = Anim->OriginalAnim; for (int AnimIndex = 0; AnimIndex < Anim->Sequences.Num(); AnimIndex++) { int i; const CAnimSequence &S = Anim->Sequences[AnimIndex]; FArchive *Ar = CreateExportArchive(OriginalAnim, "%s/%s.md5anim", OriginalAnim->Name, *S.Name); if (!Ar) continue; Ar->Printf( "MD5Version 10\n" "commandline \"Created with UE Viewer\"\n" "\n" "numFrames %d\n" "numJoints %d\n" "frameRate %g\n" "numAnimatedComponents %d\n" "\n", S.NumFrames, numBones, S.Rate, numBones * 6 ); // skeleton Ar->Printf("hierarchy {\n"); for (i = 0; i < numBones; i++) { Ar->Printf("\t\"%s\" %d %d %d\n", *Anim->TrackBoneNames[i], (i == 0) ? -1 : 0, 63, i * 6); // ParentIndex is unknown for UAnimSet, so always write "0" // here: 6 is number of components per frame, 63 = (1<<6)-1 -- flags "all components are used" } // bounds Ar->Printf("}\n\nbounds {\n"); for (i = 0; i < S.NumFrames; i++) Ar->Printf("\t( -100 -100 -100 ) ( 100 100 100 )\n"); //!! dummy Ar->Printf("}\n\n"); // baseframe and frames for (int Frame = -1; Frame < S.NumFrames; Frame++) { int t = Frame; if (Frame == -1) { Ar->Printf("baseframe {\n"); t = 0; } else Ar->Printf("frame %d {\n", Frame); for (int b = 0; b < numBones; b++) { CVec3 BP; CQuat BO; S.Tracks[b].GetBonePosition(t, S.NumFrames, false, BP, BO); if (!b) BO.Conjugate(); // root bone #if MIRROR_MESH BO.y *= -1; BO.w *= -1; BP[1] *= -1; // y #endif if (BO.w < 0) BO.Negate(); // W-component of quaternion will be removed ... if (Frame < 0) Ar->Printf("\t( %f %f %f ) ( %.10f %.10f %.10f )\n", VECTOR_ARG(BP), BO.x, BO.y, BO.z); else Ar->Printf("\t%f %f %f %.10f %.10f %.10f\n", VECTOR_ARG(BP), BO.x, BO.y, BO.z); } Ar->Printf("}\n\n"); } delete Ar; } unguard; }
static void ExportAnimations(ExportContext& Context, FArchive& Ar) { guard(ExportAnimations); const CAnimSet* Anim = Context.SkelMesh->Anim; int NumBones = Context.SkelMesh->RefSkeleton.Num(); // Build mesh to anim bone map TArray<int> BoneMap; BoneMap.Init(-1, NumBones); TArray<int> AnimBones; AnimBones.Empty(NumBones); for (int i = 0; i < NumBones; i++) { const CSkelMeshBone &B = Context.SkelMesh->RefSkeleton[i]; for (int j = 0; j < Anim->TrackBoneNames.Num(); j++) { if (!stricmp(B.Name, Anim->TrackBoneNames[j])) { BoneMap[i] = j; // lookup CAnimSet bone by mesh bone index AnimBones.Add(i); // indicate that the bone has animation break; } } } Ar.Printf( " \"animations\" : [\n" ); int FirstDataIndex = Context.Data.Num(); // Iterate over all animations for (int SeqIndex = 0; SeqIndex < Anim->Sequences.Num(); SeqIndex++) { const CAnimSequence &Seq = *Anim->Sequences[SeqIndex]; Ar.Printf( " {\n" " \"name\" : \"%s\",\n", *Seq.Name ); struct AnimSampler { enum ChannelType { TRANSLATION, ROTATION }; int BoneNodeIndex; ChannelType Type; const CAnimTrack* Track; }; TArray<AnimSampler> Samplers; Samplers.Empty(AnimBones.Num() * 2); //!! Optimization: //!! 1. there will be missing tracks (AnimRotationOnly etc) - drop such samplers //!! 2. store all time tracks in a single BufferView, all rotation tracks in another, and all position track in 3rd one - this //!! will reduce amount of BufferViews in json text (combine them by data type) // Prepare channels array Ar.Printf(" \"channels\" : [\n"); for (int BoneIndex = 0; BoneIndex < AnimBones.Num(); BoneIndex++) { int MeshBoneIndex = AnimBones[BoneIndex]; int AnimBoneIndex = BoneMap[MeshBoneIndex]; const CAnimTrack* Track = Seq.Tracks[AnimBoneIndex]; int TranslationSamplerIndex = Samplers.Num(); AnimSampler* Sampler = new (Samplers) AnimSampler; Sampler->Type = AnimSampler::TRANSLATION; Sampler->BoneNodeIndex = MeshBoneIndex + FIRST_BONE_NODE; Sampler->Track = Track; int RotationSamplerIndex = Samplers.Num(); Sampler = new (Samplers) AnimSampler; Sampler->Type = AnimSampler::ROTATION; Sampler->BoneNodeIndex = MeshBoneIndex + FIRST_BONE_NODE; Sampler->Track = Track; // Print glTF information. Not using usual formatting here to make output a little bit more compact. Ar.Printf( " { \"sampler\" : %d, \"target\" : { \"node\" : %d, \"path\" : \"%s\" } },\n", TranslationSamplerIndex, MeshBoneIndex + FIRST_BONE_NODE, "translation" ); Ar.Printf( " { \"sampler\" : %d, \"target\" : { \"node\" : %d, \"path\" : \"%s\" } }%s\n", RotationSamplerIndex, MeshBoneIndex + FIRST_BONE_NODE, "rotation", BoneIndex == AnimBones.Num()-1 ? "" : "," ); } Ar.Printf(" ],\n"); // Prepare samplers Ar.Printf(" \"samplers\" : [\n"); for (int SamplerIndex = 0; SamplerIndex < Samplers.Num(); SamplerIndex++) { const AnimSampler& Sampler = Samplers[SamplerIndex]; // Prepare time array const TArray<float>* TimeArray = (Sampler.Type == AnimSampler::TRANSLATION) ? &Sampler.Track->KeyPosTime : &Sampler.Track->KeyQuatTime; if (TimeArray->Num() == 0) { // For this situation, use track's time array TimeArray = &Sampler.Track->KeyTime; } int NumKeys = Sampler.Type == (AnimSampler::TRANSLATION) ? Sampler.Track->KeyPos.Num() : Sampler.Track->KeyQuat.Num(); int TimeBufIndex = Context.Data.AddZeroed(); BufferData& TimeBuf = Context.Data[TimeBufIndex]; TimeBuf.Setup(NumKeys, "SCALAR", BufferData::FLOAT, sizeof(float)); float RateScale = 1.0f / Seq.Rate; float LastFrameTime = 0; if (TimeArray->Num() == 0 || NumKeys == 1) { // Fill with equally spaced values for (int i = 0; i < NumKeys; i++) { TimeBuf.Put(i * RateScale); } LastFrameTime = NumKeys-1; } else { for (int i = 0; i < TimeArray->Num(); i++) { TimeBuf.Put((*TimeArray)[i] * RateScale); } LastFrameTime = (*TimeArray)[TimeArray->Num()-1]; } // Prepare min/max values for time track, it's required by glTF standard TimeBuf.BoundsMin = "[ 0 ]"; char buf[64]; appSprintf(ARRAY_ARG(buf), "[ %g ]", LastFrameTime * RateScale); TimeBuf.BoundsMax = buf; // Try to reuse TimeBuf from previous tracks TimeBufIndex = Context.GetFinalIndexForLastBlock(FirstDataIndex); // Prepare data int DataBufIndex = Context.Data.AddZeroed(); BufferData& DataBuf = Context.Data[DataBufIndex]; if (Sampler.Type == AnimSampler::TRANSLATION) { // Translation track DataBuf.Setup(NumKeys, "VEC3", BufferData::FLOAT, sizeof(CVec3)); for (int i = 0; i < NumKeys; i++) { CVec3 Pos = Sampler.Track->KeyPos[i]; TransformPosition(Pos); DataBuf.Put(Pos); } } else { // Rotation track DataBuf.Setup(NumKeys, "VEC4", BufferData::FLOAT, sizeof(CQuat)); for (int i = 0; i < NumKeys; i++) { CQuat Rot = Sampler.Track->KeyQuat[i]; TransformRotation(Rot); if (Sampler.BoneNodeIndex - FIRST_BONE_NODE == 0) { Rot.Conjugate(); } DataBuf.Put(Rot); } } // Try to reuse data block as well DataBufIndex = Context.GetFinalIndexForLastBlock(FirstDataIndex); // Write glTF info Ar.Printf( " { \"input\" : %d, \"output\" : %d }%s\n", TimeBufIndex, DataBufIndex, SamplerIndex == Samplers.Num()-1 ? "" : "," ); } Ar.Printf(" ]\n"); Ar.Printf(" }%s\n", SeqIndex == Anim->Sequences.Num()-1 ? "" : ","); } Ar.Printf(" ],\n"); unguard; }
static void ExportSkinData(ExportContext& Context, const CSkelMeshLod& Lod, FArchive& Ar) { guard(ExportSkinData); int numBones = Context.SkelMesh->RefSkeleton.Num(); int MatrixBufIndex = Context.Data.AddZeroed(); BufferData& MatrixBuf = Context.Data[MatrixBufIndex]; MatrixBuf.Setup(numBones, "MAT4", BufferData::FLOAT, sizeof(CMat4)); Ar.Printf( " \"nodes\" : [\n" " {\n" " \"name\" : \"%s\",\n" " \"mesh\" : 0,\n" " \"skin\" : 0,\n" " \"children\" : [ 1 ]\n" " },\n", Context.MeshName); TArray<CCoords> BoneCoords; BoneCoords.AddZeroed(numBones); for (int boneIndex = 0; boneIndex < numBones; boneIndex++) { const CSkelMeshBone& B = Context.SkelMesh->RefSkeleton[boneIndex]; // Find all children TStaticArray<int, 32> children; for (int j = 0; j < numBones; j++) { if (boneIndex == j) continue; const CSkelMeshBone& B2 = Context.SkelMesh->RefSkeleton[j]; if (B2.ParentIndex == boneIndex) { children.Add(j); } } Ar.Printf( " {\n" " \"name\" : \"%s\",\n", *B.Name ); // Write children if (children.Num()) { Ar.Printf(" \"children\" : [ %d", children[0]+FIRST_BONE_NODE); for (int j = 1; j < children.Num(); j++) { Ar.Printf(", %d", children[j]+FIRST_BONE_NODE); } Ar.Printf(" ],\n"); } // Bone transform CVec3 bonePos = B.Position; CQuat boneRot = B.Orientation; if (boneIndex == 0) { boneRot.Conjugate(); } TransformPosition(bonePos); TransformRotation(boneRot); Ar.Printf( " \"translation\" : [ %g, %g, %g ],\n" " \"rotation\" : [ %g, %g, %g, %g ]\n", bonePos[0], bonePos[1], bonePos[2], boneRot.x, boneRot.y, boneRot.z, boneRot.w ); boneRot.w *= -1; CCoords& BC = BoneCoords[boneIndex]; BC.origin = bonePos; boneRot.ToAxis(BC.axis); if (boneIndex) { // World coordinate BoneCoords[B.ParentIndex].UnTransformCoords(BC, BC); } CCoords InvCoords; InvertCoords(BC, InvCoords); CMat4 BC4x4(InvCoords); MatrixBuf.Put(BC4x4); // Closing brace Ar.Printf( " }%s\n", boneIndex == (numBones-1) ? "" : "," ); } // Close "nodes" array Ar.Printf(" ],\n"); // Make "skins" Ar.Printf( " \"skins\" : [\n" " {\n" " \"inverseBindMatrices\" : %d,\n" " \"skeleton\" : 1,\n" " \"joints\" : [", MatrixBufIndex ); for (int i = 0; i < numBones; i++) { if ((i & 31) == 0) Ar.Printf("\n "); Ar.Printf("%d%s", i+FIRST_BONE_NODE, (i == numBones-1) ? "" : ","); } Ar.Printf( "\n" " ]\n" " }\n" " ],\n" ); unguard; }