bool UnFbx::FFbxImporter::ImportAnimation(USkeleton* Skeleton, UAnimSequence * DestSeq, const FString& FileName, TArray<FbxNode*>& SortedLinks, TArray<FbxNode*>& NodeArray, FbxAnimStack* CurAnimStack, const int32 ResampleRate, const FbxTimeSpan AnimTimeSpan)
{
	// @todo : the length might need to change w.r.t. sampling keys
	FbxTime SequenceLength = AnimTimeSpan.GetDuration();
	float PreviousSequenceLength = DestSeq->SequenceLength;

	// 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
	DestSeq->SequenceLength = FGenericPlatformMath::Max<float>(SequenceLength.GetSecondDouble(), MINIMUM_ANIMATION_LENGTH);

	if(PreviousSequenceLength > MINIMUM_ANIMATION_LENGTH && DestSeq->RawCurveData.FloatCurves.Num() > 0)
	{
		// The sequence already existed when we began the import. We need to scale the key times for all curves to match the new 
		// duration before importing over them. This is to catch any user-added curves
		float ScaleFactor = DestSeq->SequenceLength / PreviousSequenceLength;
		for(FFloatCurve& Curve : DestSeq->RawCurveData.FloatCurves)
		{
			Curve.FloatCurve.ScaleCurve(0.0f, ScaleFactor);
		}
	}

	if (ImportOptions->bDeleteExistingMorphTargetCurves)
	{
		for (int32 CurveIdx=0; CurveIdx<DestSeq->RawCurveData.FloatCurves.Num(); ++CurveIdx)
		{
			auto& Curve = DestSeq->RawCurveData.FloatCurves[CurveIdx];
			if (Curve.GetCurveTypeFlag(ACF_DrivesMorphTarget))
			{
				DestSeq->RawCurveData.FloatCurves.RemoveAt(CurveIdx, 1, false);
				--CurveIdx;
			}
		}

		DestSeq->RawCurveData.FloatCurves.Shrink();
	}

	//
	// import blend shape curves
	//
	{
		GWarn->BeginSlowTask( LOCTEXT("BeginImportMorphTargetCurves", "Importing Morph Target Curves"), true);
		for ( int32 NodeIndex = 0; NodeIndex < NodeArray.Num(); NodeIndex++ )
		{
			// consider blendshape animation curve
			FbxGeometry* Geometry = (FbxGeometry*)NodeArray[NodeIndex]->GetNodeAttribute();
			if (Geometry)
			{
				int32 BlendShapeDeformerCount = Geometry->GetDeformerCount(FbxDeformer::eBlendShape);
				for(int32 BlendShapeIndex = 0; BlendShapeIndex<BlendShapeDeformerCount; ++BlendShapeIndex)
				{
					FbxBlendShape* BlendShape = (FbxBlendShape*)Geometry->GetDeformer(BlendShapeIndex, FbxDeformer::eBlendShape);

					const int32 BlendShapeChannelCount = BlendShape->GetBlendShapeChannelCount();

					FString BlendShapeName = UTF8_TO_TCHAR(MakeName(BlendShape->GetName()));

					for(int32 ChannelIndex = 0; ChannelIndex<BlendShapeChannelCount; ++ChannelIndex)
					{
						FbxBlendShapeChannel* Channel = BlendShape->GetBlendShapeChannel(ChannelIndex);

						if(Channel)
						{
							FString ChannelName = UTF8_TO_TCHAR(MakeName(Channel->GetName()));

							// Maya adds the name of the blendshape and an underscore to the front of the channel name, so remove it
							if(ChannelName.StartsWith(BlendShapeName))
							{
								ChannelName = ChannelName.Right(ChannelName.Len() - (BlendShapeName.Len()+1));
							}

							FbxAnimCurve* Curve = Geometry->GetShapeChannel(BlendShapeIndex, ChannelIndex, (FbxAnimLayer*)CurAnimStack->GetMember(0));
							if (Curve && Curve->KeyGetCount() > 0)
							{
								FFormatNamedArguments Args;
								Args.Add(TEXT("BlendShape"), FText::FromString(ChannelName));
								const FText StatusUpate = FText::Format(LOCTEXT("ImportingMorphTargetCurvesDetail", "Importing Morph Target Curves [{BlendShape}]"), Args);
								GWarn->StatusUpdate(NodeIndex + 1, NodeArray.Num(), StatusUpate);
								// now see if we have one already exists. If so, just overwrite that. if not, add new one. 
								ImportCurveToAnimSequence(DestSeq, *ChannelName, Curve,  ACF_DrivesMorphTarget | ACF_TriggerEvent, AnimTimeSpan, 0.01f /** for some reason blend shape values are coming as 100 scaled **/);
							}
						}
					}
				}
			}
		}
		GWarn->EndSlowTask();
	}

	// 
	// importing custom attribute START
	//
	if (ImportOptions->bImportCustomAttribute)
	{
		GWarn->BeginSlowTask( LOCTEXT("BeginImportMorphTargetCurves", "Importing Custom Attirubte Curves"), true);
		const int32 TotalLinks = SortedLinks.Num();
		int32 CurLinkIndex=0;
		for(auto Node: SortedLinks)
		{
			FbxProperty Property = Node->GetFirstProperty();
			while (Property.IsValid())
			{
				FbxAnimCurveNode* CurveNode = Property.GetCurveNode();
				// do this if user defined and animated and leaf node
				if( CurveNode && Property.GetFlag(FbxPropertyAttr::eUserDefined) && 
					CurveNode->IsAnimated() && IsSupportedCurveDataType(Property.GetPropertyDataType().GetType()) )
				{
					FString CurveName = UTF8_TO_TCHAR(CurveNode->GetName());
					UE_LOG(LogFbx, Log, TEXT("CurveName : %s"), *CurveName );

					int32 TotalCount = CurveNode->GetChannelsCount();
					for (int32 ChannelIndex=0; ChannelIndex<TotalCount; ++ChannelIndex)
					{
						FbxAnimCurve * AnimCurve = CurveNode->GetCurve(ChannelIndex);
						FString ChannelName = CurveNode->GetChannelName(ChannelIndex).Buffer();

						if (AnimCurve)
						{
							FString FinalCurveName;
							if (TotalCount == 1)
							{
								FinalCurveName = CurveName;
							}
							else
							{
								FinalCurveName = CurveName + "_" + ChannelName;
							}

							FFormatNamedArguments Args;
							Args.Add(TEXT("CurveName"), FText::FromString(FinalCurveName));
							const FText StatusUpate = FText::Format(LOCTEXT("ImportingCustomAttributeCurvesDetail", "Importing Custom Attribute [{CurveName}]"), Args);
							GWarn->StatusUpdate(CurLinkIndex + 1, TotalLinks, StatusUpate);

							ImportCurveToAnimSequence(DestSeq, FinalCurveName, AnimCurve,  ACF_DefaultCurve, AnimTimeSpan);
						}
											
					}
				}

				Property = Node->GetNextProperty(Property); 
			}

			CurLinkIndex++;
		}

		GWarn->EndSlowTask();
	}

	// importing custom attribute END

	const bool bSourceDataExists = (DestSeq->SourceRawAnimationData.Num() > 0);
	TArray<AnimationTransformDebug::FAnimationTransformDebugData> TransformDebugData;
	int32 TotalNumKeys = 0;
	const FReferenceSkeleton& RefSkeleton = Skeleton->GetReferenceSkeleton();

	// import animation
	{
		GWarn->BeginSlowTask( LOCTEXT("BeginImportAnimation", "Importing Animation"), true);

		TArray<struct FRawAnimSequenceTrack>& RawAnimationData = bSourceDataExists? DestSeq->SourceRawAnimationData : DestSeq->RawAnimationData;
		DestSeq->TrackToSkeletonMapTable.Empty();
		DestSeq->AnimationTrackNames.Empty();
		RawAnimationData.Empty();

		TArray<FName> FbxRawBoneNames;
		FillAndVerifyBoneNames(Skeleton, SortedLinks, FbxRawBoneNames, FileName);

		UnFbx::FFbxImporter* FbxImporter = UnFbx::FFbxImporter::GetInstance();

		const bool bPreserveLocalTransform = FbxImporter->GetImportOptions()->bPreserveLocalTransform;

		// Build additional transform matrix
		UFbxAnimSequenceImportData* TemplateData = Cast<UFbxAnimSequenceImportData>(DestSeq->AssetImportData);
		FbxAMatrix FbxAddedMatrix;
		BuildFbxMatrixForImportTransform(FbxAddedMatrix, TemplateData);
		FMatrix AddedMatrix = Converter.ConvertMatrix(FbxAddedMatrix);

		const int32 NumSamplingKeys = FMath::FloorToInt(AnimTimeSpan.GetDuration().GetSecondDouble() * ResampleRate);
		const FbxTime TimeIncrement = (NumSamplingKeys > 1)? AnimTimeSpan.GetDuration() / (NumSamplingKeys - 1) : AnimTimeSpan.GetDuration();
		for(int32 SourceTrackIdx = 0; SourceTrackIdx < FbxRawBoneNames.Num(); ++SourceTrackIdx)
		{
			int32 NumKeysForTrack = 0;

			// see if it's found in Skeleton
			FName BoneName = FbxRawBoneNames[SourceTrackIdx];
			int32 BoneTreeIndex = RefSkeleton.FindBoneIndex(BoneName);

			// update status
			FFormatNamedArguments Args;
			Args.Add(TEXT("TrackName"), FText::FromName(BoneName));
			Args.Add(TEXT("TotalKey"), FText::AsNumber(NumSamplingKeys));
			Args.Add(TEXT("TrackIndex"), FText::AsNumber(SourceTrackIdx+1));
			Args.Add(TEXT("TotalTracks"), FText::AsNumber(FbxRawBoneNames.Num()));
			const FText StatusUpate = FText::Format(LOCTEXT("ImportingAnimTrackDetail", "Importing Animation Track [{TrackName}] ({TrackIndex}/{TotalTracks}) - TotalKey {TotalKey}"), Args);
			GWarn->StatusForceUpdate(SourceTrackIdx + 1, FbxRawBoneNames.Num(), StatusUpate);

			if (BoneTreeIndex!=INDEX_NONE)
			{
				bool bSuccess = true;

				FRawAnimSequenceTrack RawTrack;
				RawTrack.PosKeys.Empty();
				RawTrack.RotKeys.Empty();
				RawTrack.ScaleKeys.Empty();

				AnimationTransformDebug::FAnimationTransformDebugData NewDebugData;

				FbxNode* Link = SortedLinks[SourceTrackIdx];
				FbxNode * LinkParent = Link->GetParent();
			
				for(FbxTime CurTime = AnimTimeSpan.GetStart(); CurTime <= AnimTimeSpan.GetStop(); CurTime += TimeIncrement)
				{
					// save global trasnform
					FbxAMatrix GlobalMatrix = Link->EvaluateGlobalTransform(CurTime);
					// we'd like to verify this before going to Transform. 
					// currently transform has tons of NaN check, so it will crash there
					FMatrix GlobalUEMatrix = Converter.ConvertMatrix(GlobalMatrix);
					if (GlobalUEMatrix.ContainsNaN())
					{
						bSuccess = false;
						AddTokenizedErrorMessage(FTokenizedMessage::Create(EMessageSeverity::Error, FText::Format(LOCTEXT("Error_InvalidTransform",
							"Track {0} contains invalid transform. Could not import the track."), FText::FromName(BoneName))), FFbxErrors::Animation_TransformError);
						break;
					}

					FTransform GlobalTransform =  Converter.ConvertTransform(GlobalMatrix);
					if (GlobalTransform.ContainsNaN())
					{
						bSuccess = false;
						AddTokenizedErrorMessage(FTokenizedMessage::Create(EMessageSeverity::Error, FText::Format(LOCTEXT("Error_InvalidUnrealTransform",
											"Track {0} did not yeild valid transform. Please report this to animation team."), FText::FromName(BoneName))), FFbxErrors::Animation_TransformError);
						break;
					}

					// debug data, including import transformation
					FTransform AddedTransform(AddedMatrix);
					NewDebugData.SourceGlobalTransform.Add(GlobalTransform * AddedTransform);

					FTransform LocalTransform;
					if( !bPreserveLocalTransform && LinkParent)
					{
						// I can't rely on LocalMatrix. I need to recalculate quaternion/scale based on global transform if Parent exists
						FbxAMatrix ParentGlobalMatrix = Link->GetParent()->EvaluateGlobalTransform(CurTime);
						FTransform ParentGlobalTransform =  Converter.ConvertTransform(ParentGlobalMatrix);

						LocalTransform = GlobalTransform.GetRelativeTransform(ParentGlobalTransform);
						NewDebugData.SourceParentGlobalTransform.Add(ParentGlobalTransform);
					} 
					else
					{
						FbxAMatrix& LocalMatrix = Link->EvaluateLocalTransform(CurTime); 
						FbxVector4 NewLocalT = LocalMatrix.GetT();
						FbxVector4 NewLocalS = LocalMatrix.GetS();
						FbxQuaternion NewLocalQ = LocalMatrix.GetQ();

						LocalTransform.SetTranslation(Converter.ConvertPos(NewLocalT));
						LocalTransform.SetScale3D(Converter.ConvertScale(NewLocalS));
						LocalTransform.SetRotation(Converter.ConvertRotToQuat(NewLocalQ));

						NewDebugData.SourceParentGlobalTransform.Add(FTransform::Identity);
					}

					if(TemplateData && BoneTreeIndex == 0)
					{
						// If we found template data earlier, apply the import transform matrix to
						// the root track.
						LocalTransform.SetFromMatrix(LocalTransform.ToMatrixWithScale() * AddedMatrix);
					}

					if (LocalTransform.ContainsNaN())
					{
						bSuccess = false;
						AddTokenizedErrorMessage(FTokenizedMessage::Create(EMessageSeverity::Error, FText::Format(LOCTEXT("Error_InvalidUnrealLocalTransform",
											"Track {0} did not yeild valid local transform. Please report this to animation team."), FText::FromName(BoneName))), FFbxErrors::Animation_TransformError);
						break;
					}

					RawTrack.ScaleKeys.Add(LocalTransform.GetScale3D());
					RawTrack.PosKeys.Add(LocalTransform.GetTranslation());
					RawTrack.RotKeys.Add(LocalTransform.GetRotation());

					NewDebugData.RecalculatedLocalTransform.Add(LocalTransform);
					++NumKeysForTrack;
				}

				if (bSuccess)
				{
					//add new track
					int32 NewTrackIdx = RawAnimationData.Add(RawTrack);
					DestSeq->AnimationTrackNames.Add(BoneName);

					NewDebugData.SetTrackData(NewTrackIdx, BoneTreeIndex, BoneName);

					// add mapping to skeleton bone track
					DestSeq->TrackToSkeletonMapTable.Add(FTrackToSkeletonMap(BoneTreeIndex));
					TransformDebugData.Add(NewDebugData);
				}
			}

			TotalNumKeys = FMath::Max( TotalNumKeys, NumKeysForTrack );
		}

		DestSeq->NumFrames = TotalNumKeys;
		GWarn->EndSlowTask();
	}

	// 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)
		{
			DestSeq->BakeTrackCurvesToRawAnimation();
		}
		else
		{
			// otherwise just compress
			DestSeq->PostProcessSequence();
		}

		// run debug mode
		AnimationTransformDebug::OutputAnimationTransformDebugData(TransformDebugData, TotalNumKeys, RefSkeleton);
		GWarn->EndSlowTask();
	}

	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;
}