// Copyright Epic Games, Inc. All Rights Reserved. #include "MeshUtilities.h" #include "MeshUtilitiesPrivate.h" #include "Components/StaticMeshComponent.h" #include "Engine/StaticMesh.h" #include "Materials/Material.h" #include "RawMesh.h" #include "StaticMeshResources.h" #include "DistanceFieldAtlas.h" #include "MeshRepresentationCommon.h" #include "Async/ParallelFor.h" //@todo - implement required vector intrinsics for other implementations #if PLATFORM_ENABLE_VECTORINTRINSICS class FMeshDistanceFieldAsyncTask { public: FMeshDistanceFieldAsyncTask( TkDOPTree* InkDopTree, bool bInUseEmbree, RTCScene InEmbreeScene, const TArray* InSampleDirections, FBox InVolumeBounds, FIntVector InVolumeDimensions, float InVolumeMaxDistance, int32 InZIndex, TArray* DistanceFieldVolume) : kDopTree(InkDopTree), bUseEmbree(bInUseEmbree), EmbreeScene(InEmbreeScene), SampleDirections(InSampleDirections), VolumeBounds(InVolumeBounds), VolumeDimensions(InVolumeDimensions), VolumeMaxDistance(InVolumeMaxDistance), ZIndex(InZIndex), OutDistanceFieldVolume(DistanceFieldVolume), bNegativeAtBorder(false) {} void DoWork(); bool WasNegativeAtBorder() const { return bNegativeAtBorder; } private: // Readonly inputs TkDOPTree* kDopTree; bool bUseEmbree; RTCScene EmbreeScene; const TArray* SampleDirections; FBox VolumeBounds; FIntVector VolumeDimensions; float VolumeMaxDistance; int32 ZIndex; // Output TArray* OutDistanceFieldVolume; bool bNegativeAtBorder; }; void FMeshDistanceFieldAsyncTask::DoWork() { TRACE_CPUPROFILER_EVENT_SCOPE(FMeshDistanceFieldAsyncTask::DoWork); FMeshBuildDataProvider kDOPDataProvider(*kDopTree); const FVector DistanceFieldVoxelSize(VolumeBounds.GetSize() / FVector(VolumeDimensions.X, VolumeDimensions.Y, VolumeDimensions.Z)); const float VoxelDiameterSqr = DistanceFieldVoxelSize.SizeSquared(); for (int32 YIndex = 0; YIndex < VolumeDimensions.Y; YIndex++) { for (int32 XIndex = 0; XIndex < VolumeDimensions.X; XIndex++) { const FVector VoxelPosition = FVector(XIndex + .5f, YIndex + .5f, ZIndex + .5f) * DistanceFieldVoxelSize + VolumeBounds.Min; const int32 Index = (ZIndex * VolumeDimensions.Y * VolumeDimensions.X + YIndex * VolumeDimensions.X + XIndex); float MinDistance = VolumeMaxDistance; int32 Hit = 0; int32 HitBack = 0; for (int32 SampleIndex = 0; SampleIndex < SampleDirections->Num(); SampleIndex++) { const FVector UnitRayDirection = (*SampleDirections)[SampleIndex]; const FVector EndPosition = VoxelPosition + UnitRayDirection * VolumeMaxDistance; if (FMath::LineBoxIntersection(VolumeBounds, VoxelPosition, EndPosition, UnitRayDirection)) { #if USE_EMBREE if (bUseEmbree) { FEmbreeRay EmbreeRay; FVector RayDirection = EndPosition - VoxelPosition; EmbreeRay.org[0] = VoxelPosition.X; EmbreeRay.org[1] = VoxelPosition.Y; EmbreeRay.org[2] = VoxelPosition.Z; EmbreeRay.dir[0] = RayDirection.X; EmbreeRay.dir[1] = RayDirection.Y; EmbreeRay.dir[2] = RayDirection.Z; EmbreeRay.tnear = 0; EmbreeRay.tfar = 1.0f; rtcIntersect(EmbreeScene, EmbreeRay); if (EmbreeRay.geomID != -1 && EmbreeRay.primID != -1) { Hit++; const FVector HitNormal = EmbreeRay.GetHitNormal(); if (FVector::DotProduct(UnitRayDirection, HitNormal) > 0 && !EmbreeRay.IsHitTwoSided()) { HitBack++; } const float CurrentDistance = VolumeMaxDistance * EmbreeRay.tfar; if (CurrentDistance < MinDistance) { MinDistance = CurrentDistance; } } } else #endif { FkHitResult Result; TkDOPLineCollisionCheck kDOPCheck( VoxelPosition, EndPosition, true, kDOPDataProvider, &Result); bool bHit = kDopTree->LineCheck(kDOPCheck); if (bHit) { Hit++; const FVector HitNormal = kDOPCheck.GetHitNormal(); if (FVector::DotProduct(UnitRayDirection, HitNormal) > 0 // MaterialIndex on the build triangles was set to 1 if two-sided, or 0 if one-sided && kDOPCheck.Result->Item == 0) { HitBack++; } const float CurrentDistance = VolumeMaxDistance * Result.Time; if (CurrentDistance < MinDistance) { MinDistance = CurrentDistance; } } } } } const float UnsignedDistance = MinDistance; // Consider this voxel 'inside' an object if more than 40% of the rays hit back faces if (Hit > 0 && HitBack > SampleDirections->Num() * .4f) { MinDistance *= -1; } // If we are very close to a surface and nearly all of our rays hit backfaces, treat as inside // This is important for one sided planes if (FMath::Square(UnsignedDistance) < VoxelDiameterSqr && HitBack > .95f * Hit) { MinDistance = -UnsignedDistance; } MinDistance = FMath::Min(MinDistance, VolumeMaxDistance); const float VolumeSpaceDistance = MinDistance / VolumeBounds.GetExtent().GetMax(); if (MinDistance < 0 && (XIndex == 0 || XIndex == VolumeDimensions.X - 1 || YIndex == 0 || YIndex == VolumeDimensions.Y - 1 || ZIndex == 0 || ZIndex == VolumeDimensions.Z - 1)) { bNegativeAtBorder = true; } (*OutDistanceFieldVolume)[Index] = VolumeSpaceDistance; } } } void FMeshUtilities::GenerateSignedDistanceFieldVolumeData( FString MeshName, const FSourceMeshDataForDerivedDataTask& SourceMeshData, const FStaticMeshLODResources& LODModel, class FQueuedThreadPool& ThreadPool, const TArray& MaterialBlendModes, const FBoxSphereBounds& Bounds, float DistanceFieldResolutionScale, bool bGenerateAsIfTwoSided, FDistanceFieldVolumeData& OutData) { TRACE_CPUPROFILER_EVENT_SCOPE(FMeshUtilities::GenerateSignedDistanceFieldVolumeData); if (DistanceFieldResolutionScale > 0) { const double StartTime = FPlatformTime::Seconds(); FEmbreeScene EmbreeScene; MeshRepresentation::SetupEmbreeScene(MeshName, SourceMeshData, LODModel, MaterialBlendModes, bGenerateAsIfTwoSided, EmbreeScene); //@todo - project setting const int32 NumVoxelDistanceSamples = 1200; TArray SampleDirections; FRandomStream RandomStream(0); MeshUtilities::GenerateStratifiedUniformHemisphereSamples(NumVoxelDistanceSamples, RandomStream, SampleDirections); TArray OtherHemisphereSamples; MeshUtilities::GenerateStratifiedUniformHemisphereSamples(NumVoxelDistanceSamples, RandomStream, OtherHemisphereSamples); for (int32 i = 0; i < OtherHemisphereSamples.Num(); i++) { FVector4 Sample = OtherHemisphereSamples[i]; Sample.Z *= -1; SampleDirections.Add(Sample); } static const auto CVar = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.DistanceFields.MaxPerMeshResolution")); const int32 PerMeshMax = CVar->GetValueOnAnyThread(); // Meshes with explicit artist-specified scale can go higher const int32 MaxNumVoxelsOneDim = DistanceFieldResolutionScale <= 1 ? PerMeshMax / 2 : PerMeshMax; const int32 MinNumVoxelsOneDim = 8; static const auto CVarDensity = IConsoleManager::Get().FindTConsoleVariableDataFloat(TEXT("r.DistanceFields.DefaultVoxelDensity")); const float VoxelDensity = CVarDensity->GetValueOnAnyThread(); const float NumVoxelsPerLocalSpaceUnit = VoxelDensity * DistanceFieldResolutionScale; FBox MeshBounds(Bounds.GetBox()); // Make sure BBox isn't empty and we can generate an SDF for it. This handles e.g. infinitely thin planes. FVector MeshBoundsCenter = MeshBounds.GetCenter(); FVector MeshBoundsExtent = FVector::Max(MeshBounds.GetExtent(), FVector(1.0f, 1.0f, 1.0f)); MeshBounds.Min = MeshBoundsCenter - MeshBoundsExtent; MeshBounds.Max = MeshBoundsCenter + MeshBoundsExtent; static const auto CVarEightBit = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.DistanceFieldBuild.EightBit")); const bool bEightBitFixedPoint = CVarEightBit->GetValueOnAnyThread() != 0; const int32 FormatSize = GPixelFormats[bEightBitFixedPoint ? PF_G8 : PF_R16F].BlockBytes; { FVector DesiredDimensions = FVector(MeshBounds.GetSize()* FVector(NumVoxelsPerLocalSpaceUnit)); FIntVector VolumeDimensions = FIntVector( FMath::Clamp(FMath::TruncToInt(DesiredDimensions.X) + 2 * DistanceField::MeshDistanceFieldBorder, MinNumVoxelsOneDim, MaxNumVoxelsOneDim), FMath::Clamp(FMath::TruncToInt(DesiredDimensions.Y) + 2 * DistanceField::MeshDistanceFieldBorder, MinNumVoxelsOneDim, MaxNumVoxelsOneDim), FMath::Clamp(FMath::TruncToInt(DesiredDimensions.Z) + 2 * DistanceField::MeshDistanceFieldBorder, MinNumVoxelsOneDim, MaxNumVoxelsOneDim)); // Expand to guarantee one voxel border for gradient reconstruction using bilinear filtering const FVector TexelObjectSpaceSize = MeshBounds.GetSize() / FVector(VolumeDimensions - FIntVector(2 * DistanceField::MeshDistanceFieldBorder)); const FBox DistanceFieldVolumeBounds = MeshBounds.ExpandBy(DistanceField::MeshDistanceFieldBorder * TexelObjectSpaceSize); const float DistanceFieldVolumeMaxDistance = DistanceFieldVolumeBounds.GetExtent().Size(); TArray DistanceFieldVolume; DistanceFieldVolume.Empty(VolumeDimensions.X * VolumeDimensions.Y * VolumeDimensions.Z); DistanceFieldVolume.AddZeroed(VolumeDimensions.X * VolumeDimensions.Y * VolumeDimensions.Z); TArray AsyncTasks; AsyncTasks.Reserve(VolumeDimensions.Z); for (int32 ZIndex = 0; ZIndex < VolumeDimensions.Z; ZIndex++) { AsyncTasks.Emplace( &EmbreeScene.kDopTree, EmbreeScene.bUseEmbree, EmbreeScene.EmbreeScene, &SampleDirections, DistanceFieldVolumeBounds, VolumeDimensions, DistanceFieldVolumeMaxDistance, ZIndex, &DistanceFieldVolume); } bool bNegativeAtBorder = false; ParallelForTemplate(AsyncTasks.Num(), [&AsyncTasks](int32 TaskIndex) { AsyncTasks[TaskIndex].DoWork(); }, EParallelForFlags::BackgroundPriority | EParallelForFlags::Unbalanced); for (int32 TaskIndex = 0; TaskIndex < AsyncTasks.Num(); TaskIndex++) { bNegativeAtBorder = bNegativeAtBorder || AsyncTasks[TaskIndex].WasNegativeAtBorder(); } if (bNegativeAtBorder) { // Mesh distance fields which have negative values at the boundaries (unclosed meshes) are edited to have a virtual surface just behind the real surface, effectively closing them. bNegativeAtBorder = false; const float MinInteriorDistance = -.1f; for (int32 Index = 0; Index < DistanceFieldVolume.Num(); Index++) { const float OriginalVolumeSpaceDistance = DistanceFieldVolume[Index]; float NewVolumeSpaceDistance = OriginalVolumeSpaceDistance; if (OriginalVolumeSpaceDistance < MinInteriorDistance) { NewVolumeSpaceDistance = MinInteriorDistance - OriginalVolumeSpaceDistance; DistanceFieldVolume[Index] = NewVolumeSpaceDistance; } const int32 XIndex = Index % VolumeDimensions.X; const int32 ZIndex = Index / (VolumeDimensions.Y * VolumeDimensions.X); const int32 YIndex = (Index - ZIndex * VolumeDimensions.Y * VolumeDimensions.X) / VolumeDimensions.X; if (NewVolumeSpaceDistance < 0 && (XIndex == 0 || XIndex == VolumeDimensions.X - 1 || YIndex == 0 || YIndex == VolumeDimensions.Y - 1 || ZIndex == 0 || ZIndex == VolumeDimensions.Z - 1)) { bNegativeAtBorder = true; } } } if (bNegativeAtBorder) { UE_LOG(LogMeshUtilities, Log, TEXT("Distance field for %s mesh has a negative border."), *MeshName); } TArray QuantizedDistanceFieldVolume; QuantizedDistanceFieldVolume.Empty(VolumeDimensions.X * VolumeDimensions.Y * VolumeDimensions.Z * FormatSize); QuantizedDistanceFieldVolume.AddZeroed(VolumeDimensions.X * VolumeDimensions.Y * VolumeDimensions.Z * FormatSize); float MinVolumeDistance = 1.0f; float MaxVolumeDistance = -1.0f; for (int32 Index = 0; Index < DistanceFieldVolume.Num(); Index++) { const float VolumeSpaceDistance = DistanceFieldVolume[Index]; MinVolumeDistance = FMath::Min(MinVolumeDistance, VolumeSpaceDistance); MaxVolumeDistance = FMath::Max(MaxVolumeDistance, VolumeSpaceDistance); } MinVolumeDistance = FMath::Max(MinVolumeDistance, -1.0f); MaxVolumeDistance = FMath::Min(MaxVolumeDistance, 1.0f); const float InvDistanceRange = 1.0f / (MaxVolumeDistance - MinVolumeDistance); for (int32 Index = 0; Index < DistanceFieldVolume.Num(); Index++) { const float VolumeSpaceDistance = DistanceFieldVolume[Index]; if (bEightBitFixedPoint) { check(FormatSize == sizeof(uint8)); // [MinVolumeDistance, MaxVolumeDistance] -> [0, 1] const float RescaledDistance = (VolumeSpaceDistance - MinVolumeDistance) * InvDistanceRange; // Encoding based on D3D format conversion rules for float -> UNORM const int32 QuantizedDistance = FMath::FloorToInt(RescaledDistance * 255.0f + .5f); QuantizedDistanceFieldVolume[Index * FormatSize] = (uint8)FMath::Clamp(QuantizedDistance, 0, 255); } else { check(FormatSize == sizeof(FFloat16)); FFloat16* OutputPointer = (FFloat16*)&(QuantizedDistanceFieldVolume[Index * FormatSize]); *OutputPointer = FFloat16(VolumeSpaceDistance); } } DistanceFieldVolume.Empty(); OutData.bBuiltAsIfTwoSided = bGenerateAsIfTwoSided; OutData.Size = VolumeDimensions; OutData.LocalBoundingBox = MeshBounds; OutData.DistanceMinMax = FVector2D(MinVolumeDistance, MaxVolumeDistance); if (QuantizedDistanceFieldVolume.Num() > 0) { static const auto CVarCompress = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.DistanceFieldBuild.Compress")); const bool bCompress = CVarCompress->GetValueOnAnyThread() != 0; if (bCompress) { const int32 UncompressedSize = QuantizedDistanceFieldVolume.Num() * QuantizedDistanceFieldVolume.GetTypeSize(); TArray TempCompressedMemory; // Compressed can be slightly larger than uncompressed TempCompressedMemory.Empty(UncompressedSize * 4 / 3); TempCompressedMemory.AddUninitialized(UncompressedSize * 4 / 3); int32 CompressedSize = TempCompressedMemory.Num() * TempCompressedMemory.GetTypeSize(); verify(FCompression::CompressMemory( NAME_LZ4, TempCompressedMemory.GetData(), CompressedSize, QuantizedDistanceFieldVolume.GetData(), UncompressedSize, COMPRESS_BiasMemory)); OutData.CompressedDistanceFieldVolume.Empty(CompressedSize); OutData.CompressedDistanceFieldVolume.AddUninitialized(CompressedSize); FPlatformMemory::Memcpy(OutData.CompressedDistanceFieldVolume.GetData(), TempCompressedMemory.GetData(), CompressedSize); } else { int32 CompressedSize = QuantizedDistanceFieldVolume.Num() * QuantizedDistanceFieldVolume.GetTypeSize(); OutData.CompressedDistanceFieldVolume.Empty(CompressedSize); OutData.CompressedDistanceFieldVolume.AddUninitialized(CompressedSize); FPlatformMemory::Memcpy(OutData.CompressedDistanceFieldVolume.GetData(), QuantizedDistanceFieldVolume.GetData(), CompressedSize); } } else { OutData.CompressedDistanceFieldVolume.Empty(); } UE_LOG(LogMeshUtilities, Log, TEXT("Finished distance field build in %.1fs - %ux%ux%u distance field, %u triangles, Range [%.1f, %.1f], %s"), (float)(FPlatformTime::Seconds() - StartTime), VolumeDimensions.X, VolumeDimensions.Y, VolumeDimensions.Z, EmbreeScene.NumIndices / 3, MinVolumeDistance, MaxVolumeDistance, *MeshName); } MeshRepresentation::DeleteEmbreeScene(EmbreeScene); } } #else void FMeshUtilities::GenerateSignedDistanceFieldVolumeData( FString MeshName, const FSourceMeshDataForDerivedDataTask& SourceMeshData, const FStaticMeshLODResources& LODModel, class FQueuedThreadPool& ThreadPool, const TArray& MaterialBlendModes, const FBoxSphereBounds& Bounds, float DistanceFieldResolutionScale, bool bGenerateAsIfTwoSided, FDistanceFieldVolumeData& OutData) { if (DistanceFieldResolutionScale > 0) { UE_LOG(LogMeshUtilities, Error, TEXT("Couldn't generate distance field for mesh, platform is missing required Vector intrinsics.")); } } #endif // PLATFORM_ENABLE_VECTORINTRINSICS void FMeshUtilities::DownSampleDistanceFieldVolumeData(FDistanceFieldVolumeData& DistanceFieldData, float Divider) { static const auto CVarCompress = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.DistanceFieldBuild.Compress")); const bool bDataIsCompressed = CVarCompress->GetValueOnAnyThread() != 0; static const auto CVarEightBit = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.DistanceFieldBuild.EightBit")); const bool bDataIsEightBit = CVarEightBit->GetValueOnAnyThread() != 0; if (!bDataIsEightBit) { return; } TArray UncompressedData; const TArray* SourceData = &DistanceFieldData.CompressedDistanceFieldVolume; if (DistanceFieldData.Size.X <= 4 || DistanceFieldData.Size.Y <= 4 || DistanceFieldData.Size.Z <= 4) { return; } int32 UncompressedSize = DistanceFieldData.Size.X * DistanceFieldData.Size.Y * DistanceFieldData.Size.Z; if (bDataIsCompressed) { UncompressedData.AddUninitialized(UncompressedSize); verify(FCompression::UncompressMemory(NAME_LZ4, UncompressedData.GetData(), UncompressedSize, DistanceFieldData.CompressedDistanceFieldVolume.GetData(), DistanceFieldData.CompressedDistanceFieldVolume.Num())); SourceData = &UncompressedData; } const FIntVector SrcSize = DistanceFieldData.Size; FVector DstSizeFloat = FVector(SrcSize); DstSizeFloat /= Divider; FIntVector DstSize; DstSize.X = FMath::TruncToInt(DstSizeFloat.X); DstSize.Y = FMath::TruncToInt(DstSizeFloat.Y); DstSize.Z = FMath::TruncToInt(DstSizeFloat.Z); FIntVector IntVectorOne = FIntVector(1); FIntVector SrcSizeMinusOne = SrcSize - IntVectorOne; FIntVector DstSizeMinusOne = DstSize - IntVectorOne; FVector Divider3 = FVector(SrcSizeMinusOne) / FVector(DstSizeMinusOne); TArray DownSampledTexture; DownSampledTexture.SetNum(DstSize.X * DstSize.Y * DstSize.Z); int32 SrcPitchX = SrcSize.X; int32 SrcPitchY = SrcSize.X * SrcSize.Y; int32 DstPitchX = DstSize.X; int32 DstPitchY = DstSize.X * DstSize.Y; for (int32 DstPosZ = 0; DstPosZ < DstSize.Z; ++DstPosZ) { int32 SrcPosZ0 = FMath::TruncToInt((float(DstPosZ) + 0.0f) * Divider3.Z); int32 SrcPosZ1 = FMath::TruncToInt((float(DstPosZ) + 1.0f) * Divider3.Z); SrcPosZ1 = FMath::Min(SrcPosZ1, SrcSizeMinusOne.Z); int32 SrcOffsetZ0 = SrcPosZ0 * SrcPitchY; int32 SrcOffsetZ1 = SrcPosZ1 * SrcPitchY; int32 DstOffsetZ = DstPosZ * DstPitchY; for (int32 DstPosY = 0; DstPosY < DstSize.Y; ++DstPosY) { int32 SrcPosY0 = FMath::TruncToInt(float(DstPosY) + 0.0f) * Divider3.Y; int32 SrcPosY1 = FMath::TruncToInt(float(DstPosY) + 1.0f) * Divider3.Y; SrcPosY1 = FMath::Min(SrcPosY1, SrcSizeMinusOne.Y); int32 SrcOffsetZ0Y0 = SrcPosY0 * SrcPitchX + SrcOffsetZ0; int32 SrcOffsetZ0Y1 = SrcPosY1 * SrcPitchX + SrcOffsetZ0; int32 SrcOffsetZ1Y0 = SrcPosY0 * SrcPitchX + SrcOffsetZ1; int32 SrcOffsetZ1Y1 = SrcPosY1 * SrcPitchX + SrcOffsetZ1; int32 DstOffsetZY = DstPosY * DstPitchX + DstOffsetZ; for (int32 DstPosX = 0; DstPosX < DstSize.X; ++DstPosX) { int32 SrcPosX0 = FMath::TruncToInt(float(DstPosX) + 0.0f) * Divider3.X; int32 SrcPosX1 = FMath::TruncToInt(float(DstPosX) + 1.0f) * Divider3.X; SrcPosX1 = FMath::Min(SrcPosX1, SrcSizeMinusOne.X - 1); uint32 a = uint32((*SourceData)[SrcPosX0 + SrcOffsetZ0Y0]); uint32 b = uint32((*SourceData)[SrcPosX1 + SrcOffsetZ0Y0]); uint32 c = uint32((*SourceData)[SrcPosX0 + SrcOffsetZ0Y1]); uint32 d = uint32((*SourceData)[SrcPosX1 + SrcOffsetZ0Y1]); uint32 e = uint32((*SourceData)[SrcPosX0 + SrcOffsetZ1Y0]); uint32 f = uint32((*SourceData)[SrcPosX1 + SrcOffsetZ1Y0]); uint32 g = uint32((*SourceData)[SrcPosX0 + SrcOffsetZ1Y1]); uint32 h = uint32((*SourceData)[SrcPosX1 + SrcOffsetZ1Y1]); a = a + b + c + d + e + f + g + h; a /= 8; DownSampledTexture[DstOffsetZY + DstPosX] = uint8(a); } } } DistanceFieldData.Size = DstSize; UncompressedSize = DownSampledTexture.Num(); if (bDataIsCompressed) { TArray TempCompressedMemory; // Compressed can be slightly larger than uncompressed TempCompressedMemory.Empty(UncompressedSize * 4 / 3); TempCompressedMemory.AddUninitialized(UncompressedSize * 4 / 3); int32 CompressedSize = TempCompressedMemory.Num() * TempCompressedMemory.GetTypeSize(); verify(FCompression::CompressMemory( NAME_LZ4, TempCompressedMemory.GetData(), CompressedSize, DownSampledTexture.GetData(), UncompressedSize, COMPRESS_BiasMemory)); DistanceFieldData.CompressedDistanceFieldVolume.Empty(CompressedSize); DistanceFieldData.CompressedDistanceFieldVolume.AddUninitialized(CompressedSize); FPlatformMemory::Memcpy(DistanceFieldData.CompressedDistanceFieldVolume.GetData(), TempCompressedMemory.GetData(), CompressedSize); } else { DistanceFieldData.CompressedDistanceFieldVolume.Empty(UncompressedSize); DistanceFieldData.CompressedDistanceFieldVolume.AddUninitialized(UncompressedSize); FPlatformMemory::Memcpy(DistanceFieldData.CompressedDistanceFieldVolume.GetData(), DownSampledTexture.GetData(), UncompressedSize); } }