// Copyright Epic Games, Inc. All Rights Reserved.

#include "MeshUtilities2/Public/MeshUtilities2.h"
#include "MeshUtilitiesPrivate.h"
#include "RawMesh.h"
#include "StaticMeshResources.h"
#include "DistanceFieldAtlas.h"
#include "MeshRepresentationCommon.h"
#include "Async/ParallelFor.h"

#if USE_EMBREE


class FEmbreePointQueryContext : public RTCPointQueryContext
{
public:
	RTCGeometry MeshGeometry;
	int32 NumTriangles;
};

bool EmbreePointQueryFunction(RTCPointQueryFunctionArguments* args)
{
	const FEmbreePointQueryContext* Context = (const FEmbreePointQueryContext*)args->context;

	check(args->userPtr);
	float& ClosestDistanceSq = *(float*)(args->userPtr);

	const int32 TriangleIndex = args->primID;
	check(TriangleIndex < Context->NumTriangles);

	const FVector* VertexBuffer = (const FVector*)rtcGetGeometryBufferData(Context->MeshGeometry, RTC_BUFFER_TYPE_VERTEX, 0);
	const uint32* IndexBuffer = (const uint32*)rtcGetGeometryBufferData(Context->MeshGeometry, RTC_BUFFER_TYPE_INDEX, 0);

	const uint32 I0 = IndexBuffer[TriangleIndex * 3 + 0];
	const uint32 I1 = IndexBuffer[TriangleIndex * 3 + 1];
	const uint32 I2 = IndexBuffer[TriangleIndex * 3 + 2];

	const FVector V0 = VertexBuffer[I0];
	const FVector V1 = VertexBuffer[I1];
	const FVector V2 = VertexBuffer[I2];

	const FVector QueryPosition(args->query->x, args->query->y, args->query->z);
	const FVector ClosestPoint = FMath::ClosestPointOnTriangleToPoint(QueryPosition, V0, V1, V2);
	const float QueryDistanceSq = (ClosestPoint - QueryPosition).SizeSquared();

	if (QueryDistanceSq < ClosestDistanceSq)
	{
		ClosestDistanceSq = QueryDistanceSq;

		bool bShrinkQuery = true;

		if (bShrinkQuery)
		{
			args->query->radius = FMath::Sqrt(ClosestDistanceSq);
			// Return true to indicate that the query radius has shrunk
			return true;
		}
	}

	// Return false to indicate that the query radius hasn't changed
	return false;
}

static int32 ComputeLinearVoxelIndex(FIntVector VoxelCoordinate, FIntVector VolumeDimensions)
{
	return (VoxelCoordinate.Z * VolumeDimensions.Y + VoxelCoordinate.Y) * VolumeDimensions.X + VoxelCoordinate.X;
}

class FSparseMeshDistanceFieldAsyncTask
{
public:
	FSparseMeshDistanceFieldAsyncTask(
		const FEmbreeScene& InEmbreeScene,
		const TArray<FVector4>* InSampleDirections,
		float InLocalSpaceTraceDistance,
		FBox InVolumeBounds,
		float InLocalToVolumeScale,
		FVector2D InDistanceFieldToVolumeScaleBias,
		FIntVector InBrickCoordinate,
		FIntVector InIndirectionSize,
		bool bInUsePointQuery)
		:
		EmbreeScene(InEmbreeScene),
		SampleDirections(InSampleDirections),
		LocalSpaceTraceDistance(InLocalSpaceTraceDistance),
		VolumeBounds(InVolumeBounds),
		LocalToVolumeScale(InLocalToVolumeScale),
		DistanceFieldToVolumeScaleBias(InDistanceFieldToVolumeScaleBias),
		BrickCoordinate(InBrickCoordinate),
		IndirectionSize(InIndirectionSize),
		bUsePointQuery(bInUsePointQuery),
		BrickMaxDistance(MIN_uint8),
		BrickMinDistance(MAX_uint8)
	{}

	void DoWork();

	// Readonly inputs
	const FEmbreeScene& EmbreeScene;
	const TArray<FVector4>* SampleDirections;
	float LocalSpaceTraceDistance;
	FBox VolumeBounds;
	float LocalToVolumeScale;
	FVector2D DistanceFieldToVolumeScaleBias;
	FIntVector BrickCoordinate;
	FIntVector IndirectionSize;
	bool bUsePointQuery;

	// Output
	uint8 BrickMaxDistance;
	uint8 BrickMinDistance;
	TArray<uint8> DistanceFieldVolume;
};

int32 DebugX = 0;
int32 DebugY = 0;
int32 DebugZ = 0;

void FSparseMeshDistanceFieldAsyncTask::DoWork()
{
	TRACE_CPUPROFILER_EVENT_SCOPE(FSparseMeshDistanceFieldAsyncTask::DoWork);

	const FVector IndirectionVoxelSize = VolumeBounds.GetSize() / FVector(IndirectionSize);
	const FVector DistanceFieldVoxelSize = IndirectionVoxelSize / FVector(DistanceField::UniqueDataBrickSize);
	const FVector BrickMinPosition = VolumeBounds.Min + FVector(BrickCoordinate) * IndirectionVoxelSize;

	DistanceFieldVolume.Empty(DistanceField::BrickSize * DistanceField::BrickSize * DistanceField::BrickSize);
	DistanceFieldVolume.AddZeroed(DistanceField::BrickSize * DistanceField::BrickSize * DistanceField::BrickSize);

	for (int32 ZIndex = 0; ZIndex < DistanceField::BrickSize; ZIndex++)
	{
		for (int32 YIndex = 0; YIndex < DistanceField::BrickSize; YIndex++)
		{
			for (int32 XIndex = 0; XIndex < DistanceField::BrickSize; XIndex++)
			{
				if (XIndex == DebugX && YIndex == DebugY && ZIndex == DebugZ)
				{
					int32 DebugBreak = 0;
				}

				const FVector VoxelPosition = FVector(XIndex, YIndex, ZIndex) * DistanceFieldVoxelSize + BrickMinPosition;
				const int32 Index = (ZIndex * DistanceField::BrickSize * DistanceField::BrickSize + YIndex * DistanceField::BrickSize + XIndex);

				float MinLocalSpaceDistance = LocalSpaceTraceDistance;

				bool bTraceRays = true;

				if (bUsePointQuery)
				{
					RTCPointQuery PointQuery;
					PointQuery.x = VoxelPosition.X;
					PointQuery.y = VoxelPosition.Y;
					PointQuery.z = VoxelPosition.Z;
					PointQuery.time = 0;
					PointQuery.radius = LocalSpaceTraceDistance;

					FEmbreePointQueryContext QueryContext;
					rtcInitPointQueryContext(&QueryContext);
					QueryContext.MeshGeometry = EmbreeScene.Geometry.InternalGeometry;
					QueryContext.NumTriangles = EmbreeScene.Geometry.TriangleDescs.Num();
					float ClosestUnsignedDistanceSq = (LocalSpaceTraceDistance * 2.0f) * (LocalSpaceTraceDistance * 2.0f);
					rtcPointQuery(EmbreeScene.EmbreeScene, &PointQuery, &QueryContext, EmbreePointQueryFunction, &ClosestUnsignedDistanceSq);

					const float ClosestDistance = FMath::Sqrt(ClosestUnsignedDistanceSq);
					bTraceRays = ClosestDistance <= LocalSpaceTraceDistance;
					MinLocalSpaceDistance = FMath::Min(MinLocalSpaceDistance, ClosestDistance);
				}

				if (bTraceRays)
				{
					int32 Hit = 0;
					int32 HitBack = 0;

					for (int32 SampleIndex = 0; SampleIndex < SampleDirections->Num(); SampleIndex++)
					{
						const FVector UnitRayDirection = (*SampleDirections)[SampleIndex];
						const float PullbackEpsilon = 1.e-4f;
						// Pull back the starting position slightly to make sure we hit a triangle that VoxelPosition is exactly on.
						// This happens a lot with boxes, since we trace from voxel corners.
						const FVector StartPosition = VoxelPosition - PullbackEpsilon * LocalSpaceTraceDistance * UnitRayDirection;
						const FVector EndPosition = VoxelPosition + UnitRayDirection * LocalSpaceTraceDistance;

						if (FMath::LineBoxIntersection(VolumeBounds, VoxelPosition, EndPosition, UnitRayDirection))
						{
							FEmbreeRay EmbreeRay;

							FVector RayDirection = EndPosition - VoxelPosition;
							EmbreeRay.ray.org_x = StartPosition.X;
							EmbreeRay.ray.org_y = StartPosition.Y;
							EmbreeRay.ray.org_z = StartPosition.Z;
							EmbreeRay.ray.dir_x = RayDirection.X;
							EmbreeRay.ray.dir_y = RayDirection.Y;
							EmbreeRay.ray.dir_z = RayDirection.Z;
							EmbreeRay.ray.tnear = 0;
							EmbreeRay.ray.tfar = 1.0f;

							FEmbreeIntersectionContext EmbreeContext;
							rtcInitIntersectContext(&EmbreeContext);
							rtcIntersect1(EmbreeScene.EmbreeScene, &EmbreeContext, &EmbreeRay);

							if (EmbreeRay.hit.geomID != RTC_INVALID_GEOMETRY_ID && EmbreeRay.hit.primID != RTC_INVALID_GEOMETRY_ID)
							{
								check(EmbreeContext.ElementIndex != -1);
								Hit++;

								const FVector HitNormal = EmbreeRay.GetHitNormal();

								if (FVector::DotProduct(UnitRayDirection, HitNormal) > 0 && !EmbreeContext.IsHitTwoSided())
								{
									HitBack++;
								}

								if (!bUsePointQuery)
								{
									const float CurrentDistance = EmbreeRay.ray.tfar * LocalSpaceTraceDistance;

									if (CurrentDistance < MinLocalSpaceDistance)
									{
										MinLocalSpaceDistance = CurrentDistance;
									}
								}
							}
						}
					}

					// Consider this voxel 'inside' an object if we hit a significant number of backfaces
					if (Hit > 0 && HitBack > .25f * SampleDirections->Num())
					{
						MinLocalSpaceDistance *= -1;
					}
				}

				// Transform to the tracing shader's Volume space
				const float VolumeSpaceDistance = MinLocalSpaceDistance * LocalToVolumeScale;
				// Transform to the Distance Field texture's space
				const float RescaledDistance = (VolumeSpaceDistance - DistanceFieldToVolumeScaleBias.Y) / DistanceFieldToVolumeScaleBias.X;
				check(DistanceField::DistanceFieldFormat == PF_G8);
				const uint8 QuantizedDistance = FMath::Clamp<int32>(FMath::FloorToInt(RescaledDistance * 255.0f + .5f), 0, 255);
				DistanceFieldVolume[Index] = QuantizedDistance;
				BrickMaxDistance = FMath::Max(BrickMaxDistance, QuantizedDistance);
				BrickMinDistance = FMath::Min(BrickMinDistance, QuantizedDistance);
			}
		}
	}
}

void FMeshUtilities2::GenerateSignedDistanceFieldVolumeData(
	FString MeshName,
	const FSourceMeshDataForDerivedDataTask& SourceMeshData,
	const FStaticMeshLODResources& LODModel,
	class FQueuedThreadPool& ThreadPool,
	const TArray<FSignedDistanceFieldBuildMaterialData2>& MaterialBlendModes,
	const FBoxSphereBounds& Bounds,
	float DistanceFieldResolutionScale,
	bool bGenerateAsIfTwoSided,
	FDistanceFieldVolumeData& OutData)
{
	if (DistanceFieldResolutionScale > 0)
	{
		const double StartTime = FPlatformTime::Seconds();

		FEmbreeScene EmbreeScene;
		MeshRepresentation::SetupEmbreeScene(MeshName,
			SourceMeshData,
			LODModel,
			MaterialBlendModes,
			bGenerateAsIfTwoSided,
			EmbreeScene);

		check(EmbreeScene.bUseEmbree);

		bool bMostlyTwoSided;
		{
			uint32 NumTrianglesTotal = 0;
			uint32 NumTwoSidedTriangles = 0;

			for (int32 SectionIndex = 0; SectionIndex < LODModel.Sections.Num(); SectionIndex++)
			{
				const FStaticMeshSection& Section = LODModel.Sections[SectionIndex];

				if (MaterialBlendModes.IsValidIndex(Section.MaterialIndex))
				{
					NumTrianglesTotal += Section.NumTriangles;

					if (MaterialBlendModes[Section.MaterialIndex].bTwoSided)
					{
						NumTwoSidedTriangles += Section.NumTriangles;
					}
				}
			}

			bMostlyTwoSided = NumTwoSidedTriangles * 4 >= NumTrianglesTotal || bGenerateAsIfTwoSided;
		}

		// Whether to use an Embree Point Query to compute the closest unsigned distance.  Rays will only be traced to determine backfaces visible for sign.
		const bool bUsePointQuery = true;

		TArray<FVector4> SampleDirections;
		{
			const int32 NumVoxelDistanceSamples = bUsePointQuery ? 120 : 1200;
			FRandomStream RandomStream(0);
			MeshUtilities::GenerateStratifiedUniformHemisphereSamples(NumVoxelDistanceSamples, RandomStream, SampleDirections);
			TArray<FVector4> 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 MaxNumBlocksOneDim = FMath::Min<int32>(FMath::DivideAndRoundNearest(DistanceFieldResolutionScale <= 1 ? PerMeshMax / 2 : PerMeshMax, DistanceField::UniqueDataBrickSize), DistanceField::MaxIndirectionDimension - 1);

		static const auto CVarDensity = IConsoleManager::Get().FindTConsoleVariableDataFloat(TEXT("r.DistanceFields.DefaultVoxelDensity"));
		const float VoxelDensity = CVarDensity->GetValueOnAnyThread();

		const float NumVoxelsPerLocalSpaceUnit = VoxelDensity * DistanceFieldResolutionScale;
		FBox LocalSpaceMeshBounds(Bounds.GetBox());

		// Make sure the mesh bounding box has positive extents to handle planes
		{
			FVector MeshBoundsCenter = LocalSpaceMeshBounds.GetCenter();
			FVector MeshBoundsExtent = FVector::Max(LocalSpaceMeshBounds.GetExtent(), FVector(1.0f, 1.0f, 1.0f));
			LocalSpaceMeshBounds.Min = MeshBoundsCenter - MeshBoundsExtent;
			LocalSpaceMeshBounds.Max = MeshBoundsCenter + MeshBoundsExtent;
		}

		// We sample on voxel corners and use central differencing for gradients, so a box mesh using two-sided materials whose vertices lie on LocalSpaceMeshBounds produces a zero gradient on intersection
		// Expand the mesh bounds by a fraction of a voxel to allow room for a pullback on the hit location for computing the gradient.
		// Only expand for two sided meshes as this adds significant Mesh SDF tracing cost
		if (bMostlyTwoSided)
		{
			const FVector DesiredDimensions = FVector(LocalSpaceMeshBounds.GetSize() * FVector(NumVoxelsPerLocalSpaceUnit / (float)DistanceField::UniqueDataBrickSize));
			const FIntVector Mip0IndirectionDimensions = FIntVector(
				FMath::Clamp(FMath::RoundToInt(DesiredDimensions.X), 1, MaxNumBlocksOneDim),
				FMath::Clamp(FMath::RoundToInt(DesiredDimensions.Y), 1, MaxNumBlocksOneDim),
				FMath::Clamp(FMath::RoundToInt(DesiredDimensions.Z), 1, MaxNumBlocksOneDim));

			const float CentralDifferencingExpandInVoxels = .25f;
			const FVector TexelObjectSpaceSize = LocalSpaceMeshBounds.GetSize() / FVector(Mip0IndirectionDimensions * DistanceField::UniqueDataBrickSize - FIntVector(2 * CentralDifferencingExpandInVoxels));
			LocalSpaceMeshBounds = LocalSpaceMeshBounds.ExpandBy(TexelObjectSpaceSize);
		}

		// The tracing shader uses a Volume space that is normalized by the maximum extent, to keep Volume space within [-1, 1], we must match that behavior when encoding
		const float LocalToVolumeScale = 1.0f / LocalSpaceMeshBounds.GetExtent().GetMax();

		const FVector DesiredDimensions = FVector(LocalSpaceMeshBounds.GetSize() * FVector(NumVoxelsPerLocalSpaceUnit / (float)DistanceField::UniqueDataBrickSize));
		const FIntVector Mip0IndirectionDimensions = FIntVector(
			FMath::Clamp(FMath::RoundToInt(DesiredDimensions.X), 1, MaxNumBlocksOneDim),
			FMath::Clamp(FMath::RoundToInt(DesiredDimensions.Y), 1, MaxNumBlocksOneDim),
			FMath::Clamp(FMath::RoundToInt(DesiredDimensions.Z), 1, MaxNumBlocksOneDim));

		TArray<uint8> StreamableMipData;

		for (int32 MipIndex = 0; MipIndex < DistanceField::NumMips; MipIndex++)
		{
			const FIntVector IndirectionDimensions = FIntVector(
				FMath::DivideAndRoundUp(Mip0IndirectionDimensions.X, 1 << MipIndex),
				FMath::DivideAndRoundUp(Mip0IndirectionDimensions.Y, 1 << MipIndex),
				FMath::DivideAndRoundUp(Mip0IndirectionDimensions.Z, 1 << MipIndex));

			// Expand to guarantee one voxel border for gradient reconstruction using bilinear filtering
			const FVector TexelObjectSpaceSize = LocalSpaceMeshBounds.GetSize() / FVector(IndirectionDimensions * DistanceField::UniqueDataBrickSize - FIntVector(2 * DistanceField::MeshDistanceFieldObjectBorder));
			const FBox DistanceFieldVolumeBounds = LocalSpaceMeshBounds.ExpandBy(TexelObjectSpaceSize);

			const FVector IndirectionVoxelSize = DistanceFieldVolumeBounds.GetSize() / FVector(IndirectionDimensions);
			const float IndirectionVoxelRadius = IndirectionVoxelSize.Size();

			const FVector VolumeSpaceDistanceFieldVoxelSize = IndirectionVoxelSize * LocalToVolumeScale / FVector(DistanceField::UniqueDataBrickSize);
			const float MaxDistanceForEncoding = VolumeSpaceDistanceFieldVoxelSize.Size() * DistanceField::BandSizeInVoxels;
			const float LocalSpaceTraceDistance = MaxDistanceForEncoding / LocalToVolumeScale;
			const FVector2D DistanceFieldToVolumeScaleBias(2.0f * MaxDistanceForEncoding, -MaxDistanceForEncoding);

			TArray<FSparseMeshDistanceFieldAsyncTask> AsyncTasks;
			AsyncTasks.Reserve(IndirectionDimensions.X * IndirectionDimensions.Y * IndirectionDimensions.Z / 8);

			for (int32 ZIndex = 0; ZIndex < IndirectionDimensions.Z; ZIndex++)
			{
				for (int32 YIndex = 0; YIndex < IndirectionDimensions.Y; YIndex++)
				{
					for (int32 XIndex = 0; XIndex < IndirectionDimensions.X; XIndex++)
					{
						AsyncTasks.Emplace(
							EmbreeScene,
							&SampleDirections,
							LocalSpaceTraceDistance,
							DistanceFieldVolumeBounds,
							LocalToVolumeScale,
							DistanceFieldToVolumeScaleBias,
							FIntVector(XIndex, YIndex, ZIndex),
							IndirectionDimensions,
							bUsePointQuery);
					}
				}
			}

			static bool bMultiThreaded = true;

			if (bMultiThreaded)
			{
				EParallelForFlags Flags = EParallelForFlags::BackgroundPriority | EParallelForFlags::Unbalanced;

				ParallelForTemplate(AsyncTasks.Num(), [&AsyncTasks](int32 TaskIndex)
				{
					AsyncTasks[TaskIndex].DoWork();
				}, Flags);
			}
			else
			{
				for (FSparseMeshDistanceFieldAsyncTask& AsyncTask : AsyncTasks)
				{
					AsyncTask.DoWork();
				}
			}

			FSparseDistanceFieldMip& OutMip = OutData.Mips[MipIndex];
			TArray<uint32> IndirectionTable;
			IndirectionTable.Empty(IndirectionDimensions.X * IndirectionDimensions.Y * IndirectionDimensions.Z);
			IndirectionTable.AddUninitialized(IndirectionDimensions.X * IndirectionDimensions.Y * IndirectionDimensions.Z);

			for (int32 i = 0; i < IndirectionTable.Num(); i++)
			{
				IndirectionTable[i] = DistanceField::InvalidBrickIndex;
			}

			TArray<FSparseMeshDistanceFieldAsyncTask*> ValidBricks;
			ValidBricks.Empty(AsyncTasks.Num());

			for (int32 TaskIndex = 0; TaskIndex < AsyncTasks.Num(); TaskIndex++)
			{
				if (AsyncTasks[TaskIndex].BrickMinDistance < MAX_uint8 && AsyncTasks[TaskIndex].BrickMaxDistance > MIN_uint8)
				{
					ValidBricks.Add(&AsyncTasks[TaskIndex]);
				}
			}

			const uint32 NumBricks = ValidBricks.Num();

			const uint32 BrickSizeBytes = DistanceField::BrickSize * DistanceField::BrickSize * DistanceField::BrickSize * GPixelFormats[DistanceField::DistanceFieldFormat].BlockBytes;

			TArray<uint8> DistanceFieldBrickData;
			DistanceFieldBrickData.Empty(BrickSizeBytes * NumBricks);
			DistanceFieldBrickData.AddUninitialized(BrickSizeBytes * NumBricks);

			for (int32 BrickIndex = 0; BrickIndex < ValidBricks.Num(); BrickIndex++)
			{
				const FSparseMeshDistanceFieldAsyncTask& Brick = *ValidBricks[BrickIndex];
				const int32 IndirectionIndex = ComputeLinearVoxelIndex(Brick.BrickCoordinate, IndirectionDimensions);
				IndirectionTable[IndirectionIndex] = BrickIndex;

				check(BrickSizeBytes == Brick.DistanceFieldVolume.Num() * Brick.DistanceFieldVolume.GetTypeSize());
				FPlatformMemory::Memcpy(&DistanceFieldBrickData[BrickIndex * BrickSizeBytes], Brick.DistanceFieldVolume.GetData(), Brick.DistanceFieldVolume.Num() * Brick.DistanceFieldVolume.GetTypeSize());
			}

			const int32 IndirectionTableBytes = IndirectionTable.Num() * IndirectionTable.GetTypeSize();
			const int32 MipDataBytes = IndirectionTableBytes + DistanceFieldBrickData.Num();

			if (MipIndex == DistanceField::NumMips - 1)
			{
				OutData.AlwaysLoadedMip.Empty(MipDataBytes);
				OutData.AlwaysLoadedMip.AddUninitialized(MipDataBytes);

				FPlatformMemory::Memcpy(&OutData.AlwaysLoadedMip[0], IndirectionTable.GetData(), IndirectionTableBytes);

				if (DistanceFieldBrickData.Num() > 0)
				{
					FPlatformMemory::Memcpy(&OutData.AlwaysLoadedMip[IndirectionTableBytes], DistanceFieldBrickData.GetData(), DistanceFieldBrickData.Num());
				}
			}
			else
			{
				OutMip.BulkOffset = StreamableMipData.Num();
				StreamableMipData.AddUninitialized(MipDataBytes);
				OutMip.BulkSize = StreamableMipData.Num() - OutMip.BulkOffset;
				checkf(OutMip.BulkSize > 0, TEXT("BulkSize was 0 for %s with %ux%ux%u indirection"), *MeshName, IndirectionDimensions.X, IndirectionDimensions.Y, IndirectionDimensions.Z);

				FPlatformMemory::Memcpy(&StreamableMipData[OutMip.BulkOffset], IndirectionTable.GetData(), IndirectionTableBytes);

				if (DistanceFieldBrickData.Num() > 0)
				{
					FPlatformMemory::Memcpy(&StreamableMipData[OutMip.BulkOffset + IndirectionTableBytes], DistanceFieldBrickData.GetData(), DistanceFieldBrickData.Num());
				}
			}

			OutMip.IndirectionDimensions = IndirectionDimensions;
			OutMip.DistanceFieldToVolumeScaleBias = DistanceFieldToVolumeScaleBias;
			OutMip.NumDistanceFieldBricks = NumBricks;

			// Account for the border voxels we added
			const FVector VirtualUVMin = FVector(DistanceField::MeshDistanceFieldObjectBorder) / FVector(IndirectionDimensions * DistanceField::UniqueDataBrickSize);
			const FVector VirtualUVSize = FVector(IndirectionDimensions * DistanceField::UniqueDataBrickSize - FIntVector(2 * DistanceField::MeshDistanceFieldObjectBorder)) / FVector(IndirectionDimensions * DistanceField::UniqueDataBrickSize);

			const FVector VolumePositionExtent = LocalSpaceMeshBounds.GetExtent() * LocalToVolumeScale;

			// [-VolumePositionExtent, VolumePositionExtent] -> [VirtualUVMin, VirtualUVMin + VirtualUVSize]
			OutMip.VolumeToVirtualUVScale = VirtualUVSize / (2 * VolumePositionExtent);
			OutMip.VolumeToVirtualUVAdd = VolumePositionExtent * OutMip.VolumeToVirtualUVScale + VirtualUVMin;
		}

		MeshRepresentation::DeleteEmbreeScene(EmbreeScene);

		OutData.bMostlyTwoSided = bMostlyTwoSided;
		OutData.LocalSpaceMeshBounds = LocalSpaceMeshBounds;

		OutData.StreamableMips.Lock(LOCK_READ_WRITE);
		uint8* Ptr = (uint8*)OutData.StreamableMips.Realloc(StreamableMipData.Num());
		FMemory::Memcpy(Ptr, StreamableMipData.GetData(), StreamableMipData.Num());
		OutData.StreamableMips.Unlock();
		OutData.StreamableMips.SetBulkDataFlags(BULKDATA_Force_NOT_InlinePayload);

		const float BuildTime = (float)(FPlatformTime::Seconds() - StartTime);

		if (BuildTime > 1.0f)
		{
			UE_LOG(LogMeshUtilities, Log, TEXT("完成:距离场构建 %.1fs - %ux%ux%u 稀疏距离场, %.1fMb total, %.1fMb 总是加载, %u%% occupied, %u 三角形, %s"),
				BuildTime,
				Mip0IndirectionDimensions.X * DistanceField::UniqueDataBrickSize,
				Mip0IndirectionDimensions.Y * DistanceField::UniqueDataBrickSize,
				Mip0IndirectionDimensions.Z * DistanceField::UniqueDataBrickSize,
				(OutData.GetResourceSizeBytes() + OutData.StreamableMips.GetBulkDataSize()) / 1024.0f / 1024.0f,
				(OutData.AlwaysLoadedMip.GetAllocatedSize()) / 1024.0f / 1024.0f,
				FMath::RoundToInt(100.0f * OutData.Mips[0].NumDistanceFieldBricks / (float)(Mip0IndirectionDimensions.X * Mip0IndirectionDimensions.Y * Mip0IndirectionDimensions.Z)),
				EmbreeScene.NumIndices / 3,
				*MeshName);
		}
	}
}

#else

void FMeshUtilities2::GenerateSignedDistanceFieldVolumeData(
	FString MeshName,
	const FSourceMeshDataForDerivedDataTask& SourceMeshData,
	const FStaticMeshLODResources& LODModel,
	class FQueuedThreadPool& ThreadPool,
	const TArray<FSignedDistanceFieldBuildMaterialData2>& MaterialBlendModes,
	const FBoxSphereBounds& Bounds,
	float DistanceFieldResolutionScale,
	bool bGenerateAsIfTwoSided,
	FDistanceFieldVolumeData& OutData)
{
	if (DistanceFieldResolutionScale > 0)
	{
		UE_LOG(LogMeshUtilities, Warning, TEXT("Couldn't generate distance field for mesh, platform is missing Embree support."));
	}
}

#endif // PLATFORM_ENABLE_VECTORINTRINSICS
