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