void FMeshPaintSpriteAdapter::QueryPaintableTextures(int32 MaterialIndex, int32& OutDefaultIndex, TArray<struct FPaintableTexture>& InOutTextureList)
{
	UPaperSprite* Sprite = SpriteComponent->GetSprite();
	checkSlow(Sprite != nullptr);

	// Grab the sprite texture first
	int32 ForceIndex = INDEX_NONE;
	if (UTexture2D* SourceTexture = Sprite->GetBakedTexture())
	{
		FPaintableTexture PaintableTexture(SourceTexture, 0);
		ForceIndex = InOutTextureList.AddUnique(PaintableTexture);
	}

	// Grab the additional textures next
	FAdditionalSpriteTextureArray AdditionalTextureList;
	Sprite->GetBakedAdditionalSourceTextures(/*out*/ AdditionalTextureList);
	for (UTexture* AdditionalTexture : AdditionalTextureList)
	{
		if (AdditionalTexture != nullptr)
		{
			FPaintableTexture PaintableTexture(AdditionalTexture, 0);
			InOutTextureList.AddUnique(AdditionalTexture);
		}
	}

	// Now ask the material
	DefaultQueryPaintableTextures(MaterialIndex, SpriteComponent, OutDefaultIndex, InOutTextureList);

	if (ForceIndex != INDEX_NONE)
	{
		OutDefaultIndex = ForceIndex;
	}
}
void FSpriteEditorViewportClient::EndTransaction()
{
	if (bManipulationDirtiedSomething)
	{
		UPaperSprite* Sprite = GetSpriteBeingEdited();

		if (IsInSourceRegionEditMode())
		{
			// Snap to pixel grid at the end of the drag
			Sprite->SourceUV.X = FMath::Max(FMath::RoundToFloat(Sprite->SourceUV.X), 0.0f);
			Sprite->SourceUV.Y = FMath::Max(FMath::RoundToFloat(Sprite->SourceUV.Y), 0.0f);
			Sprite->SourceDimension.X = FMath::Max(FMath::RoundToFloat(Sprite->SourceDimension.X), 0.0f);
			Sprite->SourceDimension.Y = FMath::Max(FMath::RoundToFloat(Sprite->SourceDimension.Y), 0.0f);
		}

		Sprite->PostEditChange();
	}
	
	bManipulationDirtiedSomething = false;

	if (ScopedTransaction != nullptr)
	{
		delete ScopedTransaction;
		ScopedTransaction = nullptr;
	}
}
UObject* UPaperSpriteFactory::FactoryCreateNew(UClass* Class, UObject* InParent, FName Name, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn)
{
	UPaperSprite* NewSprite = ConstructObject<UPaperSprite>(Class, InParent, Name, Flags);

	NewSprite->InitializeSprite(InitialTexture);

	return NewSprite;
}
void FSpriteEditorViewportClient::BeginTransaction(const FText& SessionName)
{
	if (ScopedTransaction == nullptr)
	{
		ScopedTransaction = new FScopedTransaction(SessionName);

		UPaperSprite* Sprite = GetSpriteBeingEdited();
		Sprite->Modify();
	}
}
		// Sort predicate operator
		bool operator()(UPaperSprite& LHS, UPaperSprite& RHS) const
		{
			FString LeftString;
			int32 LeftNumber;
			ExtractSpriteNumber(LHS.GetName(), /*out*/ LeftString, /*out*/ LeftNumber);

			FString RightString;
			int32 RightNumber;
			ExtractSpriteNumber(RHS.GetName(), /*out*/ RightString, /*out*/ RightNumber);

			return (LeftString == RightString) ? (LeftNumber < RightNumber) : (LeftString < RightString);
		}
void FSpriteEditorViewportClient::NotifySpriteBeingEditedHasChanged()
{
	//@TODO: Ideally we do this before switching
	EndTransaction();

	// Refresh the viewport in case we were not in realtime mode
	Invalidate();

	// Update components to know about the new sprite being edited
	UPaperSprite* Sprite = GetSpriteBeingEdited();

	RenderSpriteComponent->SetSprite(Sprite);
	UpdateSourceTextureSpriteFromSprite(Sprite);

	InternalActivateNewMode(CurrentMode);

	//@TODO: Only do this if the sprite isn't visible (may consider doing a flashing pulse around the source region rect?)
	RequestFocusOnSelection(/*bInstant=*/ true);

	if (Sprite != nullptr)
	{
		// Create and display a notification about the new sprite being edited
		const FText NotificationErrorText = FText::Format(LOCTEXT("SwitchingToSprite", "Editing {0}"), FText::AsCultureInvariant(Sprite->GetName()));
		FNotificationInfo Info(NotificationErrorText);
		Info.ExpireDuration = 2.0f;
		FSlateNotificationManager::Get().AddNotification(Info);
	}
}
UPaperSprite* UHGGifFactory::CreateNewSprite(UTexture2D* InitialTexture, UClass* Class, UObject* InParent, FName Name, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn)
{
	UPaperSprite* NewSprite = NewObject<UPaperSprite>(InParent, Class, Name, Flags | RF_Transactional);
	if (NewSprite && InitialTexture)
	{
		FSpriteAssetInitParameters SpriteInitParams;
		SpriteInitParams.SetTextureAndFill(InitialTexture);

		const UPaperImporterSettings* ImporterSettings = GetDefault<UPaperImporterSettings>();

		ImporterSettings->ApplySettingsForSpriteInit(SpriteInitParams, ESpriteInitMaterialLightingMode::Automatic);
		NewSprite->InitializeSprite(SpriteInitParams);
	}

	return NewSprite;
}
// Find all related sprites (not including self)
void FSpriteEditorViewportClient::UpdateRelatedSpritesList()
{
	UPaperSprite* Sprite = GetSpriteBeingEdited();
	UTexture2D* Texture = Sprite->GetSourceTexture();
	if (Texture != nullptr)
	{
		FARFilter Filter;
		Filter.ClassNames.Add(UPaperSprite::StaticClass()->GetFName());
		const FString TextureString = FAssetData(Texture).GetExportTextName();
		const FName SourceTexturePropName(TEXT("SourceTexture"));
		Filter.TagsAndValues.Add(SourceTexturePropName, TextureString);
		FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked<FAssetRegistryModule>("AssetRegistry");
		TArray<FAssetData> SpriteAssetData;
		AssetRegistryModule.Get().GetAssets(Filter, SpriteAssetData);

		FAssetData CurrentAssetData(Sprite);

		RelatedSprites.Empty();
		for (int32 i = 0; i < SpriteAssetData.Num(); ++i)
		{
			FAssetData& SpriteAsset = SpriteAssetData[i];
			if (SpriteAsset == Sprite)
			{
				continue;
			}

			const FString* SourceUVString = SpriteAsset.TagsAndValues.Find("SourceUV");
			const FString* SourceDimensionString = SpriteAsset.TagsAndValues.Find("SourceDimension");
			FVector2D SourceUV, SourceDimension;
			if (SourceUVString != nullptr && SourceDimensionString != nullptr)
			{
				if (SourceUV.InitFromString(*SourceUVString) && SourceDimension.InitFromString(*SourceDimensionString))
				{
					FRelatedSprite RelatedSprite;
					RelatedSprite.AssetData = SpriteAsset;
					RelatedSprite.SourceUV = SourceUV;
					RelatedSprite.SourceDimension = SourceDimension;
					
					RelatedSprites.Add(RelatedSprite);
				}
			}
		}
	}
}
UObject* UPaperSpriteFactory::FactoryCreateNew(UClass* Class, UObject* InParent, FName Name, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn)
{
	UPaperSprite* NewSprite = NewObject<UPaperSprite>(InParent, Class, Name, Flags | RF_Transactional);

	FSpriteAssetInitParameters SpriteInitParams;
	SpriteInitParams.bNewlyCreated = true;
	if (bUseSourceRegion)
	{
		SpriteInitParams.Texture = InitialTexture;
		SpriteInitParams.Offset = InitialSourceUV;
		SpriteInitParams.Dimension = InitialSourceDimension;
	}
	else
	{
		SpriteInitParams.SetTextureAndFill(InitialTexture);
	}
	NewSprite->InitializeSprite(SpriteInitParams);

	return NewSprite;
}
void FSpriteEditorViewportClient::DrawBoundsAsText(FViewport& InViewport, FSceneView& View, FCanvas& Canvas, int32& YPos)
{
	FNumberFormattingOptions NoDigitGroupingFormat;
	NoDigitGroupingFormat.UseGrouping = false;

	UPaperSprite* Sprite = GetSpriteBeingEdited();
	FBoxSphereBounds Bounds = Sprite->GetRenderBounds();

	const FText DisplaySizeText = FText::Format(LOCTEXT("BoundsSize", "Approx. Size: {0}x{1}x{2}"),
		FText::AsNumber((int32)(Bounds.BoxExtent.X * 2.0f), &NoDigitGroupingFormat),
		FText::AsNumber((int32)(Bounds.BoxExtent.Y * 2.0f), &NoDigitGroupingFormat),
		FText::AsNumber((int32)(Bounds.BoxExtent.Z * 2.0f), &NoDigitGroupingFormat));

	Canvas.DrawShadowedString(
		6,
		YPos,
		*DisplaySizeText.ToString(),
		GEngine->GetSmallFont(),
		FLinearColor::White);
	YPos += 18;
}
// Create a new sprite and return this sprite. The sprite editor will now be editing this new sprite
// Returns nullptr if failed
UPaperSprite* FSpriteEditorViewportClient::CreateNewSprite(const FIntPoint& TopLeft, const FIntPoint& Dimensions)
{
	FAssetToolsModule& AssetToolsModule = FModuleManager::Get().LoadModuleChecked<FAssetToolsModule>("AssetTools");
	FContentBrowserModule& ContentBrowserModule = FModuleManager::LoadModuleChecked<FContentBrowserModule>("ContentBrowser");

	UPaperSprite* CurrentSprite = GetSpriteBeingEdited();
	UPaperSprite* CreatedSprite = nullptr;

	// Create the factory used to generate the sprite
	UPaperSpriteFactory* SpriteFactory = NewObject<UPaperSpriteFactory>();
	SpriteFactory->InitialTexture = CurrentSprite->SourceTexture;
	SpriteFactory->bUseSourceRegion = true;
	SpriteFactory->InitialSourceUV = TopLeft;
	SpriteFactory->InitialSourceDimension = Dimensions;


	// Get a unique name for the sprite
	FString Name, PackageName;
	AssetToolsModule.Get().CreateUniqueAssetName(CurrentSprite->GetOutermost()->GetName(), TEXT(""), /*out*/ PackageName, /*out*/ Name);
	const FString PackagePath = FPackageName::GetLongPackagePath(PackageName);
	if (UObject* NewAsset = AssetToolsModule.Get().CreateAsset(Name, PackagePath, UPaperSprite::StaticClass(), SpriteFactory))
	{
		TArray<UObject*> Objects;
		Objects.Add(NewAsset);
		ContentBrowserModule.Get().SyncBrowserToAssets(Objects);

		UPaperSprite* NewSprite = Cast<UPaperSprite>(NewAsset);
		if (SpriteEditorPtr.IsValid() && (NewSprite != nullptr))
		{
			SpriteEditorPtr.Pin()->SetSpriteBeingEdited(NewSprite);
		}

		CreatedSprite = NewSprite;
	}

	return CreatedSprite;
}
void FSpriteEditorViewportClient::UpdateSourceTextureSpriteFromSprite(UPaperSprite* SourceSprite)
{
	UPaperSprite* TargetSprite = SourceTextureViewComponent->GetSprite();
	check(TargetSprite);

	if (SourceSprite != nullptr)
	{
		if ((SourceSprite->GetSourceTexture() != TargetSprite->GetSourceTexture()) || (TargetSprite->PixelsPerUnrealUnit != SourceSprite->PixelsPerUnrealUnit))
		{
			FComponentReregisterContext ReregisterSprite(SourceTextureViewComponent);

			FSpriteAssetInitParameters SpriteReinitParams;

			SpriteReinitParams.SetTextureAndFill(SourceSprite->SourceTexture);
			SpriteReinitParams.DefaultMaterialOverride = SourceSprite->DefaultMaterial;
			SpriteReinitParams.AlternateMaterialOverride = SourceSprite->AlternateMaterial;
			SpriteReinitParams.SetPixelsPerUnrealUnit(SourceSprite->PixelsPerUnrealUnit);
			TargetSprite->InitializeSprite(SpriteReinitParams);

			RequestFocusOnSelection(/*bInstant=*/ true);
		}


		// Position the sprite for the mode its meant to be in
		FVector2D CurrentPivotPosition;
		ESpritePivotMode::Type CurrentPivotMode = TargetSprite->GetPivotMode(/*out*/CurrentPivotPosition);

		FVector Translation(1.0f * PaperAxisZ);
		if (IsInSourceRegionEditMode())
		{
			if (CurrentPivotMode != ESpritePivotMode::Bottom_Left)
			{
				TargetSprite->SetPivotMode(ESpritePivotMode::Bottom_Left, FVector2D::ZeroVector);
				TargetSprite->PostEditChange();
			}
			SourceTextureViewComponent->SetSpriteColor(FLinearColor::White);
			SourceTextureViewComponent->SetWorldTransform(FTransform(Translation));
		}
		else
		{
			const FVector2D PivotPosition = SourceSprite->GetPivotPosition();
			if (CurrentPivotMode != ESpritePivotMode::Custom || CurrentPivotPosition != PivotPosition)
			{
				TargetSprite->SetPivotMode(ESpritePivotMode::Custom, PivotPosition);
				TargetSprite->PostEditChange();
			}

			// Tint the source texture darker to help distinguish the two
			SourceTextureViewComponent->SetSpriteColor(SpriteEditingConstants::SourceTextureDarkTintColor);

			const bool bRotated = SourceSprite->IsRotatedInSourceImage();
			if (bRotated)
			{
				FQuat Rotation(PaperAxisZ, FMath::DegreesToRadians(90.0f));
				SourceTextureViewComponent->SetWorldTransform(FTransform(Rotation, Translation));
			}
			else
			{
				SourceTextureViewComponent->SetWorldTransform(FTransform(Translation));
			}
		}
	}
	else
	{
		// No source sprite, so don't draw the target either
		TargetSprite->SourceTexture = nullptr;
	}
}
FVector FSpriteEditorViewportClient::SourceTextureSpaceToWorldSpace(const FVector2D& SourcePoint) const
{
	UPaperSprite* Sprite = SourceTextureViewComponent->GetSprite();
	return Sprite->ConvertTextureSpaceToWorldSpace(SourcePoint);
}
void FSpriteEditorViewportClient::DrawSourceRegion(FViewport& InViewport, FSceneView& View, FCanvas& Canvas, const FLinearColor& GeometryVertexColor)
{
	const bool bIsHitTesting = Canvas.IsHitTesting();
	UPaperSprite* Sprite = GetSpriteBeingEdited();

    const float CornerCollisionVertexSize = 8.0f;
	const float EdgeCollisionVertexSize = 6.0f;

	const FLinearColor GeometryLineColor(GeometryVertexColor.R, GeometryVertexColor.G, GeometryVertexColor.B, 0.5f * GeometryVertexColor.A);
    
    const bool bDrawEdgeHitProxies = true;
    const bool bDrawCornerHitProxies = true;

	FVector2D BoundsVertices[4];
	BoundsVertices[0] = SourceTextureSpaceToScreenSpace(View, Sprite->SourceUV);
	BoundsVertices[1] = SourceTextureSpaceToScreenSpace(View, Sprite->SourceUV + FVector2D(Sprite->SourceDimension.X, 0));
	BoundsVertices[2] = SourceTextureSpaceToScreenSpace(View, Sprite->SourceUV + FVector2D(Sprite->SourceDimension.X, Sprite->SourceDimension.Y));
	BoundsVertices[3] = SourceTextureSpaceToScreenSpace(View, Sprite->SourceUV + FVector2D(0, Sprite->SourceDimension.Y));

	if (bShowNamesForSprites)
	{
		const FVector2D TextPos = SourceTextureSpaceToScreenSpace(View, Sprite->SourceUV + FVector2D(Sprite->SourceDimension.X*0.5f, Sprite->SourceDimension.Y*0.5f));

		const FText AssetNameText = FText::AsCultureInvariant(Sprite->GetName());
		FCanvasTextItem TextItem(TextPos, AssetNameText, GEngine->GetSmallFont(), FLinearColor::White);
		TextItem.EnableShadow(FLinearColor::Black);
		TextItem.bCentreX = true;
		TextItem.bCentreY = true;

		TextItem.Draw(&Canvas);
	}

	for (int32 VertexIndex = 0; VertexIndex < 4; ++VertexIndex)
	{
		const int32 NextVertexIndex = (VertexIndex + 1) % 4;

		// Draw the edge
		if (bIsHitTesting)
		{
			TSharedPtr<FSpriteSelectedSourceRegion> Data = MakeShareable(new FSpriteSelectedSourceRegion());
			Data->SpritePtr = Sprite;
			Data->VertexIndex = 4 + VertexIndex;
			Canvas.SetHitProxy(new HSpriteSelectableObjectHitProxy(Data));
		}

		FCanvasLineItem LineItem(BoundsVertices[VertexIndex], BoundsVertices[NextVertexIndex]);
		LineItem.SetColor(GeometryVertexColor);
		Canvas.DrawItem(LineItem);

        // Add edge hit proxy
        if (bDrawEdgeHitProxies)
        {
			const FVector2D MidPoint = (BoundsVertices[VertexIndex] + BoundsVertices[NextVertexIndex]) * 0.5f;
			Canvas.DrawTile(MidPoint.X - EdgeCollisionVertexSize*0.5f, MidPoint.Y - EdgeCollisionVertexSize*0.5f, EdgeCollisionVertexSize, EdgeCollisionVertexSize, 0.f, 0.f, 1.f, 1.f, GeometryVertexColor, GWhiteTexture);
        }

		if (bIsHitTesting)
		{
			Canvas.SetHitProxy(nullptr);
		}

        // Add corner hit proxy
        if (bDrawCornerHitProxies)
        {
			const FVector2D CornerPoint = BoundsVertices[VertexIndex];
			
			if (bIsHitTesting)
            {
                TSharedPtr<FSpriteSelectedSourceRegion> Data = MakeShareable(new FSpriteSelectedSourceRegion());
                Data->SpritePtr = Sprite;
                Data->VertexIndex = VertexIndex;
                Canvas.SetHitProxy(new HSpriteSelectableObjectHitProxy(Data));
            }
            
            Canvas.DrawTile(CornerPoint.X - CornerCollisionVertexSize * 0.5f, CornerPoint.Y - CornerCollisionVertexSize * 0.5f, CornerCollisionVertexSize, CornerCollisionVertexSize, 0.f, 0.f, 1.f, 1.f, GeometryVertexColor, GWhiteTexture);

            if (bIsHitTesting)
            {
                Canvas.SetHitProxy(nullptr);
            }
        }
	}
}
float FSpriteEditorViewportClient::SelectedItemGetUnitsPerPixel() const
{
	UPaperSprite* Sprite = GetSpriteBeingEdited();
	return Sprite->GetUnrealUnitsPerPixel();
}
FVector FSpriteEditorViewportClient::TextureSpaceToWorldSpace(const FVector2D& SourcePoint) const
{
	UPaperSprite* Sprite = GetSpriteBeingEdited();
	return Sprite->ConvertTextureSpaceToWorldSpace(SourcePoint);
}
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 FSingleTileEditorViewportClient::SetTileIndex(int32 InTileIndex)
{
	const int32 OldTileIndex = TileBeingEditedIndex;
	const bool bNewIndexValid = (InTileIndex >= 0) && (InTileIndex < TileSet->GetTileCount());
	TileBeingEditedIndex = bNewIndexValid ? InTileIndex : INDEX_NONE;

	FSpriteGeometryEditMode* GeometryEditMode = ModeTools->GetActiveModeTyped<FSpriteGeometryEditMode>(FSpriteGeometryEditMode::EM_SpriteGeometry);
	check(GeometryEditMode);
	FSpriteGeometryCollection* GeomToEdit = nullptr;
	
	if (TileBeingEditedIndex != INDEX_NONE)
	{
		if (FPaperTileMetadata* Metadata = TileSet->GetMutableTileMetadata(InTileIndex))
		{
			GeomToEdit = &(Metadata->CollisionData);
		}
	}

	// Tell the geometry editor about the new tile (if it exists)
	GeometryEditMode->SetGeometryBeingEdited(GeomToEdit, /*bAllowCircles=*/ true, /*bAllowSubtractivePolygons=*/ false);

	// Update the visual representation
	UPaperSprite* DummySprite = nullptr;
	if (TileBeingEditedIndex != INDEX_NONE)
	{
		DummySprite = NewObject<UPaperSprite>();
 		DummySprite->SpriteCollisionDomain = ESpriteCollisionMode::None;
 		DummySprite->PivotMode = ESpritePivotMode::Center_Center;
 		DummySprite->CollisionGeometry.GeometryType = ESpritePolygonMode::SourceBoundingBox;
 		DummySprite->RenderGeometry.GeometryType = ESpritePolygonMode::SourceBoundingBox;

		FSpriteAssetInitParameters SpriteReinitParams;

		SpriteReinitParams.Texture = TileSet->GetTileSheetTexture();

		//@TODO: Should analyze the texture (*at a higher level, not per tile click!*) to pick the correct material
		FVector2D UV;
		TileSet->GetTileUV(TileBeingEditedIndex, /*out*/ UV);
		SpriteReinitParams.Offset = FIntPoint((int32)UV.X, (int32)(UV.Y));
		SpriteReinitParams.Dimension = TileSet->GetTileSize();
		SpriteReinitParams.SetPixelsPerUnrealUnit(1.0f);
		DummySprite->InitializeSprite(SpriteReinitParams);
	}
	PreviewTileSpriteComponent->SetSprite(DummySprite);

	// Update the default geometry bounds
	const FVector2D HalfTileSize(TileSet->GetTileSize().X * 0.5f, TileSet->GetTileSize().Y * 0.5f);
	FBox2D DesiredBounds(ForceInitToZero);
	DesiredBounds.Min = -HalfTileSize;
	DesiredBounds.Max = HalfTileSize;
	GeometryEditMode->SetNewGeometryPreferredBounds(DesiredBounds);

	// Zoom to fit when we start editing a tile and weren't before, but leave it alone if we just changed tiles, in case they zoomed in or out further
	if ((TileBeingEditedIndex != INDEX_NONE) && (OldTileIndex == INDEX_NONE))
	{
		RequestFocusOnSelection(/*bInstant=*/ true);
	}

	// Trigger a details panel customization rebuild
	OnSingleTileIndexChanged.Broadcast(TileBeingEditedIndex, OldTileIndex);

	// Redraw the viewport
	Invalidate();
}
void FPaperAtlasGenerator::HandleAssetChangedEvent(UPaperSpriteAtlas* Atlas)
{
	Atlas->MaxWidth = FMath::Clamp(Atlas->MaxWidth, 16, 4096);
	Atlas->MaxHeight = FMath::Clamp(Atlas->MaxHeight, 16, 4096);

	// Find all sprites that reference the atlas and force them loaded
	FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked<FAssetRegistryModule>(TEXT("AssetRegistry"));

	TArray<FAssetData> AssetList;
	TMultiMap<FName, FString> TagsAndValues;
	TagsAndValues.Add(TEXT("AtlasGroupGUID"), Atlas->AtlasGUID.ToString(EGuidFormats::Digits));
	AssetRegistryModule.Get().GetAssetsByTagValues(TagsAndValues, AssetList);

	TIndirectArray<FSingleTexturePaperAtlas> AtlasGenerators;

	// Start off with one page
	new (AtlasGenerators) FSingleTexturePaperAtlas(Atlas->MaxWidth, Atlas->MaxHeight);

	for (const FAssetData& SpriteRef : AssetList)
	{
		if (UPaperSprite* Sprite = Cast<UPaperSprite>(SpriteRef.GetAsset()))
		{
			//@TODO: Use the tight bounds instead of the source bounds
			const FVector2D SpriteSizeFloat = Sprite->GetSourceSize();
			const FIntPoint SpriteSize(FMath::TruncToInt(SpriteSizeFloat.X), FMath::TruncToInt(SpriteSizeFloat.Y));

			if (Sprite->GetSourceTexture() == nullptr)
			{
				UE_LOG(LogPaper2DEditor, Error, TEXT("Sprite %s has no source texture and cannot be packed"), *(Sprite->GetPathName()));
				continue;
			}

			//@TODO: Take padding into account (push this into the generator)
			if ((SpriteSize.X > Atlas->MaxWidth) || (SpriteSize.Y >= Atlas->MaxHeight))
			{
				// This sprite cannot ever fit into an atlas page
				UE_LOG(LogPaper2DEditor, Error, TEXT("Sprite %s (%d x %d) can never fit into atlas %s (%d x %d) due to maximum page size restrictions"),
					*(Sprite->GetPathName()),
					SpriteSize.X,
					SpriteSize.Y,
					*(Atlas->GetPathName()),
					Atlas->MaxWidth,
					Atlas->MaxHeight);
			}
			else
			{
				const FAtlasedTextureSlot* Slot = nullptr;

				// Does it fit in any existing pages?
				for (auto& Generator : AtlasGenerators)
				{
					Slot = Generator.AddSprite(Sprite);

					if (Slot != nullptr)
					{
						break;
					}
				}

				if (Slot == nullptr)
				{
					// Doesn't fit in any current pages, make a new one
					FSingleTexturePaperAtlas* NewGenerator = new (AtlasGenerators) FSingleTexturePaperAtlas(Atlas->MaxWidth, Atlas->MaxHeight);
					Slot = NewGenerator->AddSprite(Sprite);
				}

				if (Slot != nullptr)
				{
					UE_LOG(LogPaper2DEditor, Warning, TEXT("Allocated %s to (%d,%d)"), *(Sprite->GetPathName()), Slot->X, Slot->Y);
				}
				else
				{
					// ERROR: Didn't fit into a brand new page, maybe padding was wrong
					UE_LOG(LogPaper2DEditor, Error, TEXT("Failed to allocate %s into a brand new page"), *(Sprite->GetPathName()));
				}
			}
		}
	}

	// Turn the generators back into textures
	Atlas->GeneratedTextures.Empty(AtlasGenerators.Num());
	for (int32 GeneratorIndex = 0; GeneratorIndex < AtlasGenerators.Num(); ++GeneratorIndex)
	{
		FSingleTexturePaperAtlas& AtlasPage = AtlasGenerators[GeneratorIndex];

		UTexture2D* Texture = NewNamedObject<UTexture2D>(Atlas, NAME_None, RF_Public);
		Atlas->GeneratedTextures.Add(Texture);

		AtlasPage.CopyToTexture(Texture);

		// Now update the baked data for all the sprites to point there
		for (auto& SpriteToSlot : AtlasPage.SpriteToSlotMap)
		{
			UPaperSprite* Sprite = SpriteToSlot.Key;
			Sprite->Modify();
			const FAtlasedTextureSlot* Slot = SpriteToSlot.Value;
			Sprite->BakedSourceTexture = Texture;
			Sprite->BakedSourceUV = FVector2D(Slot->X, Slot->Y);
			Sprite->RebuildRenderData();
		}
	}

	//@TODO: Adjust the atlas rebuild code so that it tries to preserve existing sprites (optimize for the 'just added one to the end' case, preventing dirtying lots of assets unnecessarily)
	//@TODO: invoke this code when a sprite has the atlas group property modified
}
void FPaperSpriteSheetAssetTypeActions::ExecuteCreateFlipbooks(TArray<TWeakObjectPtr<UPaperSpriteSheet>> Objects)
{	
	for (int SpriteSheetIndex = 0; SpriteSheetIndex < Objects.Num(); ++SpriteSheetIndex)
	{
		UPaperSpriteSheet* SpriteSheet = Objects[SpriteSheetIndex].Get();
		if (SpriteSheet != nullptr)
		{
			const FString PackagePath = FPackageName::GetLongPackagePath(SpriteSheet->GetOutermost()->GetPathName());

			FAssetToolsModule& AssetToolsModule = FModuleManager::GetModuleChecked<FAssetToolsModule>("AssetTools");
			FContentBrowserModule& ContentBrowserModule = FModuleManager::LoadModuleChecked<FContentBrowserModule>("ContentBrowser");
			
			check(SpriteSheet->SpriteNames.Num() == SpriteSheet->Sprites.Num());
			bool useSpriteNames = (SpriteSheet->SpriteNames.Num() == SpriteSheet->Sprites.Num());

			// Create a list of sprites and sprite names to feed into paper flipbook helpers
			TMap<FString, TArray<UPaperSprite*> > SpriteFlipbookMap;

			{
				TArray<UPaperSprite*> Sprites;
				TArray<FString> SpriteNames;

				for (int SpriteIndex = 0; SpriteIndex < SpriteSheet->Sprites.Num(); ++SpriteIndex)
				{
					auto SpriteAssetPtr = SpriteSheet->Sprites[SpriteIndex];
					FStringAssetReference SpriteStringRef = SpriteAssetPtr.ToStringReference();
					UPaperSprite* Sprite = nullptr;
					if (!SpriteStringRef.ToString().IsEmpty())
					{
						Sprite = Cast<UPaperSprite>(StaticLoadObject(UPaperSprite::StaticClass(), nullptr, *SpriteStringRef.ToString(), nullptr, LOAD_None, nullptr));
					}
					if (Sprite != nullptr)
					{
						const FString SpriteName = useSpriteNames ? SpriteSheet->SpriteNames[SpriteIndex] : Sprite->GetName();
						Sprites.Add(Sprite);
						SpriteNames.Add(SpriteName);
					}
				}

				FPaperFlipbookHelpers::ExtractFlipbooksFromSprites(/*out*/ SpriteFlipbookMap, Sprites, SpriteNames);
			}

			// Create one flipbook for every grouped flipbook name
			if (SpriteFlipbookMap.Num() > 0)
			{
				UPaperFlipbookFactory* FlipbookFactory = NewObject<UPaperFlipbookFactory>();

				GWarn->BeginSlowTask(NSLOCTEXT("Paper2D", "Paper2D_CreateFlipbooks", "Creating flipbooks from selection"), true, true);

				int Progress = 0;
				int TotalProgress = SpriteFlipbookMap.Num();
				TArray<UObject*> ObjectsToSync;
				for (auto It : SpriteFlipbookMap)
				{
					GWarn->UpdateProgress(Progress++, TotalProgress);

					const FString& FlipbookName = It.Key;
					TArray<UPaperSprite*>& Sprites = It.Value;

					const FString TentativePackagePath = PackageTools::SanitizePackageName(PackagePath + TEXT("/") + FlipbookName);
					FString DefaultSuffix;
					FString AssetName;
					FString PackageName;
					AssetToolsModule.Get().CreateUniqueAssetName(TentativePackagePath, DefaultSuffix, /*out*/ PackageName, /*out*/ AssetName);

					FlipbookFactory->KeyFrames.Empty();
					for (int32 SpriteIndex = 0; SpriteIndex < Sprites.Num(); ++SpriteIndex)
					{
						FPaperFlipbookKeyFrame* KeyFrame = new (FlipbookFactory->KeyFrames) FPaperFlipbookKeyFrame();
						KeyFrame->Sprite = Sprites[SpriteIndex];
						KeyFrame->FrameRun = 1;
					}

					if (UObject* NewAsset = AssetToolsModule.Get().CreateAsset(AssetName, PackagePath, UPaperFlipbook::StaticClass(), FlipbookFactory))
					{
						ObjectsToSync.Add(NewAsset);
					}

					if (GWarn->ReceivedUserCancel())
					{
						break;
					}
				}

				GWarn->EndSlowTask();

				if (ObjectsToSync.Num() > 0)
				{
					ContentBrowserModule.Get().SyncBrowserToAssets(ObjectsToSync);
				}
			}
		}
	}
}
void FSpriteEditorViewportClient::ProcessClick(FSceneView& View, HHitProxy* HitProxy, FKey Key, EInputEvent Event, uint32 HitX, uint32 HitY)
{
	const FViewportClick Click(&View, this, Key, Event, HitX, HitY);
	const bool bIsCtrlKeyDown = Viewport->KeyState(EKeys::LeftControl) || Viewport->KeyState(EKeys::RightControl);
	const bool bIsShiftKeyDown = Viewport->KeyState(EKeys::LeftShift) || Viewport->KeyState(EKeys::RightShift);
	const bool bIsAltKeyDown = Viewport->KeyState(EKeys::LeftAlt) || Viewport->KeyState(EKeys::RightAlt);
	bool bHandled = false;

	HSpriteSelectableObjectHitProxy* SelectedItemProxy = HitProxyCast<HSpriteSelectableObjectHitProxy>(HitProxy);

	if (IsInSourceRegionEditMode())
	{
		if ((Event == EInputEvent::IE_DoubleClick) && (Key == EKeys::LeftMouseButton))
		{
			FVector4 WorldPoint = View.PixelToWorld(HitX, HitY, 0);
			UPaperSprite* Sprite = GetSpriteBeingEdited();
			FVector2D TexturePoint = SourceTextureViewComponent->GetSprite()->ConvertWorldSpaceToTextureSpace(WorldPoint);
			if (bIsCtrlKeyDown)
			{
				const FVector2D StartingUV = Sprite->GetSourceUV();
				const FVector2D StartingSize = Sprite->GetSourceSize();

				if (UPaperSprite* NewSprite = CreateNewSprite(FIntPoint((int32)StartingUV.X, (int32)StartingUV.Y), FIntPoint((int32)StartingSize.X, (int32)StartingSize.Y)))
				{
					NewSprite->ExtractSourceRegionFromTexturePoint(TexturePoint);
					bHandled = true;
				}
			}
			else
			{
				Sprite->ExtractSourceRegionFromTexturePoint(TexturePoint);
				bHandled = true;
			}
		}
		else if ((Event == EInputEvent::IE_Released) && (Key == EKeys::LeftMouseButton))
		{
			FVector4 WorldPoint = View.PixelToWorld(HitX, HitY, 0);
			FVector2D TexturePoint = SourceTextureViewComponent->GetSprite()->ConvertWorldSpaceToTextureSpace(WorldPoint);
			for (int32 RelatedSpriteIndex = 0; RelatedSpriteIndex < RelatedSprites.Num(); ++RelatedSpriteIndex)
			{
				FRelatedSprite& RelatedSprite = RelatedSprites[RelatedSpriteIndex];
				if ((TexturePoint.X >= RelatedSprite.SourceUV.X) && (TexturePoint.Y >= RelatedSprite.SourceUV.Y) &&
					(TexturePoint.X < (RelatedSprite.SourceUV.X + RelatedSprite.SourceDimension.X)) &&
					(TexturePoint.Y < (RelatedSprite.SourceUV.Y + RelatedSprite.SourceDimension.Y)))
				{
					bHandled = true;

					// Select this sprite
					if (UPaperSprite* LoadedSprite = Cast<UPaperSprite>(RelatedSprite.AssetData.GetAsset()))
					{
						if (SpriteEditorPtr.IsValid())
						{
							SpriteEditorPtr.Pin()->SetSpriteBeingEdited(LoadedSprite);
							break;
						}
					}
				}
			}
		}
	}

	if (!bHandled)
	{
		FPaperEditorViewportClient::ProcessClick(View, HitProxy, Key, Event, HitX, HitY);
	}
}
FVector2D FSpriteEditorViewportClient::SelectedItemConvertWorldSpaceDeltaToLocalSpace(const FVector& WorldSpaceDelta) const
{
	UPaperSprite* Sprite = GetSpriteBeingEdited();
	return Sprite->ConvertWorldSpaceDeltaToTextureSpace(WorldSpaceDelta);
}
bool FPaperJsonSpriteSheetImporter::PerformImport(const FString& LongPackagePath, EObjectFlags Flags, UPaperSpriteSheet* SpriteSheet)
{
	FAssetToolsModule& AssetToolsModule = FModuleManager::GetModuleChecked<FAssetToolsModule>("AssetTools");

	GWarn->BeginSlowTask(NSLOCTEXT("Paper2D", "PaperJsonImporterFactory_ImportingSprites", "Importing Sprite Frame"), true, true);
	for (int32 FrameIndex = 0; FrameIndex < Frames.Num(); ++FrameIndex)
	{
		GWarn->StatusUpdate(FrameIndex, Frames.Num(), NSLOCTEXT("Paper2D", "PaperJsonImporterFactory_ImportingSprites", "Importing Sprite Frames"));

		// Check for user canceling the import
		if (GWarn->ReceivedUserCancel())
		{
			break;
		}

		const FSpriteFrame& Frame = Frames[FrameIndex];

		// Create a package for the frame
		const FString TargetSubPath = LongPackagePath + TEXT("/Frames");

		UObject* OuterForFrame = nullptr; // @TODO: Use this if we don't want them to be individual assets - Flipbook;

		// Create a frame in the package
		UPaperSprite* TargetSprite = nullptr;
		
		if (bIsReimporting)
		{
			TargetSprite = FindExistingSprite(Frame.FrameName.ToString());
		}
		
		if (TargetSprite == nullptr)
		{
			const FString SanitizedFrameName = ObjectTools::SanitizeObjectName(Frame.FrameName.ToString());
			const FString TentativePackagePath = PackageTools::SanitizePackageName(TargetSubPath + TEXT("/") + SanitizedFrameName);
			FString DefaultSuffix;
			FString AssetName;
			FString PackageName;
			AssetToolsModule.Get().CreateUniqueAssetName(TentativePackagePath, DefaultSuffix, /*out*/ PackageName, /*out*/ AssetName);

			// Create a unique package name and asset name for the frame
			if (OuterForFrame == nullptr)
			{
				// Create a package for the frame
				OuterForFrame = CreatePackage(nullptr, *PackageName);
			}

			// Create the asset
			TargetSprite = NewObject<UPaperSprite>(OuterForFrame, *AssetName, Flags);
			FAssetRegistryModule::AssetCreated(TargetSprite);
		}

		TargetSprite->Modify();
		FSpriteAssetInitParameters SpriteInitParams;
		SpriteInitParams.Texture = ImageTexture;

		if (NormalMapTexture != nullptr)
		{
			// Put the normal map into the additional textures array and ask for a lit material instead of unlit
			SpriteInitParams.AdditionalTextures.Add(NormalMapTexture);
		}

		SpriteInitParams.Offset = Frame.SpritePosInSheet;
		SpriteInitParams.Dimension = Frame.SpriteSizeInSheet;

		GetDefault<UPaperImporterSettings>()->ApplySettingsForSpriteInit(/*inout*/ SpriteInitParams, (NormalMapTexture != nullptr) ? ESpriteInitMaterialLightingMode::ForceLit : ESpriteInitMaterialLightingMode::Automatic);

		TargetSprite->InitializeSprite(SpriteInitParams);

		if (Frame.bRotated)
		{
			TargetSprite->SetRotated(true);
		}

		if (Frame.bTrimmed)
		{
			TargetSprite->SetTrim(Frame.bTrimmed, Frame.SpriteSourcePos, Frame.ImageSourceSize);
		}

		// Set up pivot on object based on Texture Packer json
		ESpritePivotMode::Type PivotType = GetBestPivotType(Frame.Pivot);
		FVector2D TextureSpacePivotPoint = FVector2D::ZeroVector;
		if (PivotType == ESpritePivotMode::Custom)
		{
			TextureSpacePivotPoint.X = Frame.SpritePosInSheet.X - Frame.SpriteSourcePos.X + Frame.ImageSourceSize.X * Frame.Pivot.X;
			TextureSpacePivotPoint.Y = Frame.SpritePosInSheet.Y - Frame.SpriteSourcePos.Y + Frame.ImageSourceSize.Y * Frame.Pivot.Y;
		}
		TargetSprite->SetPivotMode(PivotType, TextureSpacePivotPoint);

		// Create the entry in the animation
		SpriteSheet->SpriteNames.Add(Frame.FrameName.ToString());
		SpriteSheet->Sprites.Add(TargetSprite);
		
		TargetSprite->PostEditChange();
	}

	SpriteSheet->TextureName = ImageName;
	SpriteSheet->Texture = ImageTexture;
	SpriteSheet->NormalMapTextureName = ComputedNormalMapName;
	SpriteSheet->NormalMapTexture = NormalMapTexture;

	GWarn->EndSlowTask();
	return true;
}
void FSpriteAssetTypeActions::ExecuteCreateFlipbook(TArray<TWeakObjectPtr<UPaperSprite>> Objects)
{
	FAssetToolsModule& AssetToolsModule = FModuleManager::GetModuleChecked<FAssetToolsModule>("AssetTools");
	FContentBrowserModule& ContentBrowserModule = FModuleManager::LoadModuleChecked<FContentBrowserModule>("ContentBrowser");
	TArray<UPaperSprite*> AllSprites;

	for (auto ObjIt = Objects.CreateConstIterator(); ObjIt; ++ObjIt)
	{
		UPaperSprite *Object = (*ObjIt).Get();
		if (Object && Object->IsValidLowLevel())
		{
			AllSprites.Add(Object);
		}
	}

	TMap<FString, TArray<UPaperSprite*> > SpriteFlipbookMap;
	FPaperFlipbookHelpers::ExtractFlipbooksFromSprites(/*out*/SpriteFlipbookMap, AllSprites, TArray<FString>());
	TArray<UObject*> ObjectsToSync;

	if (SpriteFlipbookMap.Num() > 0)
	{
		GWarn->BeginSlowTask(NSLOCTEXT("Paper2D", "Paper2D_CreateFlipbooks", "Creating flipbooks from selection"), true, true);

		int Progress = 0;
		int TotalProgress = SpriteFlipbookMap.Num();

		// Create the flipbook
		bool bOneFlipbookCreated = SpriteFlipbookMap.Num() == 1;
		for (auto Iter : SpriteFlipbookMap)
		{
			GWarn->UpdateProgress(Progress++, TotalProgress);

			const FString& FlipbookName = Iter.Key;
			TArray<UPaperSprite*> Sprites = Iter.Value;

			const FString SpritePathName = AllSprites[0]->GetOutermost()->GetPathName();
			const FString LongPackagePath = FPackageName::GetLongPackagePath(AllSprites[0]->GetOutermost()->GetPathName());

			const FString NewFlipBookDefaultPath = LongPackagePath + TEXT("/") + FlipbookName;
			FString DefaultSuffix;
			FString AssetName;
			FString PackageName;

			UPaperFlipbookFactory* FlipbookFactory = NewObject<UPaperFlipbookFactory>();
			for (int32 SpriteIndex = 0; SpriteIndex < Sprites.Num(); ++SpriteIndex)
			{
				UPaperSprite* Sprite = Sprites[SpriteIndex];
				FPaperFlipbookKeyFrame* KeyFrame = new (FlipbookFactory->KeyFrames) FPaperFlipbookKeyFrame();
				KeyFrame->Sprite = Sprite;
				KeyFrame->FrameRun = 1;
			}

			AssetToolsModule.Get().CreateUniqueAssetName(NewFlipBookDefaultPath, /*out*/ DefaultSuffix, /*out*/ PackageName, /*out*/ AssetName);
			const FString PackagePath = FPackageName::GetLongPackagePath(PackageName);
			if (bOneFlipbookCreated)
			{
				ContentBrowserModule.Get().CreateNewAsset(AssetName, PackagePath, UPaperFlipbook::StaticClass(), FlipbookFactory);
			}
			else
			{
				if (UObject* NewAsset = AssetToolsModule.Get().CreateAsset(AssetName, PackagePath, UPaperFlipbook::StaticClass(), FlipbookFactory))
				{
					ObjectsToSync.Add(NewAsset);
				}
			}

			if (GWarn->ReceivedUserCancel())
			{
				break;
			}
		}

		GWarn->EndSlowTask();

		if (ObjectsToSync.Num() > 0)
		{
			ContentBrowserModule.Get().SyncBrowserToAssets(ObjectsToSync);
		}
	}

}
bool FSpriteEditorViewportClient::ConvertMarqueeToSourceTextureSpace(/*out*/ FIntPoint& OutStartPos, /*out*/ FIntPoint& OutDimension)
{
	FSpriteGeometryEditMode* GeometryEditMode = ModeTools->GetActiveModeTyped<FSpriteGeometryEditMode>(FSpriteGeometryEditMode::EM_SpriteGeometry);
	check(GeometryEditMode);
	const FVector2D MarqueeStartPos = GeometryEditMode->GetMarqueeStartPos();
	const FVector2D MarqueeEndPos = GeometryEditMode->GetMarqueeEndPos();

	bool bSuccessful = false;
	UPaperSprite* Sprite = SourceTextureViewComponent->GetSprite();
	UTexture2D* SpriteSourceTexture = Sprite->GetSourceTexture();
	if (SpriteSourceTexture != nullptr)
	{
		// Calculate world space positions
		FSceneViewFamilyContext ViewFamily(FSceneViewFamily::ConstructionValues(Viewport, GetScene(), EngineShowFlags));
		FSceneView* View = CalcSceneView(&ViewFamily);
		const FVector StartPos = View->PixelToWorld(MarqueeStartPos.X, MarqueeStartPos.Y, 0);
		const FVector EndPos = View->PixelToWorld(MarqueeEndPos.X, MarqueeEndPos.Y, 0);

		// Convert to source texture space to work out the pixels dragged
		FVector2D TextureSpaceStartPos = Sprite->ConvertWorldSpaceToTextureSpace(StartPos);
		FVector2D TextureSpaceEndPos = Sprite->ConvertWorldSpaceToTextureSpace(EndPos);

		if (TextureSpaceStartPos.X > TextureSpaceEndPos.X)
		{
			Swap(TextureSpaceStartPos.X, TextureSpaceEndPos.X);
		}
		if (TextureSpaceStartPos.Y > TextureSpaceEndPos.Y)
		{
			Swap(TextureSpaceStartPos.Y, TextureSpaceEndPos.Y);
		}

		const FIntPoint SourceTextureSize(SpriteSourceTexture->GetImportedSize());
		const int32 SourceTextureWidth = SourceTextureSize.X;
		const int32 SourceTextureHeight = SourceTextureSize.Y;
		
		FIntPoint TSStartPos;
		TSStartPos.X = FMath::Clamp<int32>((int32)TextureSpaceStartPos.X, 0, SourceTextureWidth - 1);
		TSStartPos.Y = FMath::Clamp<int32>((int32)TextureSpaceStartPos.Y, 0, SourceTextureHeight - 1);

		FIntPoint TSEndPos;
		TSEndPos.X = FMath::Clamp<int32>((int32)TextureSpaceEndPos.X, 0, SourceTextureWidth - 1);
		TSEndPos.Y = FMath::Clamp<int32>((int32)TextureSpaceEndPos.Y, 0, SourceTextureHeight - 1);

		const FIntPoint TextureSpaceDimensions = TSEndPos - TSStartPos;
		if ((TextureSpaceDimensions.X > 0) || (TextureSpaceDimensions.Y > 0))
		{
			OutStartPos = TSStartPos;
			OutDimension = TextureSpaceDimensions;
			bSuccessful = true;
		}
	}

	return bSuccessful;
}