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; }
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 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_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_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_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_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); }
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()); }
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; }
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; } }
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; }