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

#include "Collision.h"
#include "LightingSystem.h"
#include "UnrealLightmass.h"

namespace Lightmass
{

FStaticLightingAggregateMesh::FStaticLightingAggregateMesh(const FScene& InScene):
	Scene(InScene),
	bHasShadowCastingPrimitives(false),
	SceneBounds(ForceInit),
	SceneSurfaceArea(0),
	SceneSurfaceAreaWithinImportanceVolume(0)
{
}

FBox FStaticLightingAggregateMesh::GetBounds() const
{
	// Expand the bounds slightly to avoid having to handle geometry that is exactly on the bounding box,
	// Which happens if you create a new level in Unreal with BSP from the default builder brush.
	return bHasShadowCastingPrimitives ? SceneBounds.ExpandBy(5.0f * Scene.SceneConstants.StaticLightingLevelScale) : FBox(FVector4(0,0,0), FVector4(0,0,0));
}

FDefaultAggregateMesh::~FDefaultAggregateMesh()
{
	for (int32 i = 0; i < MeshInfos.Num(); i++)
	{
		delete MeshInfos[i];
	}
}

/**
* Merges a mesh into the shadow mesh.
* @param Mesh - The mesh the triangle comes from.
*/
void FDefaultAggregateMesh::AddMesh(const FStaticLightingMesh* Mesh, const FStaticLightingMapping* Mapping)
{
	// Only use shadow casting meshes.
	if( Mesh->LightingFlags&GI_INSTANCE_CASTSHADOW )
	{
		SceneBounds = SceneBounds + Mesh->BoundingBox;

		const FStaticLightingTextureMapping* TextureMapping = Mapping ? Mapping->GetTextureMapping() : NULL;
		const int32 BaseVertexIndex = Vertices.Num();
		MeshInfos.Add(new FStaticLightingMeshInfo(BaseVertexIndex, Mesh));
		Vertices.AddUninitialized(Mesh->NumVertices);
		UVs.AddZeroed(Mesh->NumVertices);
		LightmapUVs.AddZeroed(Mesh->NumVertices);

		const uint32 MeshLODIndices = Mesh->GetLODIndices();
		const uint32 MeshHLODRange = Mesh->GetHLODRange();

		const FBoxSphereBounds ImportanceBounds = Scene.GetImportanceBounds();
		for(int32 TriangleIndex = 0;TriangleIndex < Mesh->NumTriangles;TriangleIndex++)
		{
			// Read the triangle from the mesh.
			FStaticLightingVertex V0;
			FStaticLightingVertex V1;
			FStaticLightingVertex V2;
			int32 ElementIndex;
			Mesh->GetTriangle(TriangleIndex,V0,V1,V2,ElementIndex);

			int32 I0 = 0;
			int32 I1 = 0;
			int32 I2 = 0;
			Mesh->GetTriangleIndices(TriangleIndex,I0,I1,I2);

			check(I0 <= Mesh->NumVertices && I1 <= Mesh->NumVertices && I2 <= Mesh->NumVertices);

			const bool bTwoSided = Mesh->IsTwoSided(ElementIndex) || Mesh->IsCastingShadowAsTwoSided();
			const bool bStaticAndOpaque = !Mesh->IsMasked(ElementIndex) && !Mesh->IsTranslucent(ElementIndex) && !Mesh->bMovable;

			Vertices[BaseVertexIndex + I0] = V0.WorldPosition;
			Vertices[BaseVertexIndex + I1] = V1.WorldPosition;
			Vertices[BaseVertexIndex + I2] = V2.WorldPosition;
			UVs[BaseVertexIndex + I0] = V0.TextureCoordinates[Mesh->TextureCoordinateIndex];
			UVs[BaseVertexIndex + I1] = V1.TextureCoordinates[Mesh->TextureCoordinateIndex];
			UVs[BaseVertexIndex + I2] = V2.TextureCoordinates[Mesh->TextureCoordinateIndex];
			if (TextureMapping)
			{
				LightmapUVs[BaseVertexIndex + I0] = V0.TextureCoordinates[TextureMapping->LightmapTextureCoordinateIndex];
				LightmapUVs[BaseVertexIndex + I1] = V1.TextureCoordinates[TextureMapping->LightmapTextureCoordinateIndex];
				LightmapUVs[BaseVertexIndex + I2] = V2.TextureCoordinates[TextureMapping->LightmapTextureCoordinateIndex];
			}

			// Compute the triangle's normal.
			const FVector4 TriangleNormal = (V2.WorldPosition - V0.WorldPosition) ^ (V1.WorldPosition - V0.WorldPosition);

			// Compute the triangle area.
			const float TriangleArea = TriangleNormal.Size3() * 0.5f;

			// Ignore zero area triangles.
			if( TriangleArea > TRIANGLE_AREA_THRESHOLD && Mesh->IsElementCastingShadow(ElementIndex))
			{
				const int32 PayloadIndex = TrianglePayloads.Add(
					FTriangleSOAPayload(MeshInfos.Last(), Mapping, ElementIndex, BaseVertexIndex + I0, BaseVertexIndex + I1, BaseVertexIndex + I2));

				bHasShadowCastingPrimitives = true;

				new(kDOPTriangles) FkDOPBuildCollisionTriangle<uint32>(
					PayloadIndex, // Use the triangle's material index as an index into TrianglePayloads.
					V0.WorldPosition,V1.WorldPosition,V2.WorldPosition,
					Mesh->MeshIndex,
					MeshLODIndices,
					MeshHLODRange,
					bTwoSided,
					bStaticAndOpaque
					);
			}

			// Sum the total triangle area of everything in the aggregate mesh
			SceneSurfaceArea += TriangleArea;

			// Sum the total triangle area of everything in the aggregate mesh within the importance volume,
			// if any vertex is contained or if there is no importance volume at all
			if( ImportanceBounds.SphereRadius < DELTA ||
				ImportanceBounds.GetBox().IsInside( V0.WorldPosition ) ||
				ImportanceBounds.GetBox().IsInside( V1.WorldPosition ) ||
				ImportanceBounds.GetBox().IsInside( V2.WorldPosition ) )
			{
				SceneSurfaceAreaWithinImportanceVolume += TriangleArea;
			}
		}
	}
}

void FDefaultAggregateMesh::AddMeshForVoxelization(const FStaticLightingMesh* Mesh, const FStaticLightingMapping* Mapping, bool bUseForInstancing)
{
	if (bUseForInstancing)
	{
		check(Mesh->GetInstanceableStaticMesh());
	}

	if (Mesh->LightingFlags & GI_INSTANCE_CASTSHADOW && Mesh->DoesMeshBelongToLOD0())
	{
		SceneBounds = SceneBounds + Mesh->BoundingBox;

		const FStaticLightingTextureMapping* TextureMapping = Mapping ? Mapping->GetTextureMapping() : NULL;
		const int32 BaseVertexIndex = Vertices.Num();
		MeshInfos.Add(new FStaticLightingMeshInfo(BaseVertexIndex, Mesh));
		Vertices.AddUninitialized(Mesh->NumVertices);
		UVs.AddZeroed(Mesh->NumVertices);
		LightmapUVs.AddZeroed(Mesh->NumVertices);

		const uint32 MeshLODIndices = Mesh->GetLODIndices();
		const uint32 MeshHLODRange = Mesh->GetHLODRange();

		for (int32 TriangleIndex = 0; TriangleIndex < Mesh->NumTriangles; TriangleIndex++)
		{
			// Read the triangle from the mesh.
			int32 I0 = 0;
			int32 I1 = 0;
			int32 I2 = 0;
			FStaticLightingVertex V0;
			FStaticLightingVertex V1;
			FStaticLightingVertex V2;
			int32 ElementIndex;
			if (bUseForInstancing)
			{
				Mesh->GetInstanceableStaticMesh()->GetNonTransformedTriangleIndices(TriangleIndex, I0, I1, I2);
				Mesh->GetInstanceableStaticMesh()->GetNonTransformedTriangle(TriangleIndex, V0, V1, V2, ElementIndex);
			}
			else
			{
				Mesh->GetTriangleIndices(TriangleIndex, I0, I1, I2);
				Mesh->GetTriangle(TriangleIndex, V0, V1, V2, ElementIndex);
			}

			if (Mesh->IsElementCastingShadow(ElementIndex) && !Mesh->IsTranslucent(ElementIndex))
			{
				check(I0 <= Mesh->NumVertices && I1 <= Mesh->NumVertices && I2 <= Mesh->NumVertices);

				const bool bTwoSided = Mesh->IsTwoSided(ElementIndex) || Mesh->IsCastingShadowAsTwoSided();
				const bool bStaticAndOpaque = !Mesh->IsMasked(ElementIndex) && !Mesh->IsTranslucent(ElementIndex) && !Mesh->bMovable;

				Vertices[BaseVertexIndex + I0] = V0.WorldPosition;
				Vertices[BaseVertexIndex + I1] = V1.WorldPosition;
				Vertices[BaseVertexIndex + I2] = V2.WorldPosition;
				UVs[BaseVertexIndex + I0] = V0.TextureCoordinates[Mesh->TextureCoordinateIndex];
				UVs[BaseVertexIndex + I1] = V1.TextureCoordinates[Mesh->TextureCoordinateIndex];
				UVs[BaseVertexIndex + I2] = V2.TextureCoordinates[Mesh->TextureCoordinateIndex];
				if (TextureMapping)
				{
					LightmapUVs[BaseVertexIndex + I0] = V0.TextureCoordinates[TextureMapping->LightmapTextureCoordinateIndex];
					LightmapUVs[BaseVertexIndex + I1] = V1.TextureCoordinates[TextureMapping->LightmapTextureCoordinateIndex];
					LightmapUVs[BaseVertexIndex + I2] = V2.TextureCoordinates[TextureMapping->LightmapTextureCoordinateIndex];
				}

				// Compute the triangle's normal.
				const FVector4 TriangleNormal = (V2.WorldPosition - V0.WorldPosition) ^ (V1.WorldPosition - V0.WorldPosition);

				// Compute the triangle area.
				const float TriangleArea = TriangleNormal.Size3() * 0.5f;

				// Ignore zero area triangles.
				if (TriangleArea > TRIANGLE_AREA_THRESHOLD)
				{
					const int32 PayloadIndex = TrianglePayloads.Add(
						FTriangleSOAPayload(MeshInfos.Last(), Mapping, ElementIndex, BaseVertexIndex + I0, BaseVertexIndex + I1, BaseVertexIndex + I2));

					new(kDOPTriangles) FkDOPBuildCollisionTriangle<uint32>(
						PayloadIndex, // Use the triangle's material index as an index into TrianglePayloads.
						V0.WorldPosition, V1.WorldPosition, V2.WorldPosition,
						Mesh->MeshIndex,
						MeshLODIndices,
						MeshHLODRange,
						bTwoSided,
						bStaticAndOpaque
						);
				}
			}
		}
	}
}

/**
 * Pre-allocates memory ahead of time, before calling AddMesh() a bunch of times.
 *
 * @param NumMeshes		- Expected number of meshes which will be added
 * @param NumVertices	- Expected number of vertices which will be added
 * @param NumTriangles	- Expected number of triangles which will be added
 */
void FDefaultAggregateMesh::ReserveMemory( int32 NumMeshes, int32 NumVertices, int32 NumTriangles )
{
	UE_LOG(LogLightmass, Log, TEXT("Reserving memory for %d meshes, %d vertices, %d triangles"), NumMeshes, NumVertices, NumTriangles);
	MeshInfos.Reserve( NumMeshes );
	Vertices.Reserve( NumVertices );
	UVs.Reserve( NumVertices );
	LightmapUVs.Reserve( NumVertices );
	TrianglePayloads.Reserve( NumTriangles );
	kDOPTriangles.Reserve( NumTriangles );
}

void FDefaultAggregateMesh::PrepareForRaytracing()
{
	// Build the kDOP for simple meshes.
	kDopTree.Build(kDOPTriangles);
	kDOPTriangles.Empty();
	TrianglePayloads.Shrink();
}

void FDefaultAggregateMesh::DumpStats() const
{
	UE_LOG(LogLightmass, Log, TEXT("Preallocated %.1fGb for kDOP nodes and triangles"), kDopTree.kDOPPreallocatedMemory / 1024.0f / 1024.0f / 1024.0f);
	UE_LOG(LogLightmass, Log, TEXT("Building kDOP took %5.2f seconds."), kDopTree.kDOPBuildTime);

	// Log information about the aggregate mesh.
	UE_LOG(LogLightmass, Log, TEXT("Static lighting kDOP: %u nodes, %u leaves, %u triangles, %u vertices"), kDopTree.GKDOPNodes, kDopTree.GKDOPNumLeaves, kDopTree.GKDOPTriangles, Vertices.Num());
	UE_LOG(LogLightmass, Log, TEXT("Static lighting kDOP: %.3f%% wasted space in leaves"), ((kDopTree.GKDOPTriangles - kDOPTriangles.Num()) / (float)kDopTree.GKDOPTriangles) * 100.0f);

	const uint64 kDOPTreeBytes = kDopTree.Nodes.GetAllocatedSize() 
		+ kDopTree.SOATriangles.GetAllocatedSize()
		+ kDOPTriangles.GetAllocatedSize()
		+ TrianglePayloads.GetAllocatedSize()
		+ MeshInfos.GetAllocatedSize()
		+ Vertices.GetAllocatedSize()
		+ UVs.GetAllocatedSize()
		+ LightmapUVs.GetAllocatedSize();

	UE_LOG(LogLightmass, Log, TEXT("kDopTree.Nodes        : %7.1fMb"), kDopTree.Nodes.GetAllocatedSize() / 1048576.0f);
	UE_LOG(LogLightmass, Log, TEXT("kDopTree.SOATriangles : %7.1fMb"), kDopTree.SOATriangles.GetAllocatedSize() / 1048576.0f);
	UE_LOG(LogLightmass, Log, TEXT("kDOPTriangles         : %7.1fMb"), kDOPTriangles.GetAllocatedSize() / 1048576.0f);
	UE_LOG(LogLightmass, Log, TEXT("TrianglePayloads      : %7.1fMb"), TrianglePayloads.GetAllocatedSize() / 1048576.0f);
	UE_LOG(LogLightmass, Log, TEXT("MeshInfos             : %7.1fMb"), MeshInfos.GetAllocatedSize() / 1048576.0f);
	UE_LOG(LogLightmass, Log, TEXT("Vertices              : %7.1fMb"), Vertices.GetAllocatedSize() / 1048576.0f);
	UE_LOG(LogLightmass, Log, TEXT("UVs                   : %7.1fMb"), UVs.GetAllocatedSize() / 1048576.0f);
	UE_LOG(LogLightmass, Log, TEXT("LightmapUVs           : %7.1fMb"), LightmapUVs.GetAllocatedSize() / 1048576.0f);
	UE_LOG(LogLightmass, Log, TEXT("Static lighting kDOP: %u nodes, %u leaves, %u triangles, %u vertices, %.1f Mb"), kDopTree.GKDOPNodes, kDopTree.GKDOPNumLeaves, kDopTree.GKDOPTriangles, Vertices.Num(), kDOPTreeBytes / 1048576.0f);
}

void FDefaultAggregateMesh::DumpPostBuildStats() const
{
	double KDOPTrianglesTraversedRealPercent = (1.0 - ((kDopTree.GKDOPTrianglesTraversed - kDopTree.GKDOPTrianglesTraversedReal) / (kDopTree.GKDOPTrianglesTraversed * 100.0)));
	UE_LOG(LogLightmass, Log, TEXT("kDOP traversals (in millions): %.3g parents, %.3g leaves, %.3g triangles (%.3g, %.3g%%, real triangles)."),
		kDopTree.GKDOPParentNodesTraversed / 1000000.0,
		kDopTree.GKDOPLeafNodesTraversed / 1000000.0,
		kDopTree.GKDOPTrianglesTraversed / 1000000.0,
		kDopTree.GKDOPTrianglesTraversedReal / 1000000.0,
		KDOPTrianglesTraversedRealPercent);
}

class FStaticLightingAggregateMeshDataProvider
{
public:

	/** Initialization constructor. */
	FStaticLightingAggregateMeshDataProvider(const FDefaultAggregateMesh* InMesh,const FLightRay& InLightRay):
		Mesh(InMesh),
		LightRay(InLightRay)
	{}

	// kDOP data provider interface.

	FORCEINLINE const FVector4& GetVertex(uint32 Index) const
	{
		return Mesh->Vertices[Index];
	}

	FORCEINLINE FVector2D GetUV(uint32 Index) const
	{
		return Mesh->UVs[Index];
	}

	FORCEINLINE FVector2D GetLightmapUV(uint32 Index) const
	{
		return Mesh->LightmapUVs[Index];
	}
/*
	FORCEINLINE UMaterialInterface* GetMaterial(uint32 MaterialIndex) const
	{
		return NULL;
	}
*/
	FORCEINLINE int32 GetItemIndex(uint32 MaterialIndex) const
	{
		return MaterialIndex;
	}

	FORCEINLINE const TkDOPTree<const FStaticLightingAggregateMeshDataProvider,uint32>& GetkDOPTree(void) const
	{
		return Mesh->kDopTree;
	}

	FORCEINLINE const FMatrix& GetLocalToWorld(void) const
	{
		return FMatrix::Identity;
	}

	FORCEINLINE const FMatrix& GetWorldToLocal(void) const
	{
		return FMatrix::Identity;
	}

	FORCEINLINE FMatrix GetLocalToWorldTransposeAdjoint(void) const
	{
		return FMatrix::Identity;
	}

	FORCEINLINE float GetDeterminant(void) const
	{
		return 1.0f;
	}

private:

	const FDefaultAggregateMesh* Mesh;
	const FLightRay& LightRay;
};

/**
 * Checks a light ray for intersection with the shadow mesh.
 * @param LightRay - The line segment to check for intersection.
 * @param bFindClosestIntersection - true if the intersection must return the closest intersection.  false if it may return any intersection.
 *		This can be used as an optimization for rays which only need to know if there was an intersection or not, but not any other information about the intersection.
 *		Note: bFindClosestIntersection == false currently does not handle masked materials correctly, it treats them as if they were opaque.
 *		However, bFindClosestIntersection == false does work correctly in conjunction with LIGHTRAY_STATIC_AND_OPAQUEONLY.
 * @param bCalculateTransmission - Whether to keep track of transmission or not.  If this is true, bFindClosestIntersection must also be true.
 * @param bDirectShadowingRay - Whether this ray is being used to calculate direct shadowing.
 * @param CoherentRayCache - The calling thread's collision cache.
 * @param [out] Intersection - The intersection of between the light ray and the mesh.
 * @return true if there is an intersection, false otherwise
 */
bool FDefaultAggregateMesh::IntersectLightRay(
	const FLightRay& LightRay,
	bool bFindClosestIntersection,
	bool bCalculateTransmission,
	bool bDirectShadowingRay,
	FCoherentRayCache& CoherentRayCache,
	FLightRayIntersection& ClosestIntersection) const
{
	LIGHTINGSTAT(FScopedRDTSCTimer RayTraceTimer(bFindClosestIntersection ? CoherentRayCache.FirstHitRayTraceTime : CoherentRayCache.BooleanRayTraceTime);)
	bFindClosestIntersection ? CoherentRayCache.NumFirstHitRaysTraced++ : CoherentRayCache.NumBooleanRaysTraced++;
	// Calculating transmission requires finding the closest intersection for now
	//@todo - allow boolean visibility tests while calculating transmission
	checkSlow(!bCalculateTransmission || bFindClosestIntersection);

	ClosestIntersection.bIntersects = false;
	FLinearColor Transmission(FLinearColor::White);

	//@todo - remove this hack which prevents infinite looping in some levels.
	const int32 MaxNumIterativeIntersections = 20;
	int32 NumIterativeIntersections = 0;
	do 
	{
		FLightRay ClippedLightRay = LightRay;
		if (ClosestIntersection.bIntersects)
		{
			NumIterativeIntersections++;
			// Clip the ray so it is now from the intersection point (plus some amount to avoid intersecting the same triangle again) to the original end of the ray
			ClippedLightRay.ClipAgainstIntersectionFromEnd(ClosestIntersection.IntersectionVertex.WorldPosition + Scene.SceneConstants.VisibilityRayOffsetDistance * ClippedLightRay.Direction.GetUnsafeNormal3());
			
			if ((ClosestIntersection.Mesh == LightRay.Mesh && ((ClosestIntersection.Mesh->LightingFlags & GI_INSTANCE_SELFSHADOWDISABLE) || (LightRay.TraceFlags & LIGHTRAY_SELFSHADOWDISABLE)))
				|| (bDirectShadowingRay && ClosestIntersection.Mesh->IsIndirectlyShadowedOnly(ClosestIntersection.ElementIndex))
				|| (ClosestIntersection.Mesh != LightRay.Mesh && (ClosestIntersection.Mesh->LightingFlags & GI_INSTANCE_SELFSHADOWONLY))
				|| !ClosestIntersection.Mesh->IsSurfaceDomain(ClosestIntersection.ElementIndex))
			{
				// Nothing more to do if we're just avoiding shadowing based on trace flags, just keep going
			}
			else if (ClosestIntersection.Mesh->IsMasked(ClosestIntersection.ElementIndex) || (bDirectShadowingRay && ClosestIntersection.Mesh->IsCastingShadowsAsMasked(ClosestIntersection.ElementIndex)))
			{
				// look to see if we hit a hole or opaque part
 				if (ClosestIntersection.Mesh->EvaluateMaskedCollision(ClosestIntersection.IntersectionVertex.TextureCoordinates[0], ClosestIntersection.ElementIndex))
				{
					// Hit an opaque part of a masked mesh, terminate the ray intersection
					break;
				}
			}
			else if (bCalculateTransmission)
			{
				const FLinearColor NewTransmission = ClosestIntersection.Mesh->EvaluateTransmission(
					ClosestIntersection.IntersectionVertex.TextureCoordinates[0], 
					ClosestIntersection.ElementIndex);

				// Accumulate the total transmission along the ray
				// The result is order independent so the intersections don't have to be strictly front to back
				Transmission *= NewTransmission;
			}
			ClosestIntersection.bIntersects = false;
		}

		// Check the kDOP containing low polygon meshes first.
		FHitResult Result;
		FStaticLightingAggregateMeshDataProvider kDOPDataProvider(this, ClippedLightRay);
		TkDOPLineCollisionCheck<const FStaticLightingAggregateMeshDataProvider,uint32> kDOPCheck(
			ClippedLightRay.Start,
			ClippedLightRay.Start + ClippedLightRay.Direction * ClippedLightRay.Length,
			bFindClosestIntersection,
			(LightRay.TraceFlags & LIGHTRAY_STATIC_AND_OPAQUEONLY) != 0,
			!bDirectShadowingRay,
			(LightRay.TraceFlags & LIGHTRAY_FLIP_SIDEDNESS) != 0,
			kDOPDataProvider,
			LightRay.Mapping ? LightRay.Mapping->Mesh->MeshIndex : INDEX_NONE,
			LightRay.Mapping ? LightRay.Mapping->Mesh->GetLODIndices() : INDEX_NONE,
			LightRay.Mapping ? LightRay.Mapping->Mesh->GetHLODRange() : INDEX_NONE,
			&Result);

		bool bHit = false; 
		if (!bFindClosestIntersection && CoherentRayCache.kDOPNodeIndex != 0xFFFFFFFF)
		{
			TTraversalHistory<uint32> History;
			// Trace against the last hit node if we're doing a boolean visibility check before traversing the whole tree
			// Provides a small speedup with coherent boolean visibility rays (1.1x faster for precomputed visibility)
			bHit = kDopTree.Nodes[CoherentRayCache.kDOPNodeIndex].LineCheck(kDOPCheck, History.AddNode(CoherentRayCache.kDOPNodeIndex));
		}

		if (!bHit)
		{
			bHit = kDopTree.LineCheck(kDOPCheck);
		}

		if (bHit)
		{
			// Setup a vertex to represent the intersection.
			FMinimalStaticLightingVertex IntersectionVertex;
			IntersectionVertex.WorldPosition = ClippedLightRay.Start + ClippedLightRay.Direction * ClippedLightRay.Length * Result.Time;
			IntersectionVertex.WorldTangentZ = kDOPCheck.LocalHitNormal;
			const FTriangleSOAPayload& Payload = TrianglePayloads[ Result.Item ];
			const FVector4& v1 = kDOPDataProvider.GetVertex(Payload.VertexIndex[0]);
			const FVector4& v2 = kDOPDataProvider.GetVertex(Payload.VertexIndex[1]);
			const FVector4& v3 = kDOPDataProvider.GetVertex(Payload.VertexIndex[2]);
			const FVector4 LocalHitPosition = kDOPCheck.LocalStart + kDOPCheck.LocalDir * Result.Time;
			FVector4 BaryCentricWeights;
			//@todo - why is such a huge tolerance needed?  Reuse the barycentric coords calculated by the ray-triangle intersection instead of deriving them from the hit position.
			//@todo - why does this sometimes fail if there was an intersection?
			if (bFindClosestIntersection && GetBarycentricWeights(v1, v2, v3, LocalHitPosition, KINDA_SMALL_NUMBER * 100.0f, BaryCentricWeights))
			{
				const FVector2D& UV1 = kDOPDataProvider.GetUV(Payload.VertexIndex[0]);
				const FVector2D& UV2 = kDOPDataProvider.GetUV(Payload.VertexIndex[1]);
				const FVector2D& UV3 = kDOPDataProvider.GetUV(Payload.VertexIndex[2]);
				// Interpolate the material texture coordinates to the intersection point
				//@todo - only lookup and interpolate UV's if needed
				IntersectionVertex.TextureCoordinates[0] = UV1 * BaryCentricWeights.X + UV2 * BaryCentricWeights.Y + UV3 * BaryCentricWeights.Z;
				const FVector2D& LightmapUV1 = kDOPDataProvider.GetLightmapUV(Payload.VertexIndex[0]);
				const FVector2D& LightmapUV2 = kDOPDataProvider.GetLightmapUV(Payload.VertexIndex[1]);
				const FVector2D& LightmapUV3 = kDOPDataProvider.GetLightmapUV(Payload.VertexIndex[2]);
				// Interpolate the lightmap texture coordinates to the intersection point
				IntersectionVertex.TextureCoordinates[1] = LightmapUV1 * BaryCentricWeights.X + LightmapUV2 * BaryCentricWeights.Y + LightmapUV3 * BaryCentricWeights.Z;
			}
			else
			{
				IntersectionVertex.TextureCoordinates[0] = FVector2D(0,0);
				IntersectionVertex.TextureCoordinates[1] = FVector2D(0,0);
			}

			ClosestIntersection = FLightRayIntersection(true, IntersectionVertex, Payload.MeshInfo->Mesh, Payload.Mapping, Payload.ElementIndex);
			if (bFindClosestIntersection)
			{
				ClippedLightRay.ClipAgainstIntersectionFromStart(ClosestIntersection.IntersectionVertex.WorldPosition);
			}
			else
			{
				// Store off the hit node so future boolean visibility rays can test against that first
				CoherentRayCache.kDOPNodeIndex = kDOPCheck.HitNodeIndex;
				//@todo - handle masked materials correctly with !bFindClosestIntersection
				return true;
			}
		}
	} 
	// Continue tracing as long as we are intersecting meshes that might need to restart the ray
	while (ClosestIntersection.bIntersects 
		&& (ClosestIntersection.Mesh->IsTranslucent(ClosestIntersection.ElementIndex) ||
			ClosestIntersection.Mesh->IsMasked(ClosestIntersection.ElementIndex) ||
			(ClosestIntersection.Mesh == LightRay.Mesh && ((ClosestIntersection.Mesh->LightingFlags & GI_INSTANCE_SELFSHADOWDISABLE) || (LightRay.TraceFlags & LIGHTRAY_SELFSHADOWDISABLE))) ||
			// Continue tracing if we are only allowed to self shadow and intersected a different mesh
			(ClosestIntersection.Mesh != LightRay.Mesh && (ClosestIntersection.Mesh->LightingFlags & GI_INSTANCE_SELFSHADOWONLY)) ||
			(bDirectShadowingRay && ClosestIntersection.Mesh->IsIndirectlyShadowedOnly(ClosestIntersection.ElementIndex)))
		&& NumIterativeIntersections < MaxNumIterativeIntersections);

	if (NumIterativeIntersections >= MaxNumIterativeIntersections)
	{
		ClosestIntersection.bIntersects = false;
	}
	// Must not return an intersection with a translucent mesh
	checkSlow(!ClosestIntersection.bIntersects 
		|| (!ClosestIntersection.Mesh->IsTranslucent(ClosestIntersection.ElementIndex) && !ClosestIntersection.Mesh->IsSurfaceDomain(ClosestIntersection.ElementIndex))
		|| bDirectShadowingRay && ClosestIntersection.Mesh->IsCastingShadowsAsMasked(ClosestIntersection.ElementIndex));
	ClosestIntersection.Transmission = Transmission;
	return ClosestIntersection.bIntersects;
}

const FStaticLightingMesh* FDefaultAggregateMesh::IntersectBox(const FBox Box)  const
{
	FLightRay UnusedRay;
	FStaticLightingAggregateMeshDataProvider kDOPDataProvider(this, UnusedRay);
	TkDOPBoxCollisionCheck<const FStaticLightingAggregateMeshDataProvider, uint32> kDOPCheck(
		Box,
		kDOPDataProvider);

	kDopTree.BoxCheck(kDOPCheck);

	if (kDOPCheck.Item != -1)
	{
		const FTriangleSOAPayload& Payload = TrianglePayloads[kDOPCheck.Item];
		return Payload.MeshInfo->Mesh;
	}
	else
	{
		return nullptr;
	}
}

} //namespace Lightmass
