UObject* UAnimSequenceFactory::FactoryCreateNew(UClass* Class, UObject* InParent, FName Name, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn) { if (TargetSkeleton) { UAnimSequence* AnimSequence = NewObject<UAnimSequence>(InParent, Class, Name, Flags); // @todo I think this will crash, we should support differentoptions AnimSequence->SequenceLength = 0.f; AnimSequence->NumFrames = 0; AnimSequence->SetSkeleton( TargetSkeleton ); return AnimSequence; } return NULL; }
void UAnimSet::ResetAnimSet() { #if WITH_EDITORONLY_DATA // Make sure we handle AnimSequence references properly before emptying the array. for(int32 i=0; i<Sequences.Num(); i++) { UAnimSequence* AnimSeq = Sequences[i]; if( AnimSeq ) { AnimSeq->RecycleAnimSequence(); } } Sequences.Empty(); TrackBoneNames.Empty(); LinkupCache.Empty(); SkelMesh2LinkupCache.Empty(); // We need to re-init any skeleltal mesh components now, because they might still have references to linkups in this set. for(TObjectIterator<USkeletalMeshComponent> It;It;++It) { USkeletalMeshComponent* SkelComp = *It; if(!SkelComp->IsPendingKill() && !SkelComp->IsTemplate()) { SkelComp->InitAnim(true); } } #endif // WITH_EDITORONLY_DATA }
bool UAnimationAsset::ReplaceSkeleton(USkeleton* NewSkeleton, bool bConvertSpaces/*=false*/) { // if it's not same if (NewSkeleton != Skeleton) { // get all sequences that need to change TArray<UAnimSequence*> AnimSeqsToReplace; if (UAnimSequence* AnimSequence = Cast<UAnimSequence>(this)) { AnimSeqsToReplace.AddUnique(AnimSequence); } if (GetAllAnimationSequencesReferred(AnimSeqsToReplace)) { for (auto Iter = AnimSeqsToReplace.CreateIterator(); Iter; ++Iter) { UAnimSequence* AnimSeq = *Iter; if (AnimSeq && AnimSeq->Skeleton != NewSkeleton) { AnimSeq->RemapTracksToNewSkeleton(NewSkeleton, bConvertSpaces); } } } SetSkeleton(NewSkeleton); PostEditChange(); MarkPackageDirty(); return true; } return false; }
void UAnimMontage::EvaluateCurveData(class UAnimInstance* Instance, float CurrentTime, float BlendWeight ) const { Super::EvaluateCurveData(Instance, CurrentTime, BlendWeight); // I also need to evaluate curve of the animation // for now we only get the first slot // in the future, we'll need to do based on highest weight? // first get all the montage instance weight this slot node has if ( SlotAnimTracks.Num() > 0 ) { const FAnimTrack& Track = SlotAnimTracks[0].AnimTrack; for (int32 I=0; I<Track.AnimSegments.Num(); ++I) { const FAnimSegment& AnimSegment = Track.AnimSegments[I]; float PositionInAnim = 0.f; float Weight = 0.f; UAnimSequenceBase* AnimRef = AnimSegment.GetAnimationData(CurrentTime, PositionInAnim, Weight); // make this to be 1 function if ( AnimRef && Weight > ZERO_ANIMWEIGHT_THRESH ) { // todo anim: hack - until we fix animcomposite UAnimSequence * Sequence = Cast<UAnimSequence>(AnimRef); if ( Sequence ) { Sequence->EvaluateCurveData(Instance, PositionInAnim, BlendWeight*Weight); } } } } }
void UAnimPreviewInstance::SetKeyImplementation(const FCompactPose& PreControllerInLocalSpace, const FCompactPose& PostControllerInLocalSpace) { #if WITH_EDITOR // evaluate the curve data first UAnimSequence* CurrentSequence = Cast<UAnimSequence>(CurrentAsset); UDebugSkelMeshComponent* Component = Cast<UDebugSkelMeshComponent> (GetSkelMeshComponent()); if(CurrentSequence && CurrentSkeleton && Component && Component->SkeletalMesh) { FScopedTransaction ScopedTransaction(LOCTEXT("SetKey", "Set Key")); CurrentSequence->Modify(true); Modify(); TArray<FName> BonesToModify; // need to get component transform first. Depending on when this gets called, the transform is not up-to-date. // first look at the bonecontrollers, and convert each bone controller to transform curve key // and add new curvebonecontrollers with additive data type // clear bone controller data for(auto& SingleBoneController : BoneControllers) { // find bone name, and just get transform of the bone in local space // and get the additive data // find if this already exists, then just add curve data only FName BoneName = SingleBoneController.BoneToModify.BoneName; // now convert data const FMeshPoseBoneIndex MeshBoneIndex(Component->GetBoneIndex(BoneName)); const FCompactPoseBoneIndex BoneIndex = RequiredBones.MakeCompactPoseIndex(MeshBoneIndex); FTransform LocalTransform = PostControllerInLocalSpace[BoneIndex]; // now we have LocalTransform and get additive data FTransform AdditiveTransform = LocalTransform.GetRelativeTransform(PreControllerInLocalSpace[BoneIndex]); AddKeyToSequence(CurrentSequence, CurrentTime, BoneName, AdditiveTransform); BonesToModify.Add(BoneName); } // see if the bone is selected right now and if that is added - if bone is selected, we should add identity key to it. if ( Component->BonesOfInterest.Num() > 0 ) { // if they're selected, we should add to the modifyBone list even if they're not modified, so that they can key that point. // first make sure those are added // if not added, make sure to set the key for them for (const auto& BoneIndex : Component->BonesOfInterest) { FName BoneName = Component->GetBoneName(BoneIndex); // if it's not on BonesToModify, add identity here. if (!BonesToModify.Contains(BoneName)) { AddKeyToSequence(CurrentSequence, CurrentTime, BoneName, FTransform::Identity); } } } ResetModifiedBone(false); OnSetKeyCompleteDelegate.ExecuteIfBound(); } #endif }
void UAnimPreviewInstance::RefreshCurveBoneControllers() { // go through all curves and see if it has Transform Curve // if so, find what bone that belong to and create BoneMOdifier for them UAnimSequence* CurrentSequence = Cast<UAnimSequence>(CurrentAsset); CurveBoneControllers.Empty(); // do not apply if BakedAnimation is on if(CurrentSequence) { // make sure if this needs source update if ( !CurrentSequence->DoesContainTransformCurves() ) { return; } RequiredBones.SetUseSourceData(true); TArray<FTransformCurve>& Curves = CurrentSequence->RawCurveData.TransformCurves; FSmartNameMapping* NameMapping = CurrentSkeleton->SmartNames.GetContainer(USkeleton::AnimTrackCurveMappingName); for (auto& Curve : Curves) { // skip if disabled if (Curve.GetCurveTypeFlag(ACF_Disabled)) { continue; } // add bone modifier FName CurveName; NameMapping->GetName(Curve.CurveUid, CurveName); // @TODO: this is going to be issue. If they don't save skeleton with it, we don't have name at all? if (CurveName == NAME_None) { FSmartNameMapping::UID NewUID; NameMapping->AddOrFindName(Curve.LastObservedName, NewUID); Curve.CurveUid = NewUID; CurveName = Curve.LastObservedName; } FName BoneName = CurveName; if (BoneName != NAME_None && CurrentSkeleton->GetReferenceSkeleton().FindBoneIndex(BoneName) != INDEX_NONE) { ModifyBone(BoneName, true); } } } }
void UAnimSet::PostLoad() { Super::PostLoad(); #if WITH_EDITORONLY_DATA // Make sure that AnimSets (and sequences) within level packages are not marked as standalone. if(GetOutermost()->ContainsMap() && HasAnyFlags(RF_Standalone)) { ClearFlags(RF_Standalone); for(int32 i=0; i<Sequences.Num(); i++) { UAnimSequence* Seq = Sequences[i]; if(Seq) { Seq->ClearFlags(RF_Standalone); } } } #endif //#if WITH_EDITORONLY_DATA }
bool UBlendSpaceBase::GetAllAnimationSequencesReferred(TArray<UAnimationAsset*>& AnimationAssets, bool bRecursive /*= true*/) { Super::GetAllAnimationSequencesReferred(AnimationAssets, bRecursive); for (auto Iter = SampleData.CreateConstIterator(); Iter; ++Iter) { // saves all samples in the AnimSequences UAnimSequence* Sequence = (*Iter).Animation; if (Sequence) { Sequence->HandleAnimReferenceCollection(AnimationAssets, bRecursive); } } if (PreviewBasePose) { PreviewBasePose->HandleAnimReferenceCollection(AnimationAssets, bRecursive); } return (AnimationAssets.Num() > 0); }
SIZE_T UAnimSet::GetResourceSize(EResourceSizeMode::Type Mode) { if (Mode == EResourceSizeMode::Exclusive) { // This object only references others, it doesn't have any real resource bytes return 0; } else { int32 ResourceSize = 0; #if WITH_EDITORONLY_DATA for( int32 i=0; i<Sequences.Num(); i++ ) { UAnimSequence* AnimSeq = Sequences[i]; if( AnimSeq ) { ResourceSize += AnimSeq->GetResourceSize(Mode); } } #endif //#if WITH_EDITORONLY_DATA return ResourceSize; } }
bool FAnimationTrackEditor::HandleAssetAdded(UObject* Asset, const FGuid& TargetObjectGuid) { if (Asset->IsA<UAnimSequence>()) { UAnimSequence* AnimSequence = Cast<UAnimSequence>(Asset); if (TargetObjectGuid.IsValid()) { USkeleton* Skeleton = AcquireSkeletonFromObjectGuid(TargetObjectGuid); if (Skeleton && Skeleton == AnimSequence->GetSkeleton()) { TArray<UObject*> OutObjects; GetSequencer()->GetRuntimeObjects(GetSequencer()->GetFocusedMovieSceneInstance(), TargetObjectGuid, OutObjects); AnimatablePropertyChanged(UMovieSceneAnimationTrack::StaticClass(), false, FOnKeyProperty::CreateRaw(this, &FAnimationTrackEditor::AddKeyInternal, OutObjects, AnimSequence)); return true; } } } return false; }
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; }
/** * Add to the animation set, the animations contained within the FBX document, for the given skeleton */ UAnimSequence * UnFbx::FFbxImporter::ImportAnimations(USkeleton* Skeleton, UObject* Outer, TArray<FbxNode*>& SortedLinks, const FString& Name, UFbxAnimSequenceImportData* TemplateImportData, TArray<FbxNode*>& NodeArray) { // we need skeleton to create animsequence if (Skeleton == NULL) { return NULL; } int32 ValidTakeCount = 0; if (IsValidAnimationData(SortedLinks, NodeArray, ValidTakeCount) == false) { AddTokenizedErrorMessage(FTokenizedMessage::Create(EMessageSeverity::Warning, LOCTEXT("FBXImport_InvalidAnimationData", "This does not contain any valid animation takes.")), FFbxErrors::Animation_InvalidData); return NULL; } UAnimSequence* LastCreatedAnim = NULL; int32 ResampleRate = DEFAULT_SAMPLERATE; if ( ImportOptions->bResample ) { // For FBX data, "Frame Rate" is just the speed at which the animation is played back. It can change // arbitrarily, and the underlying data can stay the same. What we really want here is the Sampling Rate, // ie: the number of animation keys per second. These are the individual animation curve keys // on the FBX nodes of the skeleton. So we loop through the nodes of the skeleton and find the maximum number // of keys that any node has, then divide this by the total length (in seconds) of the animation to find the // sampling rate of this set of data // we want the maximum resample rate, so that we don't lose any precision of fast anims, // and don't mind creating lerped frames for slow anims int32 MaxStackResampleRate = GetMaxSampleRate(SortedLinks, NodeArray); if(MaxStackResampleRate != 0) { ResampleRate = MaxStackResampleRate; } } int32 AnimStackCount = Scene->GetSrcObjectCount<FbxAnimStack>(); for( int32 AnimStackIndex = 0; AnimStackIndex < AnimStackCount; AnimStackIndex++ ) { FbxAnimStack* CurAnimStack = Scene->GetSrcObject<FbxAnimStack>(AnimStackIndex); FbxTimeSpan AnimTimeSpan = GetAnimationTimeSpan(SortedLinks[0], CurAnimStack); bool bValidAnimStack = ValidateAnimStack(SortedLinks, NodeArray, CurAnimStack, ResampleRate, ImportOptions->bImportMorph, AnimTimeSpan); // no animation if (!bValidAnimStack) { continue; } FString SequenceName = Name; if (ValidTakeCount > 1) { SequenceName += "_"; SequenceName += UTF8_TO_TCHAR(CurAnimStack->GetName()); } // See if this sequence already exists. SequenceName = ObjectTools::SanitizeObjectName(SequenceName); FString ParentPath = FString::Printf(TEXT("%s/%s"), *FPackageName::GetLongPackagePath(*Outer->GetName()), *SequenceName); UObject* ParentPackage = CreatePackage(NULL, *ParentPath); UObject* Object = LoadObject<UObject>(ParentPackage, *SequenceName, NULL, LOAD_None, NULL); UAnimSequence * DestSeq = Cast<UAnimSequence>(Object); // if object with same name exists, warn user if (Object && !DestSeq) { AddTokenizedErrorMessage(FTokenizedMessage::Create(EMessageSeverity::Error, LOCTEXT("Error_AssetExist", "Asset with same name exists. Can't overwrite another asset")), FFbxErrors::Generic_SameNameAssetExists); continue; // Move on to next sequence... } // If not, create new one now. if(!DestSeq) { DestSeq = NewObject<UAnimSequence>(ParentPackage, *SequenceName, RF_Public | RF_Standalone); // Notify the asset registry FAssetRegistryModule::AssetCreated(DestSeq); } else { DestSeq->RecycleAnimSequence(); } DestSeq->SetSkeleton(Skeleton); // since to know full path, reimport will need to do same UFbxAnimSequenceImportData* ImportData = UFbxAnimSequenceImportData::GetImportDataForAnimSequence(DestSeq, TemplateImportData); ImportData->Update(UFactory::CurrentFilename); ImportAnimation(Skeleton, DestSeq, Name, SortedLinks, NodeArray, CurAnimStack, ResampleRate, AnimTimeSpan); LastCreatedAnim = DestSeq; } return LastCreatedAnim; }
void UAnimMontage::PostLoad() { Super::PostLoad(); // copy deprecated variable to new one, temporary code to keep data copied. Am deleting it right after this for ( auto SlotIter = SlotAnimTracks.CreateIterator() ; SlotIter ; ++SlotIter) { FAnimTrack & Track = (*SlotIter).AnimTrack; for ( auto SegIter = Track.AnimSegments.CreateIterator() ; SegIter ; ++SegIter ) { FAnimSegment & Segment = (*SegIter); if ( Segment.AnimStartOffset_DEPRECATED!=0.f ) { Segment.AnimStartTime = Segment.AnimStartOffset_DEPRECATED; Segment.AnimStartOffset_DEPRECATED = 0.f; } if ( Segment.AnimEndOffset_DEPRECATED!=0.f ) { Segment.AnimEndTime = Segment.AnimEndOffset_DEPRECATED; Segment.AnimEndOffset_DEPRECATED = 0.f; } } } for ( auto CompositeIter = CompositeSections.CreateIterator(); CompositeIter; ++CompositeIter ) { FCompositeSection & Composite = (*CompositeIter); if (Composite.StarTime_DEPRECATED!=0.f) { Composite.StartTime = Composite.StarTime_DEPRECATED; Composite.StarTime_DEPRECATED = 0.f; } } SortAnimBranchingPointByTime(); // find preview base pose if it can #if WITH_EDITORONLY_DATA if ( IsValidAdditive() && PreviewBasePose == NULL ) { for (int32 I=0; I<SlotAnimTracks.Num(); ++I) { if ( SlotAnimTracks[I].AnimTrack.AnimSegments.Num() > 0 ) { UAnimSequence * Sequence = Cast<UAnimSequence>(SlotAnimTracks[I].AnimTrack.AnimSegments[0].AnimReference); if ( Sequence && Sequence->RefPoseSeq ) { PreviewBasePose = Sequence->RefPoseSeq; MarkPackageDirty(); break; } } } } // verify if skeleton matches, otherwise clear it, this can happen if anim sequence has been modified when this hasn't been loaded. USkeleton* MySkeleton = GetSkeleton(); for (int32 I=0; I<SlotAnimTracks.Num(); ++I) { if ( SlotAnimTracks[I].AnimTrack.AnimSegments.Num() > 0 ) { UAnimSequence * Sequence = Cast<UAnimSequence>(SlotAnimTracks[I].AnimTrack.AnimSegments[0].AnimReference); if ( Sequence && Sequence->GetSkeleton() != MySkeleton ) { SlotAnimTracks[I].AnimTrack.AnimSegments[0].AnimReference = 0; MarkPackageDirty(); break; } } } #endif // WITH_EDITORONLY_DATA }
void FAnimSegment::GetAnimNotifiesFromTrackPositions(const float& PreviousTrackPosition, const float& CurrentTrackPosition, TArray<const FAnimNotifyEvent *> & OutActiveNotifies) const { if( PreviousTrackPosition == CurrentTrackPosition ) { return; } const bool bTrackPlayingBackwards = (PreviousTrackPosition > CurrentTrackPosition); const float SegmentStartPos = StartPos; const float SegmentEndPos = StartPos + GetLength(); // if track range overlaps segment if( bTrackPlayingBackwards ? ((CurrentTrackPosition < SegmentEndPos) && (PreviousTrackPosition > SegmentStartPos)) : ((PreviousTrackPosition < SegmentEndPos) && (CurrentTrackPosition > SegmentStartPos)) ) { // Only allow AnimSequences for now. Other types will need additional support. UAnimSequence * AnimSequence = Cast<UAnimSequence>(AnimReference); if( AnimSequence ) { const float ValidPlayRate = GetValidPlayRate(); const float AbsValidPlayRate = FMath::Abs(ValidPlayRate); // Get starting position, closest overlap. float AnimStartPosition = ConvertTrackPosToAnimPos( bTrackPlayingBackwards ? FMath::Min(PreviousTrackPosition, SegmentEndPos) : FMath::Max(PreviousTrackPosition, SegmentStartPos) ); AnimStartPosition = FMath::Clamp(AnimStartPosition, AnimStartTime, AnimEndTime); float TrackTimeToGo = FMath::Abs(CurrentTrackPosition - PreviousTrackPosition); // The track can be playing backwards and the animation can be playing backwards, so we // need to combine to work out what direction we are traveling through the animation bool bAnimPlayingBackwards = bTrackPlayingBackwards ^ (ValidPlayRate < 0.f); // Abstract out end point since animation can be playing forward or backward. const float AnimEndPoint = bAnimPlayingBackwards ? AnimStartTime : AnimEndTime; for(int32 IterationsLeft=FMath::Max(LoopingCount, 1); ((IterationsLeft > 0) && (TrackTimeToGo > 0.f)); --IterationsLeft) { // Track time left to reach end point of animation. const float TrackTimeToAnimEndPoint = (AnimEndPoint - AnimStartPosition) / AbsValidPlayRate; // If our time left is shorter than time to end point, no problem. End there. if( FMath::Abs(TrackTimeToGo) < FMath::Abs(TrackTimeToAnimEndPoint) ) { const float AnimEndPosition = ConvertTrackPosToAnimPos(CurrentTrackPosition); // Make sure we have not wrapped around, positions should be contiguous. check(bAnimPlayingBackwards ? (AnimEndPosition <= AnimStartPosition) : (AnimStartPosition <= AnimEndPosition)); AnimSequence->GetAnimNotifiesFromDeltaPositions(AnimStartPosition, AnimEndPosition, OutActiveNotifies); break; } // Otherwise we hit the end point of the animation first... else { // Add that piece for extraction. // Make sure we have not wrapped around, positions should be contiguous. check(bAnimPlayingBackwards ? (AnimEndPoint <= AnimStartPosition) : (AnimStartPosition <= AnimEndPoint)); AnimSequence->GetAnimNotifiesFromDeltaPositions(AnimStartPosition, AnimEndPoint, OutActiveNotifies); // decrease our TrackTimeToGo if we have to do another iteration. // and put ourselves back at the beginning of the animation. TrackTimeToGo -= TrackTimeToAnimEndPoint; AnimStartPosition = bAnimPlayingBackwards ? AnimEndTime : AnimStartTime; } } } } }
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 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(); }