public AnimExporter(USkeleton skeleton, UAnimSequence?animSequence = null) { AnimSequences = new List <Anim>(); AnimName = animSequence?.Owner?.Name ?? animSequence?.Name ?? skeleton.Owner?.Name ?? skeleton.Name; var anim = skeleton.ConvertAnims(animSequence); if (anim.Sequences.Count == 0) { // empty CAnimSet return; } // Determine if CAnimSet will save animations as separate psa files, or all at once var originalAnim = anim.GetPrimaryAnimObject(); if (originalAnim == anim.OriginalAnim || anim.Sequences.Count == 1) { // Export all animations in a single file DoExportPsa(anim, 0); } else { //var skeleton = (USkeleton) anim.OriginalAnim; // Export animations separately, this will happen only when CAnimSet has // a few sequences (but more than one) var tempAnimSet = new CAnimSet(); tempAnimSet.CopyAllButSequences(anim); // Now we have a copy of AnimSet, let's set up Sequences array to a single // item and export one-by-one for (int animIndex = 0; animIndex < anim.Sequences.Count; animIndex++) { var seq = anim.Sequences[animIndex]; tempAnimSet.Sequences.Clear(); tempAnimSet.Sequences.Add(seq); // Do the export, pass UAnimSequence as the "main" object, so it will be // used as psa file name. DoExportPsa(tempAnimSet, animIndex); } // Ensure TempAnimSet destructor will not release Sequences as they are owned by Anim object tempAnimSet.Sequences.Clear(); } }
public static bool TryConvert(this USkeleton originalSkeleton, out List <CSkelMeshBone> bones) { bones = new List <CSkelMeshBone>(); for (var i = 0; i < originalSkeleton.ReferenceSkeleton.FinalRefBoneInfo.Length; i++) { var skeletalMeshBone = new CSkelMeshBone { Name = originalSkeleton.ReferenceSkeleton.FinalRefBoneInfo[i].Name, ParentIndex = originalSkeleton.ReferenceSkeleton.FinalRefBoneInfo[i].ParentIndex, Position = originalSkeleton.ReferenceSkeleton.FinalRefBonePose[i].Translation, Orientation = originalSkeleton.ReferenceSkeleton.FinalRefBonePose[i].Rotation, }; if (i >= 1) // fix skeleton; all bones but 0 { skeletalMeshBone.Orientation.Conjugate(); } bones.Add(skeletalMeshBone); } return(true); }
public MeshExporter(USkeleton originalSkeleton) { MeshLods = new List <Mesh>(); MeshName = originalSkeleton.Owner?.Name ?? originalSkeleton.Name; if (!originalSkeleton.TryConvert(out var bones) || bones.Count == 0) { Log.Logger.Warning($"Skeleton '{MeshName}' has no bone"); return; } using var Ar = new FArchiveWriter(); var mainHdr = new VChunkHeader { TypeFlag = Constants.PSK_VERSION }; Ar.SerializeChunkHeader(mainHdr, "ACTRHEAD"); ExportSkeletonData(Ar, bones); MeshLods.Add(new Mesh($"{MeshName}.psk", Ar.GetBuffer(), new List <MaterialExporter>())); }
public static CAnimSet ConvertAnims(this USkeleton skeleton, UAnimSequence?animSequence) { var animSet = new CAnimSet(skeleton); // Copy bone names var numBones = skeleton.ReferenceSkeleton.FinalRefBoneInfo.Length; Trace.Assert(skeleton.BoneTree.Length == numBones); animSet.TrackBoneNames = new FName[numBones]; animSet.BonePositions = new CSkeletonBonePosition[numBones]; animSet.BoneModes = new EBoneRetargetingMode[numBones]; for (var boneIndex = 0; boneIndex < numBones; boneIndex++) { // Store bone name animSet.TrackBoneNames[boneIndex] = skeleton.ReferenceSkeleton.FinalRefBoneInfo[boneIndex].Name; // Store skeleton's bone transform CSkeletonBonePosition bonePosition; var transform = skeleton.ReferenceSkeleton.FinalRefBonePose[boneIndex]; bonePosition.Orientation = transform.Rotation; bonePosition.Position = transform.Translation; animSet.BonePositions[boneIndex] = bonePosition; // Process bone retargeting mode var boneMode = skeleton.BoneTree[boneIndex].TranslationRetargetingMode switch { EBoneTranslationRetargetingMode.Skeleton => EBoneRetargetingMode.Mesh, EBoneTranslationRetargetingMode.Animation => EBoneRetargetingMode.Animation, EBoneTranslationRetargetingMode.AnimationScaled => EBoneRetargetingMode.AnimationScaled, EBoneTranslationRetargetingMode.AnimationRelative => EBoneRetargetingMode.AnimationRelative, EBoneTranslationRetargetingMode.OrientAndScale => EBoneRetargetingMode.OrientAndScale, _ => EBoneRetargetingMode.OrientAndScale //todo: other modes? }; animSet.BoneModes[boneIndex] = boneMode; } // Check for NULL 'animSequence' only after CAnimSet is created: we're doing ConvertAnims(null) to create an empty AnimSet if (animSequence == null) { return(animSet); } var numTracks = animSequence.GetNumTracks(); // Store UAnimSequence in 'OriginalAnims' array, we just need it from time to time //OriginalAnims.Add(animSequence); // Create CAnimSequence var dst = new CAnimSequence(animSequence); animSet.Sequences.Add(dst); dst.Name = animSequence.Name; dst.NumFrames = animSequence.NumFrames; dst.Rate = animSequence.NumFrames / animSequence.SequenceLength * animSequence.RateScale; dst.bAdditive = animSequence.AdditiveAnimType != AAT_None; // Store information for animation retargeting. // Reference: UAnimSequence::GetRetargetTransforms() FTransform[]? retargetTransforms = null; if (animSequence.RetargetSource.IsNone && animSequence.RetargetSourceAssetReferencePose is { Length : > 0 })