// Copyright Epic Games, Inc. All Rights Reserved. #include "AnimToTextureBPLibrary.h" #include "AnimToTextureEditorModule.h" #include "AnimToTextureUtils.h" #include "AnimToTextureSkeletalMesh.h" #include "LevelEditor.h" #include "RawMesh.h" #include "MeshUtilities.h" #include "Modules/ModuleManager.h" #include "Engine/SkeletalMesh.h" #include "Components/SkeletalMeshComponent.h" #include "Animation/Skeleton.h" #include "Animation/AnimSequence.h" #include "Math/Vector.h" #include "Math/NumericLimits.h" #include "MeshDescription.h" #include "Materials/MaterialInstanceConstant.h" #include "MaterialEditingLibrary.h" #include "Misc/ScopedSlowTask.h" #define LOCTEXT_NAMESPACE "AnimToTextureEditor" using namespace AnimToTexture_Private; UAnimToTextureBPLibrary::UAnimToTextureBPLibrary(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) { } bool UAnimToTextureBPLibrary::AnimationToTexture(UAnimToTextureDataAsset* DataAsset) { if (!DataAsset) { return false; } // Runs some checks for the assets in DataAsset int32 SocketIndex = INDEX_NONE; TArray AnimSequences; if (!CheckDataAsset(DataAsset, SocketIndex, AnimSequences)) { return false; } // Reset DataAsset Info Values DataAsset->ResetInfo(); // --------------------------------------------------------------------------- // Get Mapping between Static and Skeletal Meshes // Since they might not have same number of points. // FSourceMeshToDriverMesh Mapping; { FScopedSlowTask ProgressBar(1.f, LOCTEXT("ProcessingMapping", "Processing StaticMesh -> SkeletalMesh Mapping ..."), true /*Enabled*/); ProgressBar.MakeDialog(false /*bShowCancelButton*/, false /*bAllowInPIE*/); Mapping.Update(DataAsset->GetStaticMesh(), DataAsset->StaticLODIndex, DataAsset->GetSkeletalMesh(), DataAsset->SkeletalLODIndex, DataAsset->NumDriverTriangles, DataAsset->Sigma); } // Get Number of Source Vertices (StaticMesh) const int32 NumVertices = Mapping.GetNumSourceVertices(); if (!NumVertices) { return false; } // --------------------------------------------------------------------------- // Get Reference Skeleton Transforms // TArray BoneRefPositions; TArray BoneRefRotations; TArray BonePositions; TArray BoneRotations; if (DataAsset->Mode == EAnimToTextureMode::Bone) { // Gets Ref Bone Position and Rotations. DataAsset->NumBones = GetRefBonePositionsAndRotations(DataAsset->GetSkeletalMesh(), BoneRefPositions, BoneRefRotations); // Add RefPose // Note: this is added in the first frame of the Bone Position and Rotation Textures BonePositions.Append(BoneRefPositions); BoneRotations.Append(BoneRefRotations); } // -------------------------------------------------------------------------- // Create Temp Actor check(GEditor); UWorld* World = GEditor->GetEditorWorldContext().World(); check(World); AActor* Actor = World->SpawnActor(); check(Actor); // Create Temp SkeletalMesh Component USkeletalMeshComponent* SkeletalMeshComponent = NewObject(Actor); check(SkeletalMeshComponent); SkeletalMeshComponent->SetSkeletalMesh(DataAsset->GetSkeletalMesh()); SkeletalMeshComponent->SetForcedLOD(1); // Force to LOD0; SkeletalMeshComponent->SetAnimationMode(EAnimationMode::AnimationSingleNode); SkeletalMeshComponent->SetUpdateAnimationInEditor(true); SkeletalMeshComponent->VisibilityBasedAnimTickOption = EVisibilityBasedAnimTickOption::AlwaysTickPoseAndRefreshBones; SkeletalMeshComponent->RegisterComponent(); // --------------------------------------------------------------------------- // Get Vertex Data (for all frames) // TArray VertexDeltas; TArray VertexNormals; // Get Animation Frames Data // for (int32 AnimSequenceIndex = 0; AnimSequenceIndex < AnimSequences.Num(); AnimSequenceIndex++) { const FAnimToTextureAnimSequenceInfo& AnimSequenceInfo = AnimSequences[AnimSequenceIndex]; // Set Animation UAnimSequence* AnimSequence = AnimSequenceInfo.AnimSequence; SkeletalMeshComponent->SetAnimation(AnimSequence); // Get Number of Frames int32 AnimStartFrame; int32 AnimEndFrame; const int32 AnimNumFrames = GetAnimationFrameRange(AnimSequenceInfo, AnimStartFrame, AnimEndFrame); const float AnimStartTime = AnimSequence->GetTimeAtFrame(AnimStartFrame); int32 SampleIndex = 0; const float SampleInterval = 1.f / DataAsset->SampleRate; // Progress Bar FFormatNamedArguments Args; Args.Add(TEXT("AnimSequenceIndex"), AnimSequenceIndex+1); Args.Add(TEXT("NumAnimSequences"), AnimSequences.Num()); Args.Add(TEXT("AnimSequence"), FText::FromString(*AnimSequence->GetFName().ToString())); FScopedSlowTask AnimProgressBar(AnimNumFrames, FText::Format(LOCTEXT("ProcessingAnimSequence", "Processing AnimSequence: {AnimSequence} [{AnimSequenceIndex}/{NumAnimSequences}]"), Args), true /*Enabled*/); AnimProgressBar.MakeDialog(false /*bShowCancelButton*/, false /*bAllowInPIE*/); while (SampleIndex < AnimNumFrames) { AnimProgressBar.EnterProgressFrame(); const float Time = AnimStartTime + ((float)SampleIndex * SampleInterval); SampleIndex++; // Go To Time SkeletalMeshComponent->SetPosition(Time); // Update SkelMesh Animation. SkeletalMeshComponent->TickAnimation(0.f, false /*bNeedsValidRootMotion*/); SkeletalMeshComponent->RefreshBoneTransforms(nullptr /*TickFunction*/); // --------------------------------------------------------------------------- // Store Vertex Deltas & Normals. // if (DataAsset->Mode == EAnimToTextureMode::Vertex) { TArray VertexFrameDeltas; TArray VertexFrameNormals; GetVertexDeltasAndNormals(SkeletalMeshComponent, DataAsset->SkeletalLODIndex, Mapping, DataAsset->RootTransform, VertexFrameDeltas, VertexFrameNormals); VertexDeltas.Append(VertexFrameDeltas); VertexNormals.Append(VertexFrameNormals); } // --------------------------------------------------------------------------- // Store Bone Positions & Rotations // else if (DataAsset->Mode == EAnimToTextureMode::Bone) { TArray BoneFramePositions; TArray BoneFrameRotations; GetBonePositionsAndRotations(SkeletalMeshComponent, BoneRefPositions, BoneFramePositions, BoneFrameRotations); BonePositions.Append(BoneFramePositions); BoneRotations.Append(BoneFrameRotations); } } // End Frame // Store Anim Info Data FAnimToTextureAnimInfo AnimInfo; AnimInfo.StartFrame = DataAsset->NumFrames; AnimInfo.EndFrame = DataAsset->NumFrames + AnimNumFrames - 1; DataAsset->Animations.Add(AnimInfo); // Accumulate Frames DataAsset->NumFrames += AnimNumFrames; } // End Anim // Destroy Temp Component & Actor SkeletalMeshComponent->UnregisterComponent(); SkeletalMeshComponent->DestroyComponent(); Actor->Destroy(); // --------------------------------------------------------------------------- if (DataAsset->Mode == EAnimToTextureMode::Vertex) { // Find Best Resolution for Vertex Data int32 Height, Width; if (!FindBestResolution(DataAsset->NumFrames, NumVertices, Height, Width, DataAsset->VertexRowsPerFrame, DataAsset->MaxHeight, DataAsset->MaxWidth, DataAsset->bEnforcePowerOfTwo)) { UE_LOG(LogAnimToTextureEditor, Warning, TEXT("Vertex Animation data cannot be fit in a %ix%i texture."), DataAsset->MaxHeight, DataAsset->MaxWidth); return false; } // Normalize Vertex Data TArray NormalizedVertexDeltas; TArray NormalizedVertexNormals; NormalizeVertexData( VertexDeltas, VertexNormals, DataAsset->VertexMinBBox, DataAsset->VertexSizeBBox, NormalizedVertexDeltas, NormalizedVertexNormals); // Write Textures if (DataAsset->Precision == EAnimToTexturePrecision::SixteenBits) { WriteVectorsToTexture(NormalizedVertexDeltas, DataAsset->NumFrames, DataAsset->VertexRowsPerFrame, Height, Width, DataAsset->GetVertexPositionTexture()); WriteVectorsToTexture(NormalizedVertexNormals, DataAsset->NumFrames, DataAsset->VertexRowsPerFrame, Height, Width, DataAsset->GetVertexNormalTexture()); } else { WriteVectorsToTexture(NormalizedVertexDeltas, DataAsset->NumFrames, DataAsset->VertexRowsPerFrame, Height, Width, DataAsset->GetVertexPositionTexture()); WriteVectorsToTexture(NormalizedVertexNormals, DataAsset->NumFrames, DataAsset->VertexRowsPerFrame, Height, Width, DataAsset->GetVertexNormalTexture()); } // Add Vertex UVChannel CreateUVChannel(DataAsset->GetStaticMesh(), DataAsset->StaticLODIndex, DataAsset->UVChannel, Height, Width); // Update Bounds SetBoundsExtensions(DataAsset->GetStaticMesh(), (FVector)DataAsset->VertexMinBBox, (FVector)DataAsset->VertexSizeBBox); // Done with StaticMesh DataAsset->GetStaticMesh()->PostEditChange(); } // --------------------------------------------------------------------------- if (DataAsset->Mode == EAnimToTextureMode::Bone) { // Find Best Resolution for Bone Data int32 Height, Width; // Write Bone Position and Rotation Textures { // Note we are adding +1 frame for the ref pose if (!FindBestResolution(DataAsset->NumFrames + 1, DataAsset->NumBones, Height, Width, DataAsset->BoneRowsPerFrame, DataAsset->MaxHeight, DataAsset->MaxWidth, DataAsset->bEnforcePowerOfTwo)) { UE_LOG(LogAnimToTextureEditor, Warning, TEXT("Bone Animation data cannot be fit in a %ix%i texture."), DataAsset->MaxHeight, DataAsset->MaxWidth); return false; } // Normalize Bone Data TArray NormalizedBonePositions; TArray NormalizedBoneRotations; NormalizeBoneData( BonePositions, BoneRotations, DataAsset->BoneMinBBox, DataAsset->BoneSizeBBox, NormalizedBonePositions, NormalizedBoneRotations); // Write Textures if (DataAsset->Precision == EAnimToTexturePrecision::SixteenBits) { WriteVectorsToTexture(NormalizedBonePositions, DataAsset->NumFrames + 1, DataAsset->BoneRowsPerFrame, Height, Width, DataAsset->GetBonePositionTexture()); WriteVectorsToTexture(NormalizedBoneRotations, DataAsset->NumFrames + 1, DataAsset->BoneRowsPerFrame, Height, Width, DataAsset->GetBoneRotationTexture()); } else { WriteVectorsToTexture(NormalizedBonePositions, DataAsset->NumFrames + 1, DataAsset->BoneRowsPerFrame, Height, Width, DataAsset->GetBonePositionTexture()); WriteVectorsToTexture(NormalizedBoneRotations, DataAsset->NumFrames + 1, DataAsset->BoneRowsPerFrame, Height, Width, DataAsset->GetBoneRotationTexture()); } // Update Bounds SetBoundsExtensions(DataAsset->GetStaticMesh(), (FVector)DataAsset->BoneMinBBox, (FVector)DataAsset->BoneSizeBBox); } // --------------------------------------------------------------------------- // Write Weights Texture { // Find Best Resolution for Bone Weights Texture if (!FindBestResolution(2, NumVertices, Height, Width, DataAsset->BoneWeightRowsPerFrame, DataAsset->MaxHeight, DataAsset->MaxWidth, DataAsset->bEnforcePowerOfTwo)) { UE_LOG(LogAnimToTextureEditor, Warning, TEXT("Weights Data cannot be fit in a %ix%i texture."), DataAsset->MaxHeight, DataAsset->MaxWidth); return false; } TArray> SkinWeights; // Reduce BoneWeights to 4 Influences. if (SocketIndex == INDEX_NONE) { // Project SkinWeights from SkeletalMesh to StaticMesh TArray StaticMeshSkinWeights; Mapping.ProjectSkinWeights(StaticMeshSkinWeights); // Reduce Weights to 4 highest influences. ReduceSkinWeights(StaticMeshSkinWeights, SkinWeights); } // If Valid Socket, set all influences to same index. else { // Set all indices and weights to same SocketIndex SkinWeights.SetNumUninitialized(NumVertices); for (TVertexSkinWeight<4>& SkinWeight : SkinWeights) { SkinWeight.BoneWeights = TStaticArray(InPlace, 255); SkinWeight.MeshBoneIndices = TStaticArray(InPlace, SocketIndex); } } // Write Bone Weights Texture if (DataAsset->Precision == EAnimToTexturePrecision::SixteenBits) { WriteSkinWeightsToTexture(SkinWeights, DataAsset->NumBones, DataAsset->BoneWeightRowsPerFrame, Height, Width, DataAsset->GetBoneWeightTexture()); } else { WriteSkinWeightsToTexture(SkinWeights, DataAsset->NumBones, DataAsset->BoneWeightRowsPerFrame, Height, Width, DataAsset->GetBoneWeightTexture()); } // Add Vertex UVChannel CreateUVChannel(DataAsset->GetStaticMesh(), DataAsset->StaticLODIndex, DataAsset->UVChannel, Height, Width); } // Done with StaticMesh DataAsset->GetStaticMesh()->PostEditChange(); } // --------------------------------------------------------------------------- // Mark Packages dirty // DataAsset->MarkPackageDirty(); // All good here ! return true; } bool UAnimToTextureBPLibrary::CheckDataAsset(const UAnimToTextureDataAsset* DataAsset, int32& OutSocketIndex, TArray& OutAnimSequences) { // Check StaticMesh if (!DataAsset->GetStaticMesh()) { UE_LOG(LogAnimToTextureEditor, Warning, TEXT("Invalid StaticMesh")); return false; } // Check SkeletalMesh if (!DataAsset->GetSkeletalMesh()) { UE_LOG(LogAnimToTextureEditor, Warning, TEXT("Invalid SkeletalMesh")); return false; } // Check Skeleton if (!DataAsset->GetSkeletalMesh()->GetSkeleton()) { UE_LOG(LogAnimToTextureEditor, Warning, TEXT("Invalid SkeletalMesh. No valid Skeleton found")); return false; } // Check StaticMesh LOD if (!DataAsset->GetStaticMesh()->IsSourceModelValid(DataAsset->StaticLODIndex)) { UE_LOG(LogAnimToTextureEditor, Warning, TEXT("Invalid StaticMesh LOD Index: %i"), DataAsset->StaticLODIndex); return false; } // Check SkeletalMesh LOD if (!DataAsset->GetSkeletalMesh()->IsValidLODIndex(DataAsset->SkeletalLODIndex)) { UE_LOG(LogAnimToTextureEditor, Warning, TEXT("Invalid SkeletalMesh LOD Index: %i"), DataAsset->SkeletalLODIndex); return false; } // Check Socket. OutSocketIndex = INDEX_NONE; if (DataAsset->AttachToSocket.IsValid() && !DataAsset->AttachToSocket.IsNone()) { // Get Bone Names (no virtual) TArray BoneNames; GetBoneNames(DataAsset->GetSkeletalMesh(), BoneNames); // Check if Socket is in BoneNames if (!BoneNames.Find(DataAsset->AttachToSocket, OutSocketIndex)) { UE_LOG(LogAnimToTextureEditor, Warning, TEXT("Socket: %s not found in Raw Bone List"), *DataAsset->AttachToSocket.ToString()); return false; } else { // TODO: SocketIndex can only be < TNumericLimits::Max() } } // Check if UVChannel is being used by the Lightmap UV const FStaticMeshSourceModel& SourceModel = DataAsset->GetStaticMesh()->GetSourceModel(DataAsset->StaticLODIndex); if (SourceModel.BuildSettings.bGenerateLightmapUVs && SourceModel.BuildSettings.DstLightmapIndex == DataAsset->UVChannel) { UE_LOG(LogAnimToTextureEditor, Warning, TEXT("Invalid StaticMesh UVChannel: %i. Already used by LightMap"), DataAsset->UVChannel); return false; } // Check if NumBones > 256 const int32 NumBones = GetNumBones(DataAsset->GetSkeletalMesh()); if (DataAsset->Precision == EAnimToTexturePrecision::EightBits && NumBones > 256) { UE_LOG(LogAnimToTextureEditor, Warning, TEXT("Too many Bones: %i. There is a maximum of 256 bones for 8bit Precision"), NumBones); return false; } // Check Animations OutAnimSequences.Reset(); for (const FAnimToTextureAnimSequenceInfo& AnimSequenceInfo : DataAsset->AnimSequences) { const UAnimSequence* AnimSequence = AnimSequenceInfo.AnimSequence; if (AnimSequenceInfo.bEnabled && AnimSequence) { // Make sure SkeletalMesh is compatible with AnimSequence if (!DataAsset->GetSkeletalMesh()->GetSkeleton()->IsCompatibleForEditor(AnimSequence->GetSkeleton())) { UE_LOG(LogAnimToTextureEditor, Warning, TEXT("Invalid AnimSequence: %s for given SkeletalMesh: %s"), *AnimSequence->GetFName().ToString(), *DataAsset->GetSkeletalMesh()->GetFName().ToString()); return false; } // Check Frame Range if (AnimSequenceInfo.bUseCustomRange && (AnimSequenceInfo.StartFrame < 0 || AnimSequenceInfo.EndFrame > AnimSequence->GetNumberOfSampledKeys() - 1 || AnimSequenceInfo.EndFrame - AnimSequenceInfo.StartFrame < 0)) { UE_LOG(LogAnimToTextureEditor, Warning, TEXT("Invalid CustomRange for AnimSequence: %s"), *AnimSequence->GetName()); return false; } // Store Valid AnimSequenceInfo OutAnimSequences.Add(AnimSequenceInfo); } } if (!OutAnimSequences.Num()) { UE_LOG(LogAnimToTextureEditor, Warning, TEXT("No valid AnimSequences found")); return false; } // All Good ! return true; } int32 UAnimToTextureBPLibrary::GetAnimationFrameRange(const FAnimToTextureAnimSequenceInfo& Animation, int32& OutStartFrame, int32& OutEndFrame) { if (!Animation.AnimSequence) { return INDEX_NONE; } // Get Range from AnimSequence if (!Animation.bUseCustomRange) { OutStartFrame = 0; OutEndFrame = Animation.AnimSequence->GetNumberOfSampledKeys() - 1; // AnimSequence->GetNumberOfFrames(); } // Get Custom Range else { OutStartFrame = Animation.StartFrame; OutEndFrame = Animation.EndFrame; } // Return Number of Frames return OutEndFrame - OutStartFrame + 1; } // void UAnimToTextureBPLibrary::GetVertexDeltasAndNormals(const USkeletalMeshComponent* SkeletalMeshComponent, const int32 LODIndex, const AnimToTexture_Private::FSourceMeshToDriverMesh& SourceMeshToDriverMesh, const FTransform RootTransform, TArray& OutVertexDeltas, TArray& OutVertexNormals) { OutVertexDeltas.Reset(); OutVertexNormals.Reset(); // Get Deformed vertices at current frame TArray SkinnedVertices; GetSkinnedVertices(SkeletalMeshComponent, LODIndex, SkinnedVertices); // Get Source Vertices (StaticMesh) TArray SourceVertices; const int32 NumVertices = SourceMeshToDriverMesh.GetSourceVertices(SourceVertices); // Deform Source Vertices with DriverMesh (SkeletalMesh TArray DeformedVertices; TArray DeformedNormals; SourceMeshToDriverMesh.DeformVerticesAndNormals(SkinnedVertices, DeformedVertices, DeformedNormals); // Allocate check(DeformedVertices.Num() == NumVertices && DeformedNormals.Num() == NumVertices); OutVertexDeltas.SetNumUninitialized(NumVertices); OutVertexNormals.SetNumUninitialized(NumVertices); // Transform Vertices and Normals with RootTransform for (int32 VertexIndex = 0; VertexIndex < NumVertices; VertexIndex++) { const FVector3f& SourceVertex = SourceVertices[VertexIndex]; const FVector3f& DeformedVertex = DeformedVertices[VertexIndex]; const FVector3f& DeformedNormal = DeformedNormals[VertexIndex]; // Transform Position and Delta with RootTransform const FVector3f TransformedVertexDelta = ((FVector3f)RootTransform.TransformPosition((FVector)DeformedVertex)) - SourceVertex; const FVector3f TransformedVertexNormal = (FVector3f)RootTransform.TransformVector((FVector)DeformedNormal); OutVertexDeltas[VertexIndex] = TransformedVertexDelta; OutVertexNormals[VertexIndex] = TransformedVertexNormal; } } int32 UAnimToTextureBPLibrary::GetRefBonePositionsAndRotations(const USkeletalMesh* SkeletalMesh, TArray& OutBoneRefPositions, TArray& OutBoneRefRotations) { check(SkeletalMesh); OutBoneRefPositions.Reset(); OutBoneRefRotations.Reset(); // Get Number of RawBones (no virtual) const int32 NumBones = GetNumBones(SkeletalMesh); // Get Raw Ref Bone (no virtual) TArray RefBoneTransforms; GetRefBoneTransforms(SkeletalMesh, RefBoneTransforms); DecomposeTransformations(RefBoneTransforms, OutBoneRefPositions, OutBoneRefRotations); return NumBones; } int32 UAnimToTextureBPLibrary::GetBonePositionsAndRotations(const USkeletalMeshComponent* SkeletalMeshComponent, const TArray& BoneRefPositions, TArray& BonePositions, TArray& BoneRotations) { check(SkeletalMeshComponent); BonePositions.Reset(); BoneRotations.Reset(); // Get Relative Transforms // Note: Size is of Raw bones in SkeletalMesh. These are the original/raw bones of the asset, without Virtual Bones. TArray RefToLocals; SkeletalMeshComponent->CacheRefToLocalMatrices(RefToLocals); const int32 NumBones = RefToLocals.Num(); // check size check(NumBones == BoneRefPositions.Num()); // Get Component Space Transforms // Note returns all transforms, including VirtualBones const TArray& CompSpaceTransforms = SkeletalMeshComponent->GetComponentSpaceTransforms(); check(CompSpaceTransforms.Num() >= RefToLocals.Num()); // Allocate BonePositions.SetNumUninitialized(NumBones); BoneRotations.SetNumUninitialized(NumBones); for (int32 BoneIndex = 0; BoneIndex < NumBones; BoneIndex++) { // Decompose Transformation (ComponentSpace) const FTransform& CompSpaceTransform = CompSpaceTransforms[BoneIndex]; FVector3f BonePosition; FVector4f BoneRotation; DecomposeTransformation(CompSpaceTransform, BonePosition, BoneRotation); // Position Delta (from RefPose) const FVector3f Delta = BonePosition - BoneRefPositions[BoneIndex]; // Decompose Transformation (Relative to RefPose) FVector3f BoneRelativePosition; FVector4f BoneRelativeRotation; const FMatrix RefToLocalMatrix(RefToLocals[BoneIndex]); const FTransform RelativeTransform(RefToLocalMatrix); DecomposeTransformation(RelativeTransform, BoneRelativePosition, BoneRelativeRotation); BonePositions[BoneIndex] = Delta; BoneRotations[BoneIndex] = BoneRelativeRotation; } return NumBones; } void UAnimToTextureBPLibrary::UpdateMaterialInstanceFromDataAsset(const UAnimToTextureDataAsset* DataAsset, UMaterialInstanceConstant* MaterialInstance, const EMaterialParameterAssociation MaterialParameterAssociation) { check(DataAsset); check(MaterialInstance); // Set UVChannel switch (DataAsset->UVChannel) { case 0: UMaterialEditingLibrary::SetMaterialInstanceStaticSwitchParameterValue(MaterialInstance, AnimToTextureParamNames::UseUV0, true, MaterialParameterAssociation); UMaterialEditingLibrary::SetMaterialInstanceStaticSwitchParameterValue(MaterialInstance, AnimToTextureParamNames::UseUV1, false, MaterialParameterAssociation); UMaterialEditingLibrary::SetMaterialInstanceStaticSwitchParameterValue(MaterialInstance, AnimToTextureParamNames::UseUV2, false, MaterialParameterAssociation); UMaterialEditingLibrary::SetMaterialInstanceStaticSwitchParameterValue(MaterialInstance, AnimToTextureParamNames::UseUV3, false, MaterialParameterAssociation); break; case 1: UMaterialEditingLibrary::SetMaterialInstanceStaticSwitchParameterValue(MaterialInstance, AnimToTextureParamNames::UseUV0, false, MaterialParameterAssociation); UMaterialEditingLibrary::SetMaterialInstanceStaticSwitchParameterValue(MaterialInstance, AnimToTextureParamNames::UseUV1, true, MaterialParameterAssociation); UMaterialEditingLibrary::SetMaterialInstanceStaticSwitchParameterValue(MaterialInstance, AnimToTextureParamNames::UseUV2, false, MaterialParameterAssociation); UMaterialEditingLibrary::SetMaterialInstanceStaticSwitchParameterValue(MaterialInstance, AnimToTextureParamNames::UseUV3, false, MaterialParameterAssociation); break; case 2: UMaterialEditingLibrary::SetMaterialInstanceStaticSwitchParameterValue(MaterialInstance, AnimToTextureParamNames::UseUV0, false, MaterialParameterAssociation); UMaterialEditingLibrary::SetMaterialInstanceStaticSwitchParameterValue(MaterialInstance, AnimToTextureParamNames::UseUV1, false, MaterialParameterAssociation); UMaterialEditingLibrary::SetMaterialInstanceStaticSwitchParameterValue(MaterialInstance, AnimToTextureParamNames::UseUV2, true, MaterialParameterAssociation); UMaterialEditingLibrary::SetMaterialInstanceStaticSwitchParameterValue(MaterialInstance, AnimToTextureParamNames::UseUV3, false, MaterialParameterAssociation); break; case 3: UMaterialEditingLibrary::SetMaterialInstanceStaticSwitchParameterValue(MaterialInstance, AnimToTextureParamNames::UseUV0, false, MaterialParameterAssociation); UMaterialEditingLibrary::SetMaterialInstanceStaticSwitchParameterValue(MaterialInstance, AnimToTextureParamNames::UseUV1, false, MaterialParameterAssociation); UMaterialEditingLibrary::SetMaterialInstanceStaticSwitchParameterValue(MaterialInstance, AnimToTextureParamNames::UseUV2, false, MaterialParameterAssociation); UMaterialEditingLibrary::SetMaterialInstanceStaticSwitchParameterValue(MaterialInstance, AnimToTextureParamNames::UseUV3, true, MaterialParameterAssociation); break; default: UMaterialEditingLibrary::SetMaterialInstanceStaticSwitchParameterValue(MaterialInstance, AnimToTextureParamNames::UseUV0, false, MaterialParameterAssociation); UMaterialEditingLibrary::SetMaterialInstanceStaticSwitchParameterValue(MaterialInstance, AnimToTextureParamNames::UseUV1, true, MaterialParameterAssociation); UMaterialEditingLibrary::SetMaterialInstanceStaticSwitchParameterValue(MaterialInstance, AnimToTextureParamNames::UseUV2, false, MaterialParameterAssociation); UMaterialEditingLibrary::SetMaterialInstanceStaticSwitchParameterValue(MaterialInstance, AnimToTextureParamNames::UseUV3, false, MaterialParameterAssociation); break; } // Update Vertex Params if (DataAsset->Mode == EAnimToTextureMode::Vertex) { UMaterialEditingLibrary::SetMaterialInstanceVectorParameterValue(MaterialInstance, AnimToTextureParamNames::MinBBox, FLinearColor(DataAsset->VertexMinBBox), MaterialParameterAssociation); UMaterialEditingLibrary::SetMaterialInstanceVectorParameterValue(MaterialInstance, AnimToTextureParamNames::SizeBBox, FLinearColor(DataAsset->VertexSizeBBox), MaterialParameterAssociation); UMaterialEditingLibrary::SetMaterialInstanceScalarParameterValue(MaterialInstance, AnimToTextureParamNames::RowsPerFrame, DataAsset->VertexRowsPerFrame, MaterialParameterAssociation); UMaterialEditingLibrary::SetMaterialInstanceTextureParameterValue(MaterialInstance, AnimToTextureParamNames::VertexPositionTexture, DataAsset->GetVertexPositionTexture(), MaterialParameterAssociation); UMaterialEditingLibrary::SetMaterialInstanceTextureParameterValue(MaterialInstance, AnimToTextureParamNames::VertexNormalTexture, DataAsset->GetVertexNormalTexture(), MaterialParameterAssociation); } // Update Bone Params else if (DataAsset->Mode == EAnimToTextureMode::Bone) { UMaterialEditingLibrary::SetMaterialInstanceScalarParameterValue(MaterialInstance, AnimToTextureParamNames::NumBones, DataAsset->NumBones, MaterialParameterAssociation); UMaterialEditingLibrary::SetMaterialInstanceVectorParameterValue(MaterialInstance, AnimToTextureParamNames::MinBBox, FLinearColor(DataAsset->BoneMinBBox), MaterialParameterAssociation); UMaterialEditingLibrary::SetMaterialInstanceVectorParameterValue(MaterialInstance, AnimToTextureParamNames::SizeBBox, FLinearColor(DataAsset->BoneSizeBBox), MaterialParameterAssociation); UMaterialEditingLibrary::SetMaterialInstanceScalarParameterValue(MaterialInstance, AnimToTextureParamNames::RowsPerFrame, DataAsset->BoneRowsPerFrame, MaterialParameterAssociation); UMaterialEditingLibrary::SetMaterialInstanceScalarParameterValue(MaterialInstance, AnimToTextureParamNames::BoneWeightRowsPerFrame, DataAsset->BoneWeightRowsPerFrame, MaterialParameterAssociation); UMaterialEditingLibrary::SetMaterialInstanceTextureParameterValue(MaterialInstance, AnimToTextureParamNames::BonePositionTexture, DataAsset->GetBonePositionTexture(), MaterialParameterAssociation); UMaterialEditingLibrary::SetMaterialInstanceTextureParameterValue(MaterialInstance, AnimToTextureParamNames::BoneRotationTexture, DataAsset->GetBoneRotationTexture(), MaterialParameterAssociation); UMaterialEditingLibrary::SetMaterialInstanceTextureParameterValue(MaterialInstance, AnimToTextureParamNames::BoneWeightsTexture, DataAsset->GetBoneWeightTexture(), MaterialParameterAssociation); // Num Influences switch (DataAsset->NumBoneInfluences) { case EAnimToTextureNumBoneInfluences::One: UMaterialEditingLibrary::SetMaterialInstanceStaticSwitchParameterValue(MaterialInstance, AnimToTextureParamNames::UseTwoInfluences, false, MaterialParameterAssociation); UMaterialEditingLibrary::SetMaterialInstanceStaticSwitchParameterValue(MaterialInstance, AnimToTextureParamNames::UseFourInfluences, false, MaterialParameterAssociation); break; case EAnimToTextureNumBoneInfluences::Two: UMaterialEditingLibrary::SetMaterialInstanceStaticSwitchParameterValue(MaterialInstance, AnimToTextureParamNames::UseTwoInfluences, true, MaterialParameterAssociation); UMaterialEditingLibrary::SetMaterialInstanceStaticSwitchParameterValue(MaterialInstance, AnimToTextureParamNames::UseFourInfluences, false, MaterialParameterAssociation); break; case EAnimToTextureNumBoneInfluences::Four: UMaterialEditingLibrary::SetMaterialInstanceStaticSwitchParameterValue(MaterialInstance, AnimToTextureParamNames::UseTwoInfluences, false, MaterialParameterAssociation); UMaterialEditingLibrary::SetMaterialInstanceStaticSwitchParameterValue(MaterialInstance, AnimToTextureParamNames::UseFourInfluences, true, MaterialParameterAssociation); break; } } // AutoPlay UMaterialEditingLibrary::SetMaterialInstanceStaticSwitchParameterValue(MaterialInstance, AnimToTextureParamNames::AutoPlay, DataAsset->bAutoPlay, MaterialParameterAssociation); if (DataAsset->bAutoPlay) { if (DataAsset->Animations.IsValidIndex(DataAsset->AnimationIndex)) { UMaterialEditingLibrary::SetMaterialInstanceScalarParameterValue(MaterialInstance, AnimToTextureParamNames::StartFrame, DataAsset->Animations[DataAsset->AnimationIndex].StartFrame, MaterialParameterAssociation); UMaterialEditingLibrary::SetMaterialInstanceScalarParameterValue(MaterialInstance, AnimToTextureParamNames::EndFrame, DataAsset->Animations[DataAsset->AnimationIndex].EndFrame, MaterialParameterAssociation); } else { UE_LOG(LogAnimToTextureEditor, Warning, TEXT("Invalid AnimationIndex: %i"), DataAsset->AnimationIndex); } } else { if (DataAsset->Frame >= 0 && DataAsset->Frame < DataAsset->NumFrames) { UMaterialEditingLibrary::SetMaterialInstanceScalarParameterValue(MaterialInstance, AnimToTextureParamNames::Frame, DataAsset->Frame, MaterialParameterAssociation); } else { UE_LOG(LogAnimToTextureEditor, Warning, TEXT("Frame out of range: %i"), DataAsset->Frame); } } // NumFrames UMaterialEditingLibrary::SetMaterialInstanceScalarParameterValue(MaterialInstance, AnimToTextureParamNames::NumFrames, DataAsset->NumFrames, MaterialParameterAssociation); // SampleRate UMaterialEditingLibrary::SetMaterialInstanceScalarParameterValue(MaterialInstance, AnimToTextureParamNames::SampleRate, DataAsset->SampleRate, MaterialParameterAssociation); // Update Material UMaterialEditingLibrary::UpdateMaterialInstance(MaterialInstance); // Rebuild Material UMaterialEditingLibrary::RebuildMaterialInstanceEditors(MaterialInstance->GetMaterial()); // Set Preview Mesh if (DataAsset->GetStaticMesh()) { MaterialInstance->PreviewMesh = DataAsset->GetStaticMesh(); } MaterialInstance->MarkPackageDirty(); } bool UAnimToTextureBPLibrary::SetLightMapIndex(UStaticMesh* StaticMesh, const int32 LODIndex, const int32 LightmapIndex, bool bGenerateLightmapUVs) { check(StaticMesh); if (!StaticMesh->IsSourceModelValid(LODIndex)) { return false; } for (int32 Index=0; Index < LightmapIndex; Index++) { if (LightmapIndex > StaticMesh->GetNumUVChannels(LODIndex)) { StaticMesh->AddUVChannel(LODIndex); } } // Set Build Settings FStaticMeshSourceModel& SourceModel = StaticMesh->GetSourceModel(LODIndex); SourceModel.BuildSettings.bGenerateLightmapUVs = bGenerateLightmapUVs; SourceModel.BuildSettings.DstLightmapIndex = LightmapIndex; StaticMesh->SetLightMapCoordinateIndex(LightmapIndex); // Build Mesh StaticMesh->Build(false); StaticMesh->PostEditChange(); StaticMesh->MarkPackageDirty(); return true; } UStaticMesh* UAnimToTextureBPLibrary::ConvertSkeletalMeshToStaticMesh(USkeletalMesh* SkeletalMesh, const FString PackageName, const int32 LODIndex) { check(SkeletalMesh); if (PackageName.IsEmpty() || !FPackageName::IsValidObjectPath(PackageName)) { return nullptr; } if (!SkeletalMesh->IsValidLODIndex(LODIndex)) { UE_LOG(LogAnimToTextureEditor, Warning, TEXT("Invalid LODIndex: %i"), LODIndex); return nullptr; } // Create Temp Actor check(GEditor); UWorld* World = GEditor->GetEditorWorldContext().World(); check(World); AActor* Actor = World->SpawnActor(); check(Actor); // Create Temp SkeletalMesh Component USkeletalMeshComponent* MeshComponent = NewObject(Actor); MeshComponent->RegisterComponent(); MeshComponent->SetSkeletalMesh(SkeletalMesh); TArray MeshComponents = { MeshComponent }; UStaticMesh* OutStaticMesh = nullptr; bool bGeneratedCorrectly = true; // Create New StaticMesh if (!FPackageName::DoesPackageExist(PackageName)) { IMeshUtilities& MeshUtilities = FModuleManager::Get().LoadModuleChecked("MeshUtilities"); OutStaticMesh = MeshUtilities.ConvertMeshesToStaticMesh(MeshComponents, FTransform::Identity, PackageName); } // Update Existing StaticMesh else { // Load Existing Mesh OutStaticMesh = LoadObject(nullptr, *PackageName); } if (OutStaticMesh) { // Create Temp Package. // because UPackage* TransientPackage = GetTransientPackage(); // Create Temp Mesh. IMeshUtilities& MeshUtilities = FModuleManager::Get().LoadModuleChecked("MeshUtilities"); UStaticMesh* TempMesh = MeshUtilities.ConvertMeshesToStaticMesh(MeshComponents, FTransform::Identity, TransientPackage->GetPathName()); // make sure transactional flag is on TempMesh->SetFlags(RF_Transactional); // Copy All LODs if (LODIndex < 0) { const int32 NumSourceModels = TempMesh->GetNumSourceModels(); OutStaticMesh->SetNumSourceModels(NumSourceModels); for (int32 Index = 0; Index < NumSourceModels; ++Index) { // Get RawMesh FRawMesh RawMesh; TempMesh->GetSourceModel(Index).LoadRawMesh(RawMesh); // Set RawMesh OutStaticMesh->GetSourceModel(Index).SaveRawMesh(RawMesh); }; } // Copy Single LOD else { if (LODIndex >= TempMesh->GetNumSourceModels()) { UE_LOG(LogAnimToTextureEditor, Warning, TEXT("Invalid Source Model Index: %i"), LODIndex); bGeneratedCorrectly = false; } else { OutStaticMesh->SetNumSourceModels(1); // Get RawMesh FRawMesh RawMesh; TempMesh->GetSourceModel(LODIndex).LoadRawMesh(RawMesh); // Set RawMesh OutStaticMesh->GetSourceModel(0).SaveRawMesh(RawMesh); } } // Copy Materials const TArray& Materials = TempMesh->GetStaticMaterials(); OutStaticMesh->SetStaticMaterials(Materials); // Done TArray OutErrors; OutStaticMesh->Build(true, &OutErrors); OutStaticMesh->MarkPackageDirty(); } // Destroy Temp Component and Actor MeshComponent->UnregisterComponent(); MeshComponent->DestroyComponent(); Actor->Destroy(); return bGeneratedCorrectly ? OutStaticMesh : nullptr; } void UAnimToTextureBPLibrary::NormalizeVertexData( const TArray& Deltas, const TArray& Normals, FVector3f& OutMinBBox, FVector3f& OutSizeBBox, TArray& OutNormalizedDeltas, TArray& OutNormalizedNormals) { check(Deltas.Num() == Normals.Num()); // --------------------------------------------------------------------------- // Compute Bounding Box // OutMinBBox = { TNumericLimits::Max(), TNumericLimits::Max(), TNumericLimits::Max() }; FVector3f MaxBBox = { TNumericLimits::Min(), TNumericLimits::Min(), TNumericLimits::Min() }; for (const FVector3f& Delta: Deltas) { // Find Min/Max BoundingBox OutMinBBox.X = FMath::Min(Delta.X, OutMinBBox.X); OutMinBBox.Y = FMath::Min(Delta.Y, OutMinBBox.Y); OutMinBBox.Z = FMath::Min(Delta.Z, OutMinBBox.Z); MaxBBox.X = FMath::Max(Delta.X, MaxBBox.X); MaxBBox.Y = FMath::Max(Delta.Y, MaxBBox.Y); MaxBBox.Z = FMath::Max(Delta.Z, MaxBBox.Z); } OutSizeBBox = MaxBBox - OutMinBBox; // --------------------------------------------------------------------------- // Normalize Vertex Position Deltas // Basically we want all deltas to be between [0, 1] // Compute Normalization Factor per-axis. const FVector3f NormFactor = { 1.f / static_cast(OutSizeBBox.X), 1.f / static_cast(OutSizeBBox.Y), 1.f / static_cast(OutSizeBBox.Z) }; OutNormalizedDeltas.SetNumUninitialized(Deltas.Num()); for (int32 Index = 0; Index < Deltas.Num(); ++Index) { OutNormalizedDeltas[Index] = (Deltas[Index] - OutMinBBox) * NormFactor; } // --------------------------------------------------------------------------- // Normalize Vertex Normals // And move them to [0, 1] OutNormalizedNormals.SetNumUninitialized(Normals.Num()); for (int32 Index = 0; Index < Normals.Num(); ++Index) { OutNormalizedNormals[Index] = (Normals[Index].GetSafeNormal() + FVector3f::OneVector) * 0.5f; } } void UAnimToTextureBPLibrary::NormalizeBoneData( const TArray& Positions, const TArray& Rotations, FVector3f& OutMinBBox, FVector3f& OutSizeBBox, TArray& OutNormalizedPositions, TArray& OutNormalizedRotations) { check(Positions.Num() == Rotations.Num()); // --------------------------------------------------------------------------- // Compute Position Bounding Box // OutMinBBox = { TNumericLimits::Max(), TNumericLimits::Max(), TNumericLimits::Max() }; FVector3f MaxBBox = { TNumericLimits::Min(), TNumericLimits::Min(), TNumericLimits::Min() }; for (const FVector3f& Position : Positions) { // Find Min/Max BoundingBox OutMinBBox.X = FMath::Min(Position.X, OutMinBBox.X); OutMinBBox.Y = FMath::Min(Position.Y, OutMinBBox.Y); OutMinBBox.Z = FMath::Min(Position.Z, OutMinBBox.Z); MaxBBox.X = FMath::Max(Position.X, MaxBBox.X); MaxBBox.Y = FMath::Max(Position.Y, MaxBBox.Y); MaxBBox.Z = FMath::Max(Position.Z, MaxBBox.Z); } OutSizeBBox = MaxBBox - OutMinBBox; // --------------------------------------------------------------------------- // Normalize Bone Position. // Basically we want all positions to be between [0, 1] // Compute Normalization Factor per-axis. const FVector3f NormFactor = { 1.f / static_cast(OutSizeBBox.X), 1.f / static_cast(OutSizeBBox.Y), 1.f / static_cast(OutSizeBBox.Z) }; OutNormalizedPositions.SetNumUninitialized(Positions.Num()); for (int32 Index = 0; Index < Positions.Num(); ++Index) { OutNormalizedPositions[Index] = (Positions[Index] - OutMinBBox) * NormFactor; } // --------------------------------------------------------------------------- // Normalize Rotations // And move them to [0, 1] OutNormalizedRotations.SetNumUninitialized(Rotations.Num()); for (int32 Index = 0; Index < Rotations.Num(); ++Index) { const FVector4f Axis = Rotations[Index]; const float Angle = Rotations[Index].W; // Angle are returned in radians and they go from [0-pi*2] OutNormalizedRotations[Index] = (Axis.GetSafeNormal() + FVector3f::OneVector) * 0.5f; OutNormalizedRotations[Index].W = Angle / (PI * 2.f); } } bool UAnimToTextureBPLibrary::CreateUVChannel( UStaticMesh* StaticMesh, const int32 LODIndex, const int32 UVChannelIndex, const int32 Height, const int32 Width) { check(StaticMesh); if (!StaticMesh->IsSourceModelValid(LODIndex)) { return false; } // ---------------------------------------------------------------------------- // Get Mesh Description. // This is needed for Inserting UVChannel FMeshDescription* MeshDescription = StaticMesh->GetMeshDescription(LODIndex); check(MeshDescription); // Add New UVChannel. if (UVChannelIndex == StaticMesh->GetNumUVChannels(LODIndex)) { if (!StaticMesh->InsertUVChannel(LODIndex, UVChannelIndex)) { UE_LOG(LogAnimToTextureEditor, Warning, TEXT("Unable to Add UVChannel")); return false; } } else if (UVChannelIndex > StaticMesh->GetNumUVChannels(LODIndex)) { UE_LOG(LogAnimToTextureEditor, Warning, TEXT("UVChannel: %i Out of Range. Number of existing UVChannels: %i"), UVChannelIndex, StaticMesh->GetNumUVChannels(LODIndex)); return false; } // ----------------------------------------------------------------------------- TMap TexCoords; for (const FVertexInstanceID VertexInstanceID : MeshDescription->VertexInstances().GetElementIDs()) { const FVertexID VertexID = MeshDescription->GetVertexInstanceVertex(VertexInstanceID); const int32 VertexIndex = VertexID.GetValue(); float U = (0.5f / (float)Width) + (VertexIndex % Width) / (float)Width; float V = (0.5f / (float)Height) + (VertexIndex / Width) / (float)Height; TexCoords.Add(VertexInstanceID, FVector2D(U, V)); } // Set Full Precision UVs SetFullPrecisionUVs(StaticMesh, LODIndex, true); // Set UVs if (StaticMesh->SetUVChannel(LODIndex, UVChannelIndex, TexCoords)) { return true; } else { UE_LOG(LogAnimToTextureEditor, Warning, TEXT("Unable to Set UVChannel: %i. TexCoords: %i"), UVChannelIndex, TexCoords.Num()); return false; } } bool UAnimToTextureBPLibrary::FindBestResolution( const int32 NumFrames, const int32 NumElements, int32& OutHeight, int32& OutWidth, int32& OutRowsPerFrame, const int32 MaxHeight, const int32 MaxWidth, bool bEnforcePowerOfTwo) { if (bEnforcePowerOfTwo) { OutWidth = 2; while (OutWidth < NumElements && OutWidth < MaxWidth) { OutWidth *= 2; } OutRowsPerFrame = FMath::CeilToInt(NumElements / (float)OutWidth); const int32 TargetHeight = NumFrames * OutRowsPerFrame; OutHeight = 2; while (OutHeight < TargetHeight) { OutHeight *= 2; } } else { OutRowsPerFrame = FMath::CeilToInt(NumElements / (float)MaxWidth); OutWidth = FMath::CeilToInt(NumElements / (float)OutRowsPerFrame); OutHeight = NumFrames * OutRowsPerFrame; } const bool bValidResolution = OutWidth <= MaxWidth && OutHeight <= MaxHeight; return bValidResolution; }; void UAnimToTextureBPLibrary::SetFullPrecisionUVs(UStaticMesh* StaticMesh, int32 LODIndex, bool bFullPrecision) { check(StaticMesh); if (StaticMesh->IsSourceModelValid(LODIndex)) { FStaticMeshSourceModel& SourceModel = StaticMesh->GetSourceModel(LODIndex); SourceModel.BuildSettings.bUseFullPrecisionUVs = bFullPrecision; } } void UAnimToTextureBPLibrary::SetBoundsExtensions(UStaticMesh* StaticMesh, const FVector& MinBBox, const FVector& SizeBBox) { check(StaticMesh); // Calculate MaxBBox const FVector MaxBBox = SizeBBox + MinBBox; // Reset current extension bounds const FVector PositiveBoundsExtension = StaticMesh->GetPositiveBoundsExtension(); const FVector NegativeBoundsExtension = StaticMesh->GetNegativeBoundsExtension(); // Get current BoundingBox including extensions FBox BoundingBox = StaticMesh->GetBoundingBox(); // Remove extensions from BoundingBox BoundingBox.Max -= PositiveBoundsExtension; BoundingBox.Min += NegativeBoundsExtension; // Calculate New BoundingBox FVector NewMaxBBox( FMath::Max(BoundingBox.Max.X, MaxBBox.X), FMath::Max(BoundingBox.Max.Y, MaxBBox.Y), FMath::Max(BoundingBox.Max.Z, MaxBBox.Z) ); FVector NewMinBBox( FMath::Min(BoundingBox.Min.X, MinBBox.X), FMath::Min(BoundingBox.Min.Y, MinBBox.Y), FMath::Min(BoundingBox.Min.Z, MinBBox.Z) ); // Calculate New Extensions FVector NewPositiveBoundsExtension = NewMaxBBox - BoundingBox.Max; FVector NewNegativeBoundsExtension = BoundingBox.Min - NewMinBBox; // Update StaticMesh StaticMesh->SetPositiveBoundsExtension(NewPositiveBoundsExtension); StaticMesh->SetNegativeBoundsExtension(NewNegativeBoundsExtension); StaticMesh->CalculateExtendedBounds(); } #undef LOCTEXT_NAMESPACE