void FSpriteEditorViewportClient::BeginTransaction(const FText& SessionName) { if (ScopedTransaction == nullptr) { ScopedTransaction = new FScopedTransaction(SessionName); UPaperSprite* Sprite = GetSpriteBeingEdited(); Sprite->Modify(); } }
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 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 }