// Copyright Epic Games, Inc. All Rights Reserved. #pragma once #include "AnimToTextureSkeletalMesh.h" #include "CoreMinimal.h" #include "TextureResource.h" #include "Engine/Texture.h" #include "Engine/Texture2D.h" namespace AnimToTexture_Private { struct FVector4u16 { uint16 X; uint16 Y; uint16 Z; uint16 W; }; struct FLowPrecision { using ColorType = FColor; static constexpr EPixelFormat PixelFormat = EPixelFormat::PF_B8G8R8A8; static constexpr ETextureSourceFormat TextureSourceFormat = ETextureSourceFormat::TSF_BGRA8; static constexpr TextureCompressionSettings CompressionSettings = TextureCompressionSettings::TC_VectorDisplacementmap; static constexpr ColorType DefaultColor = FColor(0, 0, 0, 0); }; struct FHighPrecision { using ColorType = FVector4u16; static constexpr EPixelFormat PixelFormat = EPixelFormat::PF_R16G16B16A16_UNORM; static constexpr ETextureSourceFormat TextureSourceFormat = ETextureSourceFormat::TSF_RGBA16; static constexpr TextureCompressionSettings CompressionSettings = TextureCompressionSettings::TC_HDR; static constexpr ColorType DefaultColor = { 0, 0, 0, 0 }; }; /** Writes list of vectors into texture * Note: They must be pre-normalized. */ template bool WriteVectorsToTexture(const TArray& Vectors, const int32 NumFrames, const int32 RowsPerFrame, const int32 Height, const int32 Width, UTexture2D* Texture); /* Writes list of skinweights into texture. * The SkinWeights data is already in uint8 & uint16 format, no need for normalizing it. */ template bool WriteSkinWeightsToTexture(const TArray& SkinWeights, const int32 NumBones, const int32 RowsPerFrame, const int32 Height, const int32 Width, UTexture2D* Texture); /* Helper utility for writing 8 or 16 bits textures */ template bool WriteToTexture(UTexture2D* Texture, const uint32 Height, const uint32 Width, const TArray& Data); template void VectorToColor(const V& Vector, C& Color); /* Decomposes Transform in Translation and AxisAndAngle */ void DecomposeTransformation(const FTransform& Transform, FVector3f& OutTranslation, FVector4f& OutRotation); void DecomposeTransformations(const TArray& Transforms, TArray& OutTranslations, TArray& OutRotations); } // end namespace AnimToTexture_Private // ---------------------------------------------------------------------------- // LowPrecision template<> FORCEINLINE void AnimToTexture_Private::VectorToColor(const FVector3f& Vector, FColor& Color) { const float ClampedX = FMath::Clamp(Vector.X, 0.f, 1.f); const float ClampedY = FMath::Clamp(Vector.Y, 0.f, 1.f); const float ClampedZ = FMath::Clamp(Vector.Z, 0.f, 1.f); Color.R = (uint8)FMath::RoundToInt(ClampedX * 255.f); Color.G = (uint8)FMath::RoundToInt(ClampedY * 255.f); Color.B = (uint8)FMath::RoundToInt(ClampedZ * 255.f); Color.A = TNumericLimits::Max(); } // LowPrecision template<> FORCEINLINE void AnimToTexture_Private::VectorToColor(const FVector4f& Vector, FColor& Color) { const float ClampedX = FMath::Clamp(Vector.X, 0.f, 1.f); const float ClampedY = FMath::Clamp(Vector.Y, 0.f, 1.f); const float ClampedZ = FMath::Clamp(Vector.Z, 0.f, 1.f); const float ClampedW = FMath::Clamp(Vector.W, 0.f, 1.f); Color.R = (uint8)FMath::RoundToInt(ClampedX * 255.f); Color.G = (uint8)FMath::RoundToInt(ClampedY * 255.f); Color.B = (uint8)FMath::RoundToInt(ClampedZ * 255.f); Color.A = (uint8)FMath::RoundToInt(ClampedW * 255.f); } // HighPrecision template<> FORCEINLINE void AnimToTexture_Private::VectorToColor(const FVector3f& Vector, FVector4u16& Color) { Color.X = FMath::RoundToInt(FMath::Clamp(Vector.X, 0.f, 1.f) * TNumericLimits::Max()); Color.Y = FMath::RoundToInt(FMath::Clamp(Vector.Y, 0.f, 1.f) * TNumericLimits::Max()); Color.Z = FMath::RoundToInt(FMath::Clamp(Vector.Z, 0.f, 1.f) * TNumericLimits::Max()); Color.W = TNumericLimits::Max(); } // HighPrecision template<> FORCEINLINE void AnimToTexture_Private::VectorToColor(const FVector4f& Vector, FVector4u16& Color) { Color.X = FMath::RoundToInt(FMath::Clamp(Vector.X, 0.f, 1.f) * TNumericLimits::Max()); Color.Y = FMath::RoundToInt(FMath::Clamp(Vector.Y, 0.f, 1.f) * TNumericLimits::Max()); Color.Z = FMath::RoundToInt(FMath::Clamp(Vector.Z, 0.f, 1.f) * TNumericLimits::Max()); Color.W = FMath::RoundToInt(FMath::Clamp(Vector.W, 0.f, 1.f) * TNumericLimits::Max()); } template FORCEINLINE_DEBUGGABLE bool AnimToTexture_Private::WriteVectorsToTexture(const TArray& Vectors, const int32 NumFrames, const int32 RowsPerFrame, const int32 Height, const int32 Width, UTexture2D* Texture) { if (!Texture || !NumFrames) { return false; } // NumElements Per-Frame const int32 NumElements = Vectors.Num() / NumFrames; // Allocate PixelData. TArray Pixels; Pixels.Init(TextureSettings::DefaultColor, Height * Width); // Fillout Frame Data for (int32 Frame = 0; Frame < NumFrames; Frame++) { const int32 BlockStart = RowsPerFrame * Width * Frame; // Set Data. for (int32 Index = 0; Index < NumElements; Index++) { const V& Vector = Vectors[NumElements * Frame + Index]; typename TextureSettings::ColorType& Pixel = Pixels[BlockStart + Index]; VectorToColor(Vector, Pixel); } } // Write to Texture return WriteToTexture(Texture, Height, Width, Pixels); } template FORCEINLINE_DEBUGGABLE bool AnimToTexture_Private::WriteSkinWeightsToTexture(const TArray& SkinWeights, const int32 NumBones, const int32 RowsPerFrame, const int32 Height, const int32 Width, UTexture2D* Texture) { check(Texture); const int32 NumVertices = SkinWeights.Num(); // Allocate PixelData. TArray Pixels; Pixels.Init(TextureSettings::DefaultColor, Height * Width); for (int32 VertexIndex = 0; VertexIndex < NumVertices; ++VertexIndex) { const VertexSkinWeightFour& VertexSkinWeight = SkinWeights[VertexIndex]; // Normalize BoneIndices const FVector4f BoneIndices( (float)VertexSkinWeight.MeshBoneIndices[0] / float(NumBones), (float)VertexSkinWeight.MeshBoneIndices[1] / float(NumBones), (float)VertexSkinWeight.MeshBoneIndices[2] / float(NumBones), (float)VertexSkinWeight.MeshBoneIndices[3] / float(NumBones)); // Normalize BoneWeights const FVector4f BoneWeights( (float)VertexSkinWeight.BoneWeights[0] / 255.f, (float)VertexSkinWeight.BoneWeights[1] / 255.f, (float)VertexSkinWeight.BoneWeights[2] / 255.f, (float)VertexSkinWeight.BoneWeights[3] / 255.f); // Write BoneIndex { typename TextureSettings::ColorType& Pixel = Pixels[VertexIndex]; VectorToColor(BoneIndices, Pixel); } // Write BoneWeight { typename TextureSettings::ColorType& Pixel = Pixels[RowsPerFrame * Width + VertexIndex]; VectorToColor(BoneWeights, Pixel); } }; // Write to Texture return WriteToTexture(Texture, Height, Width, Pixels); } template FORCEINLINE_DEBUGGABLE bool AnimToTexture_Private::WriteToTexture( UTexture2D* Texture, const uint32 Height, const uint32 Width, const TArray& Pixels) { check(Texture); // TODO FIX ME: this is the wrong way to build a UTexture // instead fill out an FImage // and use Texture.Source.Init() from FImage // there is no reason to be touching the PlatformData or BulkData Texture->PreEditChange(nullptr); // ------------------------------------------------------------------------ // Get Texture Platform FTexturePlatformData* PlatformData = Texture->GetPlatformData(); if (!PlatformData) { PlatformData = new FTexturePlatformData(); Texture->SetPlatformData(PlatformData); } PlatformData->SizeX = Width; PlatformData->SizeY = Height; PlatformData->SetNumSlices(1); PlatformData->PixelFormat = TextureSettings::PixelFormat; // ------------------------------------------------------------------------ // Get First MipMap // FTexture2DMipMap* Mip; if (PlatformData->Mips.IsEmpty()) { Mip = new FTexture2DMipMap(0, 0); PlatformData->Mips.Add(Mip); } else { Mip = &PlatformData->Mips[0]; } Mip->SizeX = Width; Mip->SizeY = Height; Mip->SizeZ = 1; // ------------------------------------------------------------------------ // Lock the Mipmap data so it can be modified Mip->BulkData.Lock(LOCK_READ_WRITE); // Reallocate MipMap uint8* TextureData = (uint8*)Mip->BulkData.Realloc(Width * Height * sizeof(typename TextureSettings::ColorType)); // Copy the pixel data into the Texture data const uint8* PixelsData = (uint8*)Pixels.GetData(); FMemory::Memcpy(TextureData, PixelsData, Width * Height * sizeof(typename TextureSettings::ColorType)); // Unlock data Mip->BulkData.Unlock(); // Initialize a new texture Texture->Source.Init(Width, Height, 1, 1, TextureSettings::TextureSourceFormat, PixelsData); // Set parameters Texture->SRGB = 0; Texture->Filter = TextureFilter::TF_Nearest; Texture->CompressionSettings = TextureSettings::CompressionSettings; Texture->MipGenSettings = TextureMipGenSettings::TMGS_NoMipmaps; // Update and Mark to Save. Texture->PostEditChange(); return true; }