From ac531012148caf3787133a79b9e0b7665afa64ea Mon Sep 17 00:00:00 2001
From: gm-matthew <108370479+gm-matthew@users.noreply.github.com>
Date: Thu, 16 Nov 2023 23:03:21 +0000
Subject: [PATCH] Implement LOD blending If two translucent polygons with
 opposing patterns overlap the result is always opaque Also the LOD scale
 calculation depends on Euclidean distance of x, y and z, not just z

---
 Src/Graphics/New3D/New3D.cpp           | 63 ++++++++++++++++++--------
 Src/Graphics/New3D/R3DFrameBuffers.cpp | 20 ++++----
 2 files changed, 54 insertions(+), 29 deletions(-)

diff --git a/Src/Graphics/New3D/New3D.cpp b/Src/Graphics/New3D/New3D.cpp
index f69b1d0..5ed5b19 100644
--- a/Src/Graphics/New3D/New3D.cpp
+++ b/Src/Graphics/New3D/New3D.cpp
@@ -714,46 +714,69 @@ void CNew3D::DescendCullingNode(UINT32 addr)
 		}
 	}
 
-	float LODscale = fBlendRadius * m_nodeAttribs.currentModelScale / std::abs(m_modelMat.currentMatrix[14]);
+	float LODscale;
+	if (m_nodeAttribs.currentDisableCulling)
+		LODscale = FLT_MAX;
+	else
+	{
+		float distance = std::hypot(m_modelMat.currentMatrix[12], m_modelMat.currentMatrix[13], m_modelMat.currentMatrix[14]);
+		LODscale = fBlendRadius * m_nodeAttribs.currentModelScale / distance;
+	}
 
 	const LODFeatureType& lodTableEntry = m_LODBlendTable->table[lodTablePointer];
 
-	if (m_nodeAttribs.currentDisableCulling)
-	{
-		m_nodeAttribs.currentModelAlpha = 1.0f;
-	}
-	else
-	{
-		float nodeAlpha = lodTableEntry.lod[3].blendFactor * (LODscale - lodTableEntry.lod[3].deleteSize);
-		nodeAlpha = std::clamp(nodeAlpha, 0.0f, 1.0f);
-		m_nodeAttribs.currentModelAlpha *= nodeAlpha;	// alpha of each node multiples by the alpha of its parent
-	}
-
-	if (m_nodeAttribs.currentClipStatus != Clip::OUTSIDE && m_nodeAttribs.currentModelAlpha > 0.0f) {
+	if (m_nodeAttribs.currentClipStatus != Clip::OUTSIDE && LODscale >= lodTableEntry.lod[3].deleteSize) {
 
 		// Descend down first link
 		if ((node[0x00] & 0x08))	// 4-element LOD table
 		{
 			lodPtr = TranslateCullingAddress(child1Ptr);
 
-			// determine which LOD to use; we do not currently blend between LODs
-			int modelLOD;
-			for (modelLOD = 0; modelLOD < 3; modelLOD++)
+			if (NULL != lodPtr)
 			{
-				if (LODscale >= lodTableEntry.lod[modelLOD].deleteSize)
-					break;
-			}
+				int modelLOD;
+				for (modelLOD = 0; modelLOD < 3; modelLOD++)
+				{
+					if (LODscale >= lodTableEntry.lod[modelLOD].deleteSize && lodPtr[modelLOD] & 0x1000000)
+						break;
+				}
 
-			if (NULL != lodPtr) {
+				float tempAlpha = m_nodeAttribs.currentModelAlpha;
+
+				float nodeAlpha = lodTableEntry.lod[modelLOD].blendFactor * (LODscale - lodTableEntry.lod[modelLOD].deleteSize);
+				nodeAlpha = std::clamp(nodeAlpha, 0.0f, 1.0f);
+				if (nodeAlpha > 15.0f / 16.0f)		// shader discards pixels below 1/16 alpha
+					nodeAlpha = 1.0f;
+				else if (nodeAlpha < 1.0f / 16.0f)
+					nodeAlpha = 0.0f;
+				m_nodeAttribs.currentModelAlpha *= nodeAlpha;	// alpha of each node multiples by the alpha of its parent
+				
 				if ((node[0x03 - m_offset] & 0x20000000)) {
 					DescendCullingNode(lodPtr[modelLOD] & 0xFFFFFF);
+
+					if (nodeAlpha < 1.0f && modelLOD != 3)
+					{
+						m_nodeAttribs.currentModelAlpha = (1.0f - nodeAlpha) * tempAlpha;
+						DescendCullingNode(lodPtr[modelLOD+1] & 0xFFFFFF);
+					}
 				}
 				else {
 					DrawModel(lodPtr[modelLOD] & 0xFFFFFF);
+
+					if (nodeAlpha < 1.0f && modelLOD != 3)
+					{
+						m_nodeAttribs.currentModelAlpha = (1.0f - nodeAlpha) * tempAlpha;
+						DrawModel(lodPtr[modelLOD + 1] & 0xFFFFFF);
+					}
 				}
 			}
 		}
 		else {
+
+			float nodeAlpha = lodTableEntry.lod[3].blendFactor * (LODscale - lodTableEntry.lod[3].deleteSize);
+			nodeAlpha = std::clamp(nodeAlpha, 0.0f, 1.0f);
+			m_nodeAttribs.currentModelAlpha *= nodeAlpha;	// alpha of each node multiples by the alpha of its parent
+
 			DescendNodePtr(child1Ptr);
 		}
 
diff --git a/Src/Graphics/New3D/R3DFrameBuffers.cpp b/Src/Graphics/New3D/R3DFrameBuffers.cpp
index b91fd7c..7461b01 100644
--- a/Src/Graphics/New3D/R3DFrameBuffers.cpp
+++ b/Src/Graphics/New3D/R3DFrameBuffers.cpp
@@ -274,16 +274,18 @@ void R3DFrameBuffers::AllocShaderTrans()
 	{
 		vec4 colTrans1 = texture(tex1, fsTexCoord);
 		vec4 colTrans2 = texture(tex2, fsTexCoord);
-
-		if(colTrans1.a+colTrans2.a > 0.0) {
-			vec3 col1 = colTrans1.rgb * colTrans1.a;
-			vec3 col2 = colTrans2.rgb * colTrans2.a;
-
-			colTrans1 = vec4((col1+col2) / (colTrans1.a + colTrans2.a), // this is my best guess at the blending between the layers
-							 colTrans1.a+colTrans2.a);
+			
+		// if both transparency layers overlap, the result is opaque
+		if (colTrans1.a * colTrans2.a > 0.0) {
+			vec3 mixCol = mix(colTrans1.rgb, colTrans2.rgb, (colTrans2.a + (1.0 - colTrans1.a)) / 2.0);
+			fragColor = vec4(mixCol, 1.0);
+		}
+		else if (colTrans1.a > 0.0) {
+			fragColor = colTrans1;
+		}
+		else {
+			fragColor = colTrans2;		// if alpha is zero it will have no effect anyway
 		}
-		
-		fragColor = colTrans1;
 	}
 
 	)glsl";