bool CObjectViewer::loadMesh(const std::string &meshFileName, const std::string &skelFileName) { CPath::addSearchPath(CFile::getPath(meshFileName), false, false); // create instance of the mesh character UInstance Entity = _Scene->createInstance(meshFileName); USkeleton Skeleton = _Scene->createSkeleton(skelFileName); // if we can't create entity, skip it if (Entity.empty()) return false; // create a new entity EIT eit = (_Entities.insert (make_pair (CFile::getFilenameWithoutExtension(meshFileName), CEntity()))).first; CEntity &entity = (*eit).second; // set the entity up entity._Name = CFile::getFilenameWithoutExtension(meshFileName); entity._Instance = Entity; if (!Skeleton.empty()) { entity._Skeleton = Skeleton; entity._Skeleton.bindSkin (entity._Instance); } entity._AnimationSet = _Driver->createAnimationSet(false); entity._PlayList = _PlayListManager->createPlayList(entity._AnimationSet); return true; }
void UAnimGraphNode_PoseHandler::ValidateAnimNodeDuringCompilation(USkeleton* ForSkeleton, FCompilerResultsLog& MessageLog) { UPoseAsset* PoseAssetToCheck = GetPoseHandlerNode()->PoseAsset; UEdGraphPin* PoseAssetPin = FindPin(GET_MEMBER_NAME_STRING_CHECKED(FAnimNode_PoseHandler, PoseAsset)); if (PoseAssetPin != nullptr && PoseAssetToCheck == nullptr) { PoseAssetToCheck = Cast<UPoseAsset>(PoseAssetPin->DefaultObject); } if (PoseAssetToCheck == nullptr) { if (PoseAssetPin == nullptr || PoseAssetPin->LinkedTo.Num() == 0) { MessageLog.Error(TEXT("@@ references an unknown poseasset"), this); } } else { USkeleton* SeqSkeleton = PoseAssetToCheck->GetSkeleton(); if (SeqSkeleton && // if PoseAsset doesn't have skeleton, it might be due to PoseAsset not loaded yet, @todo: wait with anim blueprint compilation until all assets are loaded? !SeqSkeleton->IsCompatible(ForSkeleton)) { MessageLog.Error(TEXT("@@ references poseasset that uses different skeleton @@"), this, SeqSkeleton); } } Super::ValidateAnimNodeDuringCompilation(ForSkeleton, MessageLog); }
void UAnimGraphNode_PoseByName::ValidateAnimNodeDuringCompilation(class USkeleton* ForSkeleton, class FCompilerResultsLog& MessageLog) { UPoseAsset* PoseAssetToCheck = Node.PoseAsset; UEdGraphPin* PoseAssetPin = FindPin(GET_MEMBER_NAME_STRING_CHECKED(FAnimNode_PoseByName, PoseAsset)); if (PoseAssetPin != nullptr && PoseAssetToCheck == nullptr) { PoseAssetToCheck = Cast<UPoseAsset>(PoseAssetPin->DefaultObject); } if (PoseAssetToCheck == nullptr) { // we may have a connected node if (PoseAssetPin == nullptr || PoseAssetPin->LinkedTo.Num() == 0) { MessageLog.Error(TEXT("@@ references an unknown pose asset"), this); } } else { USkeleton* SeqSkeleton = PoseAssetToCheck->GetSkeleton(); if (SeqSkeleton&& // if anim sequence doesn't have skeleton, it might be due to anim sequence not loaded yet, @todo: wait with anim blueprint compilation until all assets are loaded? !SeqSkeleton->IsCompatible(ForSkeleton)) { MessageLog.Error(TEXT("@@ references sequence that uses different skeleton @@"), this, SeqSkeleton); } } }
PyObject *py_ue_skeletal_mesh_set_skeleton(ue_PyUObject * self, PyObject * args) { ue_py_check(self); PyObject *py_skeleton; if (!PyArg_ParseTuple(args, "O:skeletal_mesh_set_skeleton", &py_skeleton)) return nullptr; USkeletalMesh *mesh = ue_py_check_type<USkeletalMesh>(self); if (!mesh) return PyErr_Format(PyExc_Exception, "UObject is not a USkeletalMesh."); USkeleton *skeleton = ue_py_check_type<USkeleton>(py_skeleton); if (!skeleton) return PyErr_Format(PyExc_Exception, "argument is not a USkeleton."); mesh->ReleaseResources(); mesh->ReleaseResourcesFence.Wait(); mesh->Skeleton = skeleton; mesh->RefSkeleton = skeleton->GetReferenceSkeleton(); mesh->RefBasesInvMatrix.Empty(); mesh->CalculateInvRefMatrices(); #if WITH_EDITOR mesh->PostEditChange(); #endif mesh->InitResources(); mesh->MarkPackageDirty(); Py_RETURN_NONE; }
void UAnimGraphNode_RotationOffsetBlendSpace::ValidateAnimNodeDuringCompilation(class USkeleton* ForSkeleton, class FCompilerResultsLog& MessageLog) { if (Node.BlendSpace == NULL) { MessageLog.Error(TEXT("@@ references an unknown blend space"), this); } else if (Cast<UAimOffsetBlendSpace>(Node.BlendSpace) == NULL && Cast<UAimOffsetBlendSpace1D>(Node.BlendSpace) == NULL) { MessageLog.Error(TEXT("@@ references an invalid blend space (one that is not an aim offset)"), this); } else { USkeleton* BlendSpaceSkeleton = Node.BlendSpace->GetSkeleton(); if (BlendSpaceSkeleton && // if blend space doesn't have skeleton, it might be due to blend space not loaded yet, @todo: wait with anim blueprint compilation until all assets are loaded? !BlendSpaceSkeleton->IsCompatible(ForSkeleton)) { MessageLog.Error(TEXT("@@ references blendspace that uses different skeleton @@"), this, BlendSpaceSkeleton); } } if (UAnimationSettings::Get()->bEnablePerformanceLog) { if (Node.LODThreshold < 0) { MessageLog.Warning(TEXT("@@ contains no LOD Threshold."), this); } } }
void SAnimationSegmentViewport::InitSkeleton() { UObject *Object = NULL; AnimRefPropertyHandle->GetValue(Object); UAnimSequenceBase *AnimSequence = Cast<UAnimSequenceBase>(Object); USkeleton *Skeleton = NULL; if(AnimSequence != NULL) { Skeleton = AnimSequence->GetSkeleton(); } if( PreviewComponent != NULL && Skeleton != NULL ) { USkeletalMesh* PreviewMesh = Skeleton->GetAssetPreviewMesh(AnimSequence); if (PreviewMesh) { UAnimSingleNodeInstance * Preview = PreviewComponent->PreviewInstance; if((Preview == NULL || Preview->GetCurrentAsset() != AnimSequence) || (PreviewComponent->SkeletalMesh != PreviewMesh)) { PreviewComponent->SetSkeletalMesh(PreviewMesh); PreviewComponent->EnablePreview(true, AnimSequence, NULL); PreviewComponent->PreviewInstance->SetLooping(true); //Place the camera at a good viewer position FVector NewPosition = LevelViewportClient->GetViewLocation(); NewPosition.Normalize(); LevelViewportClient->SetViewLocation(NewPosition * (PreviewMesh->GetImportedBounds().SphereRadius*1.5f)); } } } TargetSkeleton = Skeleton; }
void UAnimGraphNode_TwoBoneIK::MoveSelectActorLocation(const USkeletalMeshComponent* SkelComp, FAnimNode_SkeletalControlBase* AnimNode) { USkeleton * Skeleton = SkelComp->SkeletalMesh->Skeleton; // create a bone select actor if (!BoneSelectActor.IsValid()) { if (Node.JointTargetLocationSpace == EBoneControlSpace::BCS_BoneSpace || Node.JointTargetLocationSpace == EBoneControlSpace::BCS_ParentBoneSpace) { int32 JointTargetIndex = Skeleton->GetReferenceSkeleton().FindBoneIndex(Node.JointTargetSpaceBoneName); if (JointTargetIndex != INDEX_NONE) { UWorld* World = SkelComp->GetWorld(); check(World); BoneSelectActor = World->SpawnActor<ABoneSelectActor>(FVector(0), FRotator(0, 0, 0)); check(BoneSelectActor.IsValid()); } } } if (!BoneSelectActor.IsValid()) { return; } // move the actor's position if (BoneSelectMode == BSM_JointTarget) { FName BoneName; int32 EffectorBoneIndex = SkelComp->GetBoneIndex(Node.EffectorSpaceBoneName); if (EffectorBoneIndex != INDEX_NONE) { BoneName = Node.EffectorSpaceBoneName; } else { BoneName = Node.IKBone.BoneName; } FVector ActorLocation = ConvertWidgetLocation(SkelComp, AnimNode->ForwardedPose, BoneName, Node.EffectorLocation, Node.EffectorLocationSpace); BoneSelectActor->SetActorLocation(ActorLocation + FVector(0, 10, 0)); } else if (BoneSelectMode == BSM_EndEffector) { int32 JointTargetIndex = SkelComp->GetBoneIndex(Node.JointTargetSpaceBoneName); if (JointTargetIndex != INDEX_NONE) { FVector ActorLocation = ConvertWidgetLocation(SkelComp, AnimNode->ForwardedPose, Node.JointTargetSpaceBoneName, Node.JointTargetLocation, Node.JointTargetLocationSpace); BoneSelectActor->SetActorLocation(ActorLocation + FVector(0, 10, 0)); } } }
PyObject *py_ue_skeleton_bones_get_num(ue_PyUObject *self, PyObject * args) { ue_py_check(self); USkeleton *skeleton = ue_py_check_type<USkeleton>(self); if (!skeleton) return PyErr_Format(PyExc_Exception, "uobject is not a USkeleton"); return PyLong_FromLong(skeleton->GetReferenceSkeleton().GetNum()); }
void UAnimGraphNode_BlendSpacePlayer::ValidateAnimNodeDuringCompilation(class USkeleton* ForSkeleton, class FCompilerResultsLog& MessageLog) { if (Node.BlendSpace == NULL) { MessageLog.Error(TEXT("@@ references an unknown blend space"), this); } else { USkeleton * BlendSpaceSkeleton = Node.BlendSpace->GetSkeleton(); if (BlendSpaceSkeleton && // if blend space doesn't have skeleton, it might be due to blend space not loaded yet, @todo: wait with anim blueprint compilation until all assets are loaded? !BlendSpaceSkeleton->IsCompatible(ForSkeleton)) { MessageLog.Error(TEXT("@@ references blendspace that uses different skeleton @@"), this, BlendSpaceSkeleton); } } }
PyObject *py_ue_skeleton_get_ref_bone_pose(ue_PyUObject *self, PyObject * args) { ue_py_check(self); int index; if (!PyArg_ParseTuple(args, "i:skeleton_get_ref_bone_pose", &index)) return nullptr; USkeleton *skeleton = ue_py_check_type<USkeleton>(self); if (!skeleton) return PyErr_Format(PyExc_Exception, "uobject is not a USkeleton"); if (!skeleton->GetReferenceSkeleton().IsValidIndex(index)) return PyErr_Format(PyExc_Exception, "invalid bone index"); return py_ue_new_ftransform(skeleton->GetReferenceSkeleton().GetRefBonePose()[index]); }
PyObject *py_ue_skeleton_get_parent_index(ue_PyUObject *self, PyObject * args) { ue_py_check(self); int index; if (!PyArg_ParseTuple(args, "i:skeleton_get_parent_index", &index)) return nullptr; USkeleton *skeleton = ue_py_check_type<USkeleton>(self); if (!skeleton) return PyErr_Format(PyExc_Exception, "uobject is not a USkeleton"); if (!skeleton->GetReferenceSkeleton().IsValidIndex(index)) return PyErr_Format(PyExc_Exception, "invalid bone index"); return PyLong_FromLong(skeleton->GetReferenceSkeleton().GetParentIndex(index)); }
PyObject *py_ue_skeleton_get_bone_name(ue_PyUObject *self, PyObject * args) { ue_py_check(self); int index; if (!PyArg_ParseTuple(args, "i:skeleton_get_bone_name", &index)) return nullptr; USkeleton *skeleton = ue_py_check_type<USkeleton>(self); if (!skeleton) return PyErr_Format(PyExc_Exception, "uobject is not a USkeleton"); if (!skeleton->GetReferenceSkeleton().IsValidIndex(index)) return PyErr_Format(PyExc_Exception, "invalid bone index"); return PyUnicode_FromString(TCHAR_TO_UTF8(*skeleton->GetReferenceSkeleton().GetBoneName(index).ToString())); }
PyObject *py_ue_skeleton_find_bone_index(ue_PyUObject *self, PyObject * args) { ue_py_check(self); char *name; if (!PyArg_ParseTuple(args, "s:skeleton_find_bone_index", &name)) return nullptr; USkeleton *skeleton = ue_py_check_type<USkeleton>(self); if (!skeleton) return PyErr_Format(PyExc_Exception, "uobject is not a USkeleton"); int32 index = skeleton->GetReferenceSkeleton().FindBoneIndex(FName(UTF8_TO_TCHAR(name))); if (!skeleton->GetReferenceSkeleton().IsValidIndex(index)) return PyErr_Format(PyExc_Exception, "unable to find bone"); return PyLong_FromLong(index); }
bool UnFbx::FFbxImporter::ImportCurveToAnimSequence(class UAnimSequence * TargetSequence, const FString& CurveName, const FbxAnimCurve* FbxCurve, int32 CurveFlags,const FbxTimeSpan AnimTimeSpan, const float ValueScale/*=1.f*/) const { if (TargetSequence && FbxCurve) { FName Name = *CurveName; USkeleton* Skeleton = TargetSequence->GetSkeleton(); FSmartNameMapping* NameMapping = Skeleton->SmartNames.GetContainer(USkeleton::AnimCurveMappingName); // Add or retrieve curve USkeleton::AnimCurveUID Uid; if (!NameMapping->Exists(Name)) { // mark skeleton dirty Skeleton->Modify(); } NameMapping->AddOrFindName(Name, Uid); FFloatCurve * CurveToImport = static_cast<FFloatCurve *>(TargetSequence->RawCurveData.GetCurveData(Uid, FRawCurveTracks::FloatType)); if(CurveToImport==NULL) { if(TargetSequence->RawCurveData.AddCurveData(Uid, CurveFlags)) { CurveToImport = static_cast<FFloatCurve *> (TargetSequence->RawCurveData.GetCurveData(Uid, FRawCurveTracks::FloatType)); } else { // this should not happen, we already checked before adding ensureMsgf(0, TEXT("FBX Import: Critical error: no memory?")); } } else { CurveToImport->FloatCurve.Reset(); } return ImportCurve(FbxCurve, CurveToImport, AnimTimeSpan, ValueScale); } return false; }
void FBoneContainer::RemapFromSkelMesh(USkeletalMesh const & SourceSkeletalMesh, USkeleton& TargetSkeleton) { int32 const SkelMeshLinkupIndex = TargetSkeleton.GetMeshLinkupIndex(&SourceSkeletalMesh); check(SkelMeshLinkupIndex != INDEX_NONE); FSkeletonToMeshLinkup const & LinkupTable = TargetSkeleton.LinkupCache[SkelMeshLinkupIndex]; // Copy LinkupTable arrays for now. // @laurent - Long term goal is to trim that down based on LOD, so we can get rid of the BoneIndicesArray and branch cost of testing if PoseBoneIndex is in that required bone index array. SkeletonToPoseBoneIndexArray = LinkupTable.SkeletonToMeshTable; PoseToSkeletonBoneIndexArray = LinkupTable.MeshToSkeletonTable; }
void FBoneContainer::RemapFromSkeleton(USkeleton const & SourceSkeleton) { // Map SkeletonBoneIndex to the SkeletalMesh Bone Index, taking into account the required bone index array. SkeletonToPoseBoneIndexArray.Init(INDEX_NONE, SourceSkeleton.GetRefLocalPoses().Num()); for(int32 Index=0; Index<BoneIndicesArray.Num(); Index++) { int32 const & PoseBoneIndex = BoneIndicesArray[Index]; SkeletonToPoseBoneIndexArray[PoseBoneIndex] = PoseBoneIndex; } // Skeleton to Skeleton mapping... PoseToSkeletonBoneIndexArray = SkeletonToPoseBoneIndexArray; }
PyObject *py_ue_skeleton_add_bone(ue_PyUObject *self, PyObject * args) { ue_py_check(self); char *name; int parent_index; PyObject *py_transform; if (!PyArg_ParseTuple(args, "siO:skeleton_add_bone", &name, &parent_index, &py_transform)) return nullptr; USkeleton *skeleton = ue_py_check_type<USkeleton>(self); if (!skeleton) return PyErr_Format(PyExc_Exception, "uobject is not a USkeleton"); ue_PyFTransform *transform = py_ue_is_ftransform(py_transform); if (!transform) return PyErr_Format(PyExc_Exception, "argument is not a FTransform"); if (skeleton->GetReferenceSkeleton().FindBoneIndex(FName(UTF8_TO_TCHAR(name))) > -1) { return PyErr_Format(PyExc_Exception, "bone %s already exists", name); } #if WITH_EDITOR skeleton->PreEditChange(nullptr); #endif { const FReferenceSkeleton &ref = skeleton->GetReferenceSkeleton(); // horrible hack to modify the skeleton in place FReferenceSkeletonModifier modifier((FReferenceSkeleton &)ref, skeleton); TCHAR *bone_name = UTF8_TO_TCHAR(name); modifier.Add(FMeshBoneInfo(FName(bone_name), FString(bone_name), parent_index), transform->transform); } #if WITH_EDITOR skeleton->PostEditChange(); #endif skeleton->MarkPackageDirty(); return PyLong_FromLong(skeleton->GetReferenceSkeleton().FindBoneIndex(FName(UTF8_TO_TCHAR(name)))); }
void FMovieScene3DTransformSectionRecorder::FinalizeSection() { FScopedSlowTask SlowTask(4.0f, NSLOCTEXT("SequenceRecorder", "ProcessingTransforms", "Processing Transforms")); bRecording = false; // if we have a valid animation recorder, we need to build our transforms from the animation // so we properly synchronize our keyframes if(AnimRecorder.IsValid()) { check(BufferedTransforms.Num() == 0); UAnimSequence* AnimSequence = AnimRecorder->GetAnimSequence(); USkeletalMeshComponent* SkeletalMeshComponent = AnimRecorder->GetSkeletalMeshComponent(); if (SkeletalMeshComponent) { USkeletalMesh* SkeletalMesh = SkeletalMeshComponent->MasterPoseComponent != nullptr ? SkeletalMeshComponent->MasterPoseComponent->SkeletalMesh : SkeletalMeshComponent->SkeletalMesh; if (AnimSequence && SkeletalMesh) { // find the root bone int32 RootIndex = INDEX_NONE; USkeleton* AnimSkeleton = AnimSequence->GetSkeleton(); for (int32 TrackIndex = 0; TrackIndex < AnimSequence->RawAnimationData.Num(); ++TrackIndex) { // verify if this bone exists in skeleton int32 BoneTreeIndex = AnimSequence->GetSkeletonIndexFromRawDataTrackIndex(TrackIndex); if (BoneTreeIndex != INDEX_NONE) { int32 BoneIndex = AnimSkeleton->GetMeshBoneIndexFromSkeletonBoneIndex(SkeletalMesh, BoneTreeIndex); int32 ParentIndex = SkeletalMesh->RefSkeleton.GetParentIndex(BoneIndex); if (ParentIndex == INDEX_NONE) { // found root RootIndex = BoneIndex; break; } } } check(RootIndex != INDEX_NONE); const float StartTime = MovieSceneSection->GetStartTime(); // we may need to offset the transform here if the animation was not recorded on the root component FTransform InvComponentTransform = AnimRecorder->GetComponentTransform().Inverse(); FRawAnimSequenceTrack& RawTrack = AnimSequence->RawAnimationData[RootIndex]; const int32 KeyCount = FMath::Max(FMath::Max(RawTrack.PosKeys.Num(), RawTrack.RotKeys.Num()), RawTrack.ScaleKeys.Num()); for (int32 KeyIndex = 0; KeyIndex < KeyCount; KeyIndex++) { FTransform Transform; if (RawTrack.PosKeys.IsValidIndex(KeyIndex)) { Transform.SetTranslation(RawTrack.PosKeys[KeyIndex]); } else if (RawTrack.PosKeys.Num() > 0) { Transform.SetTranslation(RawTrack.PosKeys[0]); } if (RawTrack.RotKeys.IsValidIndex(KeyIndex)) { Transform.SetRotation(RawTrack.RotKeys[KeyIndex]); } else if (RawTrack.RotKeys.Num() > 0) { Transform.SetRotation(RawTrack.RotKeys[0]); } if (RawTrack.ScaleKeys.IsValidIndex(KeyIndex)) { Transform.SetScale3D(RawTrack.ScaleKeys[KeyIndex]); } else if (RawTrack.ScaleKeys.Num() > 0) { Transform.SetScale3D(RawTrack.ScaleKeys[0]); } BufferedTransforms.Add(FBufferedTransformKey(InvComponentTransform * Transform, StartTime + AnimSequence->GetTimeAtFrame(KeyIndex))); } } } } SlowTask.EnterProgressFrame(); // Try to 're-wind' rotations that look like axis flips // We need to do this as a post-process because the recorder cant reliably access 'wound' rotations: // - Net quantize may use quaternions. // - Scene components cache transforms as quaternions. // - Gameplay is free to clamp/fmod rotations as it sees fit. int32 TransformCount = BufferedTransforms.Num(); for(int32 TransformIndex = 0; TransformIndex < TransformCount - 1; TransformIndex++) { FRotator& Rotator = BufferedTransforms[TransformIndex].WoundRotation; FRotator& NextRotator = BufferedTransforms[TransformIndex + 1].WoundRotation; FMath::WindRelativeAnglesDegrees(Rotator.Pitch, NextRotator.Pitch); FMath::WindRelativeAnglesDegrees(Rotator.Yaw, NextRotator.Yaw); FMath::WindRelativeAnglesDegrees(Rotator.Roll, NextRotator.Roll); } SlowTask.EnterProgressFrame(); // never unwind rotations const bool bUnwindRotation = false; // If we are syncing to an animation, use linear interpolation to avoid foot sliding etc. // Otherwise use cubic for better quality (much better for projectiles etc.) const EMovieSceneKeyInterpolation Interpolation = AnimRecorder.IsValid() ? EMovieSceneKeyInterpolation::Linear : EMovieSceneKeyInterpolation::Auto; // add buffered transforms for(const FBufferedTransformKey& BufferedTransform : BufferedTransforms) { const FVector Translation = BufferedTransform.Transform.GetTranslation(); const FVector Rotation = BufferedTransform.WoundRotation.Euler(); const FVector Scale = BufferedTransform.Transform.GetScale3D(); MovieSceneSection->AddKey(BufferedTransform.KeyTime, FTransformKey(EKey3DTransformChannel::Translation, EAxis::X, Translation.X, bUnwindRotation), Interpolation); MovieSceneSection->AddKey(BufferedTransform.KeyTime, FTransformKey(EKey3DTransformChannel::Translation, EAxis::Y, Translation.Y, bUnwindRotation), Interpolation); MovieSceneSection->AddKey(BufferedTransform.KeyTime, FTransformKey(EKey3DTransformChannel::Translation, EAxis::Z, Translation.Z, bUnwindRotation), Interpolation); MovieSceneSection->AddKey(BufferedTransform.KeyTime, FTransformKey(EKey3DTransformChannel::Rotation, EAxis::X, Rotation.X, bUnwindRotation), Interpolation); MovieSceneSection->AddKey(BufferedTransform.KeyTime, FTransformKey(EKey3DTransformChannel::Rotation, EAxis::Y, Rotation.Y, bUnwindRotation), Interpolation); MovieSceneSection->AddKey(BufferedTransform.KeyTime, FTransformKey(EKey3DTransformChannel::Rotation, EAxis::Z, Rotation.Z, bUnwindRotation), Interpolation); MovieSceneSection->AddKey(BufferedTransform.KeyTime, FTransformKey(EKey3DTransformChannel::Scale, EAxis::X, Scale.X, bUnwindRotation), Interpolation); MovieSceneSection->AddKey(BufferedTransform.KeyTime, FTransformKey(EKey3DTransformChannel::Scale, EAxis::Y, Scale.Y, bUnwindRotation), Interpolation); MovieSceneSection->AddKey(BufferedTransform.KeyTime, FTransformKey(EKey3DTransformChannel::Scale, EAxis::Z, Scale.Z, bUnwindRotation), Interpolation); } BufferedTransforms.Empty(); SlowTask.EnterProgressFrame(); // now remove linear keys TPair<FRichCurve*, float> CurvesAndTolerances[] = { TPairInitializer<FRichCurve*, float>(&MovieSceneSection->GetTranslationCurve(EAxis::X), KINDA_SMALL_NUMBER), TPairInitializer<FRichCurve*, float>(&MovieSceneSection->GetTranslationCurve(EAxis::Y), KINDA_SMALL_NUMBER), TPairInitializer<FRichCurve*, float>(&MovieSceneSection->GetTranslationCurve(EAxis::Z), KINDA_SMALL_NUMBER), TPairInitializer<FRichCurve*, float>(&MovieSceneSection->GetRotationCurve(EAxis::X), KINDA_SMALL_NUMBER), TPairInitializer<FRichCurve*, float>(&MovieSceneSection->GetRotationCurve(EAxis::Y), KINDA_SMALL_NUMBER), TPairInitializer<FRichCurve*, float>(&MovieSceneSection->GetRotationCurve(EAxis::Z), KINDA_SMALL_NUMBER), TPairInitializer<FRichCurve*, float>(&MovieSceneSection->GetScaleCurve(EAxis::X), KINDA_SMALL_NUMBER), TPairInitializer<FRichCurve*, float>(&MovieSceneSection->GetScaleCurve(EAxis::Y), KINDA_SMALL_NUMBER), TPairInitializer<FRichCurve*, float>(&MovieSceneSection->GetScaleCurve(EAxis::Z), KINDA_SMALL_NUMBER), }; for(TPair<FRichCurve*, float>& CurveAndTolerance : CurvesAndTolerances) { CurveAndTolerance.Key->RemoveRedundantKeys(CurveAndTolerance.Value); } // we cant remove redundant tracks if we were attached as the playback relies on update order of // transform tracks. Without this track, relative transforms would accumulate. if(!bWasAttached) { // now we have reduced our keys, if we dont have any, remove the section as it is redundant if( MovieSceneSection->GetTranslationCurve(EAxis::X).Keys.Num() == 0 && MovieSceneSection->GetTranslationCurve(EAxis::Y).Keys.Num() == 0 && MovieSceneSection->GetTranslationCurve(EAxis::Z).Keys.Num() == 0 && MovieSceneSection->GetRotationCurve(EAxis::X).Keys.Num() == 0 && MovieSceneSection->GetRotationCurve(EAxis::Y).Keys.Num() == 0 && MovieSceneSection->GetRotationCurve(EAxis::Z).Keys.Num() == 0 && MovieSceneSection->GetScaleCurve(EAxis::X).Keys.Num() == 0 && MovieSceneSection->GetScaleCurve(EAxis::Y).Keys.Num() == 0 && MovieSceneSection->GetScaleCurve(EAxis::Z).Keys.Num() == 0) { if(DefaultTransform.Equals(FTransform::Identity)) { MovieScene->RemoveTrack(*MovieSceneTrack.Get()); } } } SlowTask.EnterProgressFrame(); }
bool UAnimExporterITP::ExportText(const FExportObjectInnerContext* Context, UObject* Object, const TCHAR* Type, FOutputDevice& Ar, FFeedbackContext* Warn, uint32 PortFlags /*= 0*/) { UAnimSequence* AnimSeq = CastChecked<UAnimSequence>(Object); USkeleton* Skeleton = AnimSeq->GetSkeleton(); const FReferenceSkeleton& RefSkeleton = Skeleton->GetReferenceSkeleton(); USkeletalMesh* SkelMesh = Skeleton->GetPreviewMesh(); if (AnimSeq->SequenceLength == 0.f) { // something is wrong return false; } const float FrameRate = AnimSeq->NumFrames / AnimSeq->SequenceLength; // Open another archive FArchive* File = IFileManager::Get().CreateFileWriter(*UExporter::CurrentFilename); // Let's try the header... File->Logf(TEXT("{")); File->Logf(TEXT("\t\"metadata\":{")); File->Logf(TEXT("\t\t\"type\":\"itpanim\",")); File->Logf(TEXT("\t\t\"version\":2")); File->Logf(TEXT("\t},")); File->Logf(TEXT("\t\"sequence\":{")); File->Logf(TEXT("\t\t\"frames\":%d,"), AnimSeq->NumFrames); File->Logf(TEXT("\t\t\"length\":%f,"), AnimSeq->SequenceLength); File->Logf(TEXT("\t\t\"bonecount\":%d,"), RefSkeleton.GetNum()); File->Logf(TEXT("\t\t\"tracks\":[")); bool firstOutput = false; for (int32 BoneIndex = 0; BoneIndex < RefSkeleton.GetNum(); ++BoneIndex) { //int32 BoneTreeIndex = Skeleton->GetSkeletonBoneIndexFromMeshBoneIndex(SkelMesh, BoneIndex); int32 BoneTrackIndex = Skeleton->GetAnimationTrackIndex(BoneIndex, AnimSeq); if (BoneTrackIndex == INDEX_NONE) { // If this sequence does not have a track for the current bone, then skip it continue; } if (firstOutput) { File->Logf(TEXT("\t\t\t},")); } firstOutput = true; File->Logf(TEXT("\t\t\t{")); File->Logf(TEXT("\t\t\t\t\"bone\":%d,"), BoneIndex); File->Logf(TEXT("\t\t\t\t\"transforms\":[")); float AnimTime = 0.0f; float AnimEndTime = AnimSeq->SequenceLength; // Subtracts 1 because NumFrames includes an initial pose for 0.0 second double TimePerKey = (AnimSeq->SequenceLength / (AnimSeq->NumFrames - 1)); const float AnimTimeIncrement = TimePerKey; bool bLastKey = false; // Step through each frame and add the bone's transformation data while (!bLastKey) { const TArray<FBoneNode>& BoneTree = Skeleton->GetBoneTree(); FTransform BoneAtom; AnimSeq->GetBoneTransform(BoneAtom, BoneTrackIndex, AnimTime, true); bLastKey = AnimTime >= AnimEndTime; File->Logf(TEXT("\t\t\t\t\t{")); FQuat rot = BoneAtom.GetRotation(); // For the root bone, we need to fix-up the rotation because Unreal exports // animations with Y-forward for some reason (maybe because Maya?) if (BoneIndex == 0) { FQuat addRot(FVector(0.0f, 0.0f, 1.0f), -1.57f); rot = addRot * rot; } File->Logf(TEXT("\t\t\t\t\t\t\"rot\":[%f,%f,%f,%f],"), rot.X, rot.Y, rot.Z, rot.W); FVector trans = BoneAtom.GetTranslation(); // Sanjay: If it's skeleton retargeting, change the translation to be from the ref pose skeleton if (BoneTree[BoneIndex].TranslationRetargetingMode == EBoneTranslationRetargetingMode::Skeleton) { const FTransform& BoneTransform = RefSkeleton.GetRefBonePose()[BoneIndex]; trans = BoneTransform.GetTranslation(); } File->Logf(TEXT("\t\t\t\t\t\t\"trans\":[%f,%f,%f]"), trans.X, trans.Y, trans.Z); if (!bLastKey) { File->Logf(TEXT("\t\t\t\t\t},")); } else { File->Logf(TEXT("\t\t\t\t\t}")); } AnimTime += AnimTimeIncrement; } File->Logf(TEXT("\t\t\t\t]"), BoneIndex); } File->Logf(TEXT("\t\t\t}")); File->Logf(TEXT("\t\t]")); File->Logf(TEXT("\t}")); File->Logf(TEXT("}")); delete File; return true; }
UObject* USpriterImporterFactory::FactoryCreateText(UClass* InClass, UObject* InParent, FName InName, EObjectFlags Flags, UObject* Context, const TCHAR* Type, const TCHAR*& Buffer, const TCHAR* BufferEnd, FFeedbackContext* Warn) { Flags |= RF_Transactional; FEditorDelegates::OnAssetPreImport.Broadcast(this, InClass, InParent, InName, Type); FAssetToolsModule& AssetToolsModule = FModuleManager::GetModuleChecked<FAssetToolsModule>("AssetTools"); bool bLoadedSuccessfully = true; const FString CurrentFilename = UFactory::GetCurrentFilename(); FString CurrentSourcePath; FString FilenameNoExtension; FString UnusedExtension; FPaths::Split(CurrentFilename, CurrentSourcePath, FilenameNoExtension, UnusedExtension); const FString LongPackagePath = FPackageName::GetLongPackagePath(InParent->GetOutermost()->GetPathName()); const FString NameForErrors(InName.ToString()); const FString FileContent(BufferEnd - Buffer, Buffer); TSharedPtr<FJsonObject> DescriptorObject = ParseJSON(FileContent, NameForErrors); UPaperSpriterImportData* Result = nullptr; // Parse the file FSpriterSCON DataModel; if (DescriptorObject.IsValid()) { DataModel.ParseFromJSON(DescriptorObject, NameForErrors, /*bSilent=*/ false, /*bPreParseOnly=*/ false); } // Create the new 'hub' asset and convert the data model over if (DataModel.IsValid()) { const bool bSilent = false; Result = NewObject<UPaperSpriterImportData>(InParent, InName, Flags); Result->Modify(); //@TODO: Do some things here maybe? Result->ImportedData = DataModel; // Import the assets in the folders for (const FSpriterFolder& Folder : DataModel.Folders) { for (const FSpriterFile& File : Folder.Files) { const FString RelativeFilename = File.Name.Replace(TEXT("\\"), TEXT("/"), ESearchCase::CaseSensitive); const FString SourceSpriterFilePath = FPaths::Combine(*CurrentSourcePath, *RelativeFilename); FString RelativeDestPath; FString JustFilename; FString JustExtension; FPaths::Split(RelativeFilename, /*out*/ RelativeDestPath, /*out*/ JustFilename, /*out*/ JustExtension); if (File.FileType == ESpriterFileType::Sprite) { const FString TargetTexturePath = LongPackagePath / TEXT("Textures") / RelativeDestPath; const FString TargetSpritePath = LongPackagePath / TEXT("Sprites") / RelativeDestPath; // Import the texture UTexture2D* ImportedTexture = ImportTexture(SourceSpriterFilePath, TargetTexturePath); if (ImportTexture == nullptr) { SPRITER_IMPORT_ERROR(TEXT("Failed to import texture '%s' while importing '%s'"), *SourceSpriterFilePath, *CurrentFilename); } // Create a sprite from it UPaperSprite* ImportedSprite = CastChecked<UPaperSprite>(CreateNewAsset(UPaperSprite::StaticClass(), TargetSpritePath, JustFilename, Flags)); const ESpritePivotMode::Type PivotMode = ConvertNormalizedPivotPointToPivotMode(File.PivotX, File.PivotY); const double PivotInPixelsX = File.Width * File.PivotX; const double PivotInPixelsY = File.Height * File.PivotY; ImportedSprite->SetPivotMode(PivotMode, FVector2D((float)PivotInPixelsX, (float)PivotInPixelsY)); FSpriteAssetInitParameters SpriteInitParams; SpriteInitParams.SetTextureAndFill(ImportedTexture); GetDefault<UPaperImporterSettings>()->ApplySettingsForSpriteInit(SpriteInitParams); SpriteInitParams.SetPixelsPerUnrealUnit(1.0f); ImportedSprite->InitializeSprite(SpriteInitParams); } else if (File.FileType == ESpriterFileType::Sound) { // Import the sound const FString TargetAssetPath = LongPackagePath / RelativeDestPath; UObject* ImportedSound = ImportAsset(SourceSpriterFilePath, TargetAssetPath); } else if (File.FileType != ESpriterFileType::INVALID) { ensureMsgf(false, TEXT("Importer was not updated when a new entry was added to ESpriterFileType")); } // TMap<FString, class UTexture2D*> ImportedTextures; // TMap<FString, class UPaperSprite> ImportedSprites; } } for (const FSpriterEntity& Entity : DataModel.Entities) { // Extract the common/shared skeleton FBoneHierarchyBuilder HierarchyBuilder; HierarchyBuilder.ProcessHierarchy(Entity); // Create the skeletal mesh const FString TargetMeshName = Entity.Name + TEXT("_SkelMesh"); const FString TargetMeshPath = LongPackagePath; USkeletalMesh* SkeletalMesh = CastChecked<USkeletalMesh>(CreateNewAsset(USkeletalMesh::StaticClass(), TargetMeshPath, TargetMeshName, Flags)); // Create the skeleton const FString TargetSkeletonName = Entity.Name + TEXT("_Skeleton"); const FString TargetSkeletonPath = LongPackagePath; USkeleton* EntitySkeleton = CastChecked<USkeleton>(CreateNewAsset(USkeleton::StaticClass(), TargetSkeletonPath, TargetSkeletonName, Flags)); // Initialize the mesh asset FSkeletalMeshResource* ImportedResource = SkeletalMesh->GetImportedResource(); check(ImportedResource->LODModels.Num() == 0); ImportedResource->LODModels.Empty(); FStaticLODModel& LODModel = *new (ImportedResource->LODModels) FStaticLODModel(); SkeletalMesh->LODInfo.Empty(); SkeletalMesh->LODInfo.AddZeroed(); SkeletalMesh->LODInfo[0].LODHysteresis = 0.02f; FSkeletalMeshOptimizationSettings Settings; // set default reduction settings values SkeletalMesh->LODInfo[0].ReductionSettings = Settings; // Create initial bounding box based on expanded version of reference pose for meshes without physics assets. Can be overridden by artist. // FBox BoundingBox(SkelMeshImportDataPtr->Points.GetData(), SkelMeshImportDataPtr->Points.Num()); // FBox Temp = BoundingBox; // FVector MidMesh = 0.5f*(Temp.Min + Temp.Max); // BoundingBox.Min = Temp.Min + 1.0f*(Temp.Min - MidMesh); // BoundingBox.Max = Temp.Max + 1.0f*(Temp.Max - MidMesh); // // Tuck up the bottom as this rarely extends lower than a reference pose's (e.g. having its feet on the floor). // // Maya has Y in the vertical, other packages have Z. // //BEN const int32 CoordToTuck = bAssumeMayaCoordinates ? 1 : 2; // //BEN BoundingBox.Min[CoordToTuck] = Temp.Min[CoordToTuck] + 0.1f*(Temp.Min[CoordToTuck] - MidMesh[CoordToTuck]); // BoundingBox.Min[2] = Temp.Min[2] + 0.1f*(Temp.Min[2] - MidMesh[2]); // SkeletalMesh->Bounds = FBoxSphereBounds(BoundingBox); // Store whether or not this mesh has vertex colors // SkeletalMesh->bHasVertexColors = SkelMeshImportDataPtr->bHasVertexColors; // Pass the number of texture coordinate sets to the LODModel. Ensure there is at least one UV coord LODModel.NumTexCoords = 1;// FMath::Max<uint32>(1, SkelMeshImportDataPtr->NumTexCoords); // Create the reference skeleton and update LOD0 FReferenceSkeleton& RefSkeleton = SkeletalMesh->RefSkeleton; HierarchyBuilder.CopyToRefSkeleton(RefSkeleton); SkeletalMesh->CalculateRequiredBones(LODModel, RefSkeleton, /*BonesToRemove=*/ nullptr); SkeletalMesh->CalculateInvRefMatrices(); // Initialize the skeleton asset EntitySkeleton->MergeAllBonesToBoneTree(SkeletalMesh); // Point the mesh and skeleton at each other SkeletalMesh->Skeleton = EntitySkeleton; EntitySkeleton->SetPreviewMesh(SkeletalMesh); // Create the animations for (const FSpriterAnimation& Animation : Entity.Animations) { //@TODO: That thing I said... const FString TargetAnimationName = Animation.Name; const FString TargetAnimationPath = LongPackagePath / TEXT("Animations"); UAnimSequence* AnimationAsset = CastChecked<UAnimSequence>(CreateNewAsset(UAnimSequence::StaticClass(), TargetAnimationPath, TargetAnimationName, Flags)); AnimationAsset->SetSkeleton(EntitySkeleton); // if you have one pose(thus 0.f duration), it still contains animation, so we'll need to consider that as MINIMUM_ANIMATION_LENGTH time length const float DurationInSeconds = Animation.LengthInMS * 0.001f; AnimationAsset->SequenceLength = FMath::Max<float>(DurationInSeconds, MINIMUM_ANIMATION_LENGTH); const bool bSourceDataExists = (AnimationAsset->SourceRawAnimationData.Num() > 0); TArray<struct FRawAnimSequenceTrack>& RawAnimationData = bSourceDataExists ? AnimationAsset->SourceRawAnimationData : AnimationAsset->RawAnimationData; int32 TotalNumKeys = 0; for (const FSpriterTimeline& Timeline : Animation.Timelines) { if (Timeline.ObjectType != ESpriterObjectType::Bone) { continue; } const FName BoneName = Entity.Objects[Timeline.ObjectIndex].ObjectName; const int32 RefBoneIndex = EntitySkeleton->GetReferenceSkeleton().FindBoneIndex(BoneName); check(RefBoneIndex != INDEX_NONE); FRawAnimSequenceTrack RawTrack; RawTrack.PosKeys.Empty(); RawTrack.RotKeys.Empty(); RawTrack.ScaleKeys.Empty(); int32 NumKeysForTrack = 0; //@TODO: Quick and dirty resampling code that needs to be replaced (totally ignores curve type, edge cases, etc...) const float ResampleFPS = 30.0f; int32 DesiredNumKeys = FMath::CeilToInt(ResampleFPS * DurationInSeconds); const float TimePerKey = 1.0f / ResampleFPS; float CurrentSampleTime = 0.0f; for (int32 FrameIndex = 0; FrameIndex < DesiredNumKeys; ++FrameIndex) { int32 LowerKeyIndex = 0; for (; LowerKeyIndex < Timeline.Keys.Num(); ++LowerKeyIndex) { if (Timeline.Keys[LowerKeyIndex].TimeInMS * 0.001f > CurrentSampleTime) { --LowerKeyIndex; break; } } if (LowerKeyIndex >= Timeline.Keys.Num()) { LowerKeyIndex = Timeline.Keys.Num() - 1; } int32 UpperKeyIndex = LowerKeyIndex + 1; float UpperKeyTime = 0.0f; if (UpperKeyIndex >= Timeline.Keys.Num()) { UpperKeyTime = DurationInSeconds; if (Animation.bIsLooping) { UpperKeyIndex = 0; } else { UpperKeyIndex = Timeline.Keys.Num() - 1; } } else { UpperKeyTime = Timeline.Keys[UpperKeyIndex].TimeInMS * 0.001f; } const FSpriterFatTimelineKey& TimelineKey0 = Timeline.Keys[LowerKeyIndex]; const FSpriterFatTimelineKey& TimelineKey1 = Timeline.Keys[UpperKeyIndex]; const float LowerKeyTime = TimelineKey0.TimeInMS * 0.001f; const FTransform LocalTransform0 = TimelineKey0.Info.ConvertToTransform(); const FTransform LocalTransform1 = TimelineKey1.Info.ConvertToTransform(); FTransform LocalTransform = LocalTransform0; if (LowerKeyIndex != UpperKeyIndex) { const float Alpha = (CurrentSampleTime - LowerKeyTime) / (UpperKeyTime - LowerKeyTime); LocalTransform.Blend(LocalTransform0, LocalTransform1, Alpha); } RawTrack.ScaleKeys.Add(LocalTransform.GetScale3D()); RawTrack.PosKeys.Add(LocalTransform.GetTranslation()); RawTrack.RotKeys.Add(LocalTransform.GetRotation()); ++NumKeysForTrack; CurrentSampleTime += TimePerKey; } // // for (const FSpriterFatTimelineKey& TimelineKey : Timeline.Keys) // { // //@TODO: Ignoring TimeInMS // const FTransform LocalTransform = TimelineKey.Info.ConvertToTransform(); // // RawTrack.ScaleKeys.Add(LocalTransform.GetScale3D()); // RawTrack.PosKeys.Add(LocalTransform.GetTranslation()); // RawTrack.RotKeys.Add(LocalTransform.GetRotation()); // // ++NumKeysForTrack; // } // RawAnimationData.Add(RawTrack); AnimationAsset->AnimationTrackNames.Add(BoneName); // add mapping to skeleton bone track AnimationAsset->TrackToSkeletonMapTable.Add(FTrackToSkeletonMap(RefBoneIndex)); TotalNumKeys = FMath::Max(TotalNumKeys, NumKeysForTrack); } AnimationAsset->NumFrames = TotalNumKeys; AnimationAsset->MarkRawDataAsModified(); // compress animation { GWarn->BeginSlowTask(LOCTEXT("BeginCompressAnimation", "Compress Animation"), true); GWarn->StatusForceUpdate(1, 1, LOCTEXT("CompressAnimation", "Compressing Animation")); // if source data exists, you should bake it to Raw to apply if (bSourceDataExists) { AnimationAsset->BakeTrackCurvesToRawAnimation(); } else { // otherwise just compress AnimationAsset->PostProcessSequence(); } // run debug mode GWarn->EndSlowTask(); } // NewAnimation = FFbxImporter->ImportAnimations(Skeleton, Outer, SortedLinks, AnimName, TemplateImportData, FBXMeshNodeArray); // // if (NewAnimation) // { // // since to know full path, reimport will need to do same // UFbxAnimSequenceImportData* ImportData = UFbxAnimSequenceImportData::GetImportDataForAnimSequence(NewAnimation, TemplateImportData); // ImportData->SourceFilePath = FReimportManager::SanitizeImportFilename(UFactory::CurrentFilename, NewAnimation); // ImportData->SourceFileTimestamp = IFileManager::Get().GetTimeStamp(*UFactory::CurrentFilename).ToString(); // } } } Result->PostEditChange(); } else { // Failed to parse the JSON bLoadedSuccessfully = false; } if (Result != nullptr) { //@TODO: Need to do this // Store the current file path and timestamp for re-import purposes // UAssetImportData* ImportData = UTileMapAssetImportData::GetImportDataForTileMap(Result); // ImportData->SourceFilePath = FReimportManager::SanitizeImportFilename(CurrentFilename, Result); // ImportData->SourceFileTimestamp = IFileManager::Get().GetTimeStamp(*CurrentFilename).ToString(); } FEditorDelegates::OnAssetPostImport.Broadcast(this, Result); return Result; }
void UAnimGraphNode_SkeletalControlBase::ConvertToComponentSpaceTransform(const USkeletalMeshComponent* SkelComp, const FTransform & InTransform, FTransform & OutCSTransform, int32 BoneIndex, EBoneControlSpace Space) const { USkeleton * Skeleton = SkelComp->SkeletalMesh->Skeleton; switch (Space) { case BCS_WorldSpace: { OutCSTransform = InTransform; OutCSTransform.SetToRelativeTransform(SkelComp->ComponentToWorld); } break; case BCS_ComponentSpace: { // Component Space, no change. OutCSTransform = InTransform; } break; case BCS_ParentBoneSpace: if (BoneIndex != INDEX_NONE) { const int32 ParentIndex = Skeleton->GetReferenceSkeleton().GetParentIndex(BoneIndex); if (ParentIndex != INDEX_NONE) { const int32 MeshParentIndex = Skeleton->GetMeshBoneIndexFromSkeletonBoneIndex(SkelComp->SkeletalMesh, ParentIndex); if (MeshParentIndex != INDEX_NONE) { const FTransform ParentTM = SkelComp->GetBoneTransform(MeshParentIndex); OutCSTransform = InTransform * ParentTM; } else { OutCSTransform = InTransform; } } } break; case BCS_BoneSpace: if (BoneIndex != INDEX_NONE) { const int32 MeshBoneIndex = Skeleton->GetMeshBoneIndexFromSkeletonBoneIndex(SkelComp->SkeletalMesh, BoneIndex); if (MeshBoneIndex != INDEX_NONE) { const FTransform BoneTM = SkelComp->GetBoneTransform(MeshBoneIndex); OutCSTransform = InTransform * BoneTM; } else { OutCSTransform = InTransform; } } break; default: if (SkelComp->SkeletalMesh) { UE_LOG(LogAnimation, Warning, TEXT("ConvertToComponentSpaceTransform: Unknown BoneSpace %d for Mesh: %s"), (uint8)Space, *SkelComp->SkeletalMesh->GetFName().ToString()); } else { UE_LOG(LogAnimation, Warning, TEXT("ConvertToComponentSpaceTransform: Unknown BoneSpace %d for Skeleton: %s"), (uint8)Space, *Skeleton->GetFName().ToString()); } break; } }
void FFbxExporter::ExportAnimSequenceToFbx(const UAnimSequence* AnimSeq, const USkeletalMesh* SkelMesh, TArray<FbxNode*>& BoneNodes, FbxAnimLayer* InAnimLayer, float AnimStartOffset, float AnimEndOffset, float AnimPlayRate, float StartTime) { USkeleton* Skeleton = AnimSeq->GetSkeleton(); if (AnimSeq->SequenceLength == 0.f) { // something is wrong return; } const float FrameRate = AnimSeq->NumFrames / AnimSeq->SequenceLength; // set time correctly FbxTime ExportedStartTime, ExportedStopTime; if ( FMath::IsNearlyEqual(FrameRate, DEFAULT_SAMPLERATE, 1.f) ) { ExportedStartTime.SetGlobalTimeMode(FbxTime::eFrames30); ExportedStopTime.SetGlobalTimeMode(FbxTime::eFrames30); } else { ExportedStartTime.SetGlobalTimeMode(FbxTime::eCustom, FrameRate); ExportedStopTime.SetGlobalTimeMode(FbxTime::eCustom, FrameRate); } ExportedStartTime.SetSecondDouble(0.f); ExportedStopTime.SetSecondDouble(AnimSeq->SequenceLength); FbxTimeSpan ExportedTimeSpan; ExportedTimeSpan.Set(ExportedStartTime, ExportedStopTime); AnimStack->SetLocalTimeSpan(ExportedTimeSpan); // Add the animation data to the bone nodes for(int32 BoneIndex = 0; BoneIndex < BoneNodes.Num(); ++BoneIndex) { FbxNode* CurrentBoneNode = BoneNodes[BoneIndex]; // Create the AnimCurves FbxAnimCurve* Curves[6]; Curves[0] = CurrentBoneNode->LclTranslation.GetCurve(InAnimLayer, FBXSDK_CURVENODE_COMPONENT_X, true); Curves[1] = CurrentBoneNode->LclTranslation.GetCurve(InAnimLayer, FBXSDK_CURVENODE_COMPONENT_Y, true); Curves[2] = CurrentBoneNode->LclTranslation.GetCurve(InAnimLayer, FBXSDK_CURVENODE_COMPONENT_Z, true); Curves[3] = CurrentBoneNode->LclRotation.GetCurve(InAnimLayer, FBXSDK_CURVENODE_COMPONENT_X, true); Curves[4] = CurrentBoneNode->LclRotation.GetCurve(InAnimLayer, FBXSDK_CURVENODE_COMPONENT_Y, true); Curves[5] = CurrentBoneNode->LclRotation.GetCurve(InAnimLayer, FBXSDK_CURVENODE_COMPONENT_Z, true); float AnimTime = AnimStartOffset; float AnimEndTime = (AnimSeq->SequenceLength - AnimEndOffset); // Subtracts 1 because NumFrames includes an initial pose for 0.0 second double TimePerKey = (AnimSeq->SequenceLength / (AnimSeq->NumFrames-1)); const float AnimTimeIncrement = TimePerKey * AnimPlayRate; FbxTime ExportTime; ExportTime.SetSecondDouble(StartTime); FbxTime ExportTimeIncrement; ExportTimeIncrement.SetSecondDouble( TimePerKey ); int32 BoneTreeIndex = Skeleton->GetSkeletonBoneIndexFromMeshBoneIndex(SkelMesh, BoneIndex); int32 BoneTrackIndex = Skeleton->GetAnimationTrackIndex(BoneTreeIndex, AnimSeq); if(BoneTrackIndex == INDEX_NONE) { // If this sequence does not have a track for the current bone, then skip it continue; } for(int32 i = 0; i < 6; ++i) { Curves[i]->KeyModifyBegin(); } bool bLastKey = false; // Step through each frame and add the bone's transformation data while (!bLastKey) { FTransform BoneAtom; AnimSeq->GetBoneTransform(BoneAtom, BoneTrackIndex, AnimTime, true); FbxVector4 Translation = Converter.ConvertToFbxPos(BoneAtom.GetTranslation()); FbxVector4 Rotation = Converter.ConvertToFbxRot(BoneAtom.GetRotation().Euler()); int32 lKeyIndex; bLastKey = AnimTime >= AnimEndTime; for(int32 i = 0, j=3; i < 3; ++i, ++j) { lKeyIndex = Curves[i]->KeyAdd(ExportTime); Curves[i]->KeySetValue(lKeyIndex, Translation[i]); Curves[i]->KeySetInterpolation(lKeyIndex, bLastKey ? FbxAnimCurveDef::eInterpolationConstant : FbxAnimCurveDef::eInterpolationCubic); if( bLastKey ) { Curves[i]->KeySetConstantMode( lKeyIndex, FbxAnimCurveDef::eConstantStandard ); } lKeyIndex = Curves[j]->KeyAdd(ExportTime); Curves[j]->KeySetValue(lKeyIndex, Rotation[i]); Curves[j]->KeySetInterpolation(lKeyIndex, bLastKey ? FbxAnimCurveDef::eInterpolationConstant : FbxAnimCurveDef::eInterpolationCubic); if( bLastKey ) { Curves[j]->KeySetConstantMode( lKeyIndex, FbxAnimCurveDef::eConstantStandard ); } } ExportTime += ExportTimeIncrement; AnimTime += AnimTimeIncrement; } for(int32 i = 0; i < 6; ++i) { Curves[i]->KeyModifyEnd(); } } }