//================================================================
// E3D
//
// Inline ray-tracing utility functions
//
// AUTHOR:  Gabor Nagy
// DATE:    2025-Jan-22 01:25:36
//
// Copyright (C) 1996-2026 by Gabor Nagy. All rights reserved.
//================================================================


#define _M_SampleWithOffsetRay(pVIndex) do\
{\
	const E3dCoordinate  lT = E3dRay_IntersectTriangle_KnownnHit_UV(&LMipRay, PRTriangle, &LUVs[pVIndex]);\
\
	E3dAnisoPoint*  lAnisoPoint = LAnisoPoints + pVIndex;\
	E3dCoord3*      lPosition = &lAnisoPoint->Position;\
\
/* Set hit position, for procedural texture-mappers	*/\
	E_AddScaled3_vvp(LMipRay.Origin, LMipRay.Direction, lT,  lPosition);\
} while(0)


//================================================================================
// Sample RTriangle at the given point
// Assumes PUV to be set
//================================================================================
static inline void _E3dRTriangle_Sample(E3dRayTracer_thread* PThread,
 const E3dRPolyGroup* PRPolyGroup, const E3dRTriangle* PRTriangle, const E3dCoord2* PUV,
 E3dGridRay* PRay, E3dSurfacePoint* PPoint,
 const E3dMaterial* PMaterial, const EBool PHasMIPMapping, const int PNAnisotropicTextureSamples)
{
	const E3dVertexNodeSize  LRVertexNodeSize = PRPolyGroup->RVertexNodeSize;

// Interpolate Normal and Tangent
//
	const uint8_t*  LRVertexNodes8 = (uint8_t*)PRTriangle->RVertexNodes;

	E3dCoord3  LNormal;

	{
		const E3dCoordinate  LU = PUV->X, LV = PUV->Y, LNUV = 1.0f - LU - LV;

		const E3dVertexNodeSize  LRNormalOffset = PRPolyGroup->NormalOffsetNew;if(LRNormalOffset >= 0)
		{
			const E3dCoord3*  LNormal0 = (E3dCoord3*)(LRVertexNodes8 + LRVertexNodeSize * 0 + LRNormalOffset);
			const E3dCoord3*  LNormal1 = (E3dCoord3*)(LRVertexNodes8 + LRVertexNodeSize * 1 + LRNormalOffset);
			const E3dCoord3*  LNormal2 = (E3dCoord3*)(LRVertexNodes8 + LRVertexNodeSize * 2 + LRNormalOffset);
			LNormal = E3d_BaryNormalize3_p(LNormal0, LNormal1, LNormal2,  LU, LV, LNUV);
		}
		else LNormal = PRTriangle->Normal;

		PPoint->Normal = LNormal;

		{
//printf("%s() %s:%d  %sTangent check%s", __FUNCTION__, __FILE__, __LINE__, ECodeGreen, ECodeNormal);fflush(stdout);
			const E3dVertexNodeSize  LOffset = PRPolyGroup->TangentOffsetNew;if(LOffset >= 0)
			{
				E3d_BaryNormalize3_sp(PRTriangle->RVertexNodes, LRVertexNodeSize, LOffset,  LU, LV, LNUV,  &PPoint->Tangent);
/*
				const E3dCoord3  LTangent0 = E3d_Bary3_vvo(PRTriangle->RVertexNodes, LRVertexNodeSize, LOffset,  LU, LV, LNUV);
				E3dCoord3  LTangent;E_Cross3_vvv(LNormal, LTangent0, LTangent);
				E3d_Normalize3_v(LTangent);
				PPoint->Tangent = LTangent;
*/

//printf("  UV % 7.3f % 7.3f  % 7.3f,% 7.3f,% 7.3f\n",  LU, LV,  XYZ_s(PPoint->Tangent));fflush(stdout);
//PPoint->Tangent = E3dCoord3_s(0.0, 0.0, 1.0);
			}
			else
			{
//printf(" - %sNONE%s\n", ECodeGreen, ECodeNormal);fflush(stdout);
				_E3d_GenerateTangent_vv(PPoint->Normal, PPoint->Tangent);
//				PPoint->Tangent = E3dCoord3_s(0.0, 0.0, 1.0);
			}
		}

		{
			const E3dVertexNodeSize  LOffset = PRPolyGroup->ColorOffsetNew;
			if(LOffset >= 0) E3d_BaryFloats_sp(PRTriangle->RVertexNodes, LRVertexNodeSize, LOffset, 4,  LU, LV, LNUV,  &PPoint->Color);
			else PPoint->Color = EcRGBAf32Color_s(1.0, 1.0, 1.0, 1.0);
		}
	}


	E3dRTriangle_MapTextures(PRTriangle,  &PPoint->Position, &PPoint->Normal,  PUV, PMaterial, PPoint->STs);

	const E3dTextureMapperIndex  LTxMN = PRPolyGroup->TextureMappers.Count;

	if(PRay->HasSubRays)
	{
		if(PNAnisotropicTextureSamples > 0)
		{
			E3dGridRay      LMipRay;LMipRay.Origin = PRay->Origin;
			E3dCoord2*      LUVs = PThread->Aniso.UVs;
			E3dAnisoPoint*  LAnisoPoints = PThread->Aniso.Points;

			{
				int  LI = 0;for(;LI < PNAnisotropicTextureSamples;LI++)
				{
					LMipRay.Direction = PRay->Directions[LI];
					const E3dCoordinate  LT = E3dRay_IntersectTriangle_KnownnHit_UV(&LMipRay, PRTriangle, LUVs + LI);
					{
						E3dAnisoPoint*  LAnisoPoint = LAnisoPoints + LI;
						E3dCoord3*      LPosition = &LAnisoPoint->Position;
						E_AddScaled3_vvp(LMipRay.Origin, LMipRay.Direction, LT,  LPosition);
					}
				}
			}

			E3dTextureMapperIndex  LTxMI = 0;E3dSTWithDiff*  LST = PPoint->STs;for(;LTxMI < LTxMN;LTxMI++, LST++)
			{
				const E3dTextureMapper*       LTextureMapper = PRPolyGroup->TextureMappers.Items[LTxMI];
				const E3dTextureMapperClass*  LTextureMapperClass = LTextureMapper->Class;

				if(LTextureMapperClass->MapPoint)
				{
					int  LI = 0;for(;LI < PNAnisotropicTextureSamples;LI++)
					{
						LTextureMapperClass->MapPoint(LTextureMapper, &LAnisoPoints[LI].Position, &PPoint->Normal /* E3dTEXTUREMAPPINGFIXME */, LST->STs + LI);
					}
				}
				else
				{
					const E3dVertexNodeSize  LST0Offset = PRPolyGroup->ST0OffsetNew;
					const E3dST*  LST0 = (E3dST*)(LRVertexNodes8 + LRVertexNodeSize * 0 + LST0Offset) + LTxMI;
					const E3dST*  LST1 = (E3dST*)(LRVertexNodes8 + LRVertexNodeSize * 1 + LST0Offset) + LTxMI;
					const E3dST*  LST2 = (E3dST*)(LRVertexNodes8 + LRVertexNodeSize * 2 + LST0Offset) + LTxMI;

					int  LI = 0;const E3dCoord2*  LUV = LUVs;for(;LI < PNAnisotropicTextureSamples;LI++, LUV++)
					{
						const E3dCoordinate  LU = LUV->X, LV = LUV->Y, LNUV = 1.0 - LU - LV;
						E3dCoordinate        LS, LT;

						LS = LNUV * LST0->S + LST1->S * LU + LST2->S * LV;
						LT = LNUV * LST0->T + LST1->T * LU + LST2->T * LV;
						LST->STs[LI].S = LS;
						LST->STs[LI].T = LT;
					}
				}
				LST->NumOfSTs = PNAnisotropicTextureSamples;
			}
		}

		if(PHasMIPMapping)
		{
// MIP-mapping with pixel-corner rays
//
			E3dGridRay      LMipRay;LMipRay.Origin = PRay->Origin;
			E3dCoord2*      LUVs = PThread->Aniso.UVs;
			E3dAnisoPoint*  LAnisoPoints = PThread->Aniso.Points;
			E3dST*          LSTs = PThread->Aniso.STs;

			_E3d_MIPMap(PRay, PRTriangle, 0);
			_E3d_MIPMap(PRay, PRTriangle, 1);
			_E3d_MIPMap(PRay, PRTriangle, 2);
			_E3d_MIPMap(PRay, PRTriangle, 3);

			if(LTxMN > 0)
			{
				EBool  LNeedsTxTangent[LTxMN];EClr(LNeedsTxTangent);

				{
					EIndex  LI = 0;const EIndex  LN = PMaterial->Textures.Count;const E3dTextureMapperIndex*  LSTIndices = PRPolyGroup->STIndices.Items;for(;LI < LN;LI++)
					{
						const E3d2DTexture*  LTexture = PMaterial->Textures.Items[LI];
						if(LTexture->DoNormalMap && LTexture->NormalMapType == E3dTANGENT_SPACE) LNeedsTxTangent[LSTIndices[LI]] = TRUE;
					}
				}

				E3dTextureMapperIndex  LTxMI = 0;E3dSTWithDiff*  LST = PPoint->STs;for(;LTxMI < LTxMN;LTxMI++, LST++)
				{
					int  LI = 0;for(;LI < 4;LI++) E3d_MapTextureNoNormalize(PRTriangle, PMaterial, LTxMI, LNeedsTxTangent[LTxMI],  &LAnisoPoints[LI].Position, &PPoint->Normal /* E3dTEXTUREMAPPINGFIXME */,  LUVs + LI, LSTs + LI);

					E3dST  LSpan;

					E3d_Span4_vv(LSTs, S, LSpan);
					E3d_Span4_vv(LSTs, T, LSpan);

					LST->dS = LSpan.S;
					LST->dT = LSpan.T;
				}
			}
		}
	}

	switch(PMaterial->Textures.Count)
	{
		case 0:
// Nothing to do here, we just need to keep this case separate from "default:"
		break;

		default:
			_E3d_SampleTextures(PMaterial, PRTriangle, (E3dSurfacePointBase*)PPoint);
		break;
	}
}


//================================================
// Sample RTriangle at the given point
// -Assumes LUV to be set
// -Uses ray differentials for texture filtering
//================================================
static inline const E3dMaterial* _E3dRTriangle_SampleD(E3dRayTracer_thread* PRayTracer_thread,
 const E3dRPolyGroup* PRPolyGroup, const E3dRTriangle* PRTriangle, const E3dCoord2* PUV,
 E3dStackRay* PRay, const EBool PRayHasDifferentials, E3dRayDifferentialsTmp* PRayDiffTmp,
 E3dSurfacePoint* PPoint, const E3dMaterial* PSurfaceMaterial,
 const EBool PHasMIPMapping, const int PNAnisotropicTextureSamples,
 const E3dCamera* PCamera)
{
	const E3dShader*         LSurfaceShader = PSurfaceMaterial->Shader;
	const E3dVertexNodeSize  LRVertexNodeSize = PRPolyGroup->RVertexNodeSize;

// Interpolate Normal and Tangent
//
	const E3dCoord3*  LNormal0;const E3dCoord3*  LNormal1;const E3dCoord3*  LNormal2;
	const uint8_t*    LRVertexNodes8 = (uint8_t*)PRTriangle->RVertexNodes;
	E3dCoord3         LNormal;

	{
		const E3dCoordinate  LU = PUV->X, LV = PUV->Y, LNUV = 1.0f - LU - LV;

		const E3dVertexNodeSize  LRNormalOffset = PRPolyGroup->NormalOffsetNew;if(LRNormalOffset >= 0)
		{
			LNormal0 = (E3dCoord3*)(LRVertexNodes8 + LRVertexNodeSize * 0 + LRNormalOffset);
			LNormal1 = (E3dCoord3*)(LRVertexNodes8 + LRVertexNodeSize * 1 + LRNormalOffset);
			LNormal2 = (E3dCoord3*)(LRVertexNodes8 + LRVertexNodeSize * 2 + LRNormalOffset);
			LNormal = E3d_BaryNormalize3_p(LNormal0, LNormal1, LNormal2,  LU, LV, LNUV);
		}
		else
		{
			LNormal0 = LNormal1 = LNormal2 = &PRTriangle->Normal;
			LNormal = PRTriangle->Normal;
		}

		PPoint->Normal = LNormal;

		{
//printf("%s() %s:%d  %sTangent check%s", __FUNCTION__, __FILE__, __LINE__, ECodeGreen, ECodeNormal);fflush(stdout);
			const E3dVertexNodeSize  LOffset = PRPolyGroup->TangentOffsetNew;if(LOffset >= 0)
			{
				E3d_BaryNormalize3_sp(PRTriangle->RVertexNodes, LRVertexNodeSize, LOffset,  LU, LV, LNUV,  &PPoint->Tangent);

/*
				const E3dCoord3  LTangent0 = E3d_Bary3_vvo(PRTriangle->RVertexNodes, LRVertexNodeSize, LOffset,  LU, LV, LNUV);
				E3dCoord3  LTangent;E_Cross3_vvv(LNormal, LTangent0, LTangent);
				E3d_Normalize3_v(LTangent);
				PPoint->Tangent = LTangent;
*/

//printf("  UV % 7.3f % 7.3f  % 7.3f,% 7.3f,% 7.3f\n",  LU, LV,  XYZ_s(PPoint->Tangent));fflush(stdout);
//PPoint->Tangent = E3dCoord3_s(0.0, 0.0, 1.0);
			}
			else
			{
//printf("%s() %s:%d  %sNo tangent%s Offset %d\n", __FUNCTION__, __FILE__, __LINE__, ECodeYellow, ECodeNormal, PRPolyGroup->TangentOffsetNew);fflush(stdout);
				_E3d_GenerateTangent_vv(PPoint->Normal, PPoint->Tangent);
//				PPoint->Tangent = E3dCoord3_s(0.0, 0.0, 1.0);
			}
		}

		{
			const E3dVertexNodeSize  LOffset = PRPolyGroup->ColorOffsetNew;
			if(LOffset >= 0) E3d_BaryFloats_sp(PRTriangle->RVertexNodes, LRVertexNodeSize, LOffset, 4,  LU, LV, LNUV,  &PPoint->Color);
			else PPoint->Color = EcRGBAf32Color_s(1.0, 1.0, 1.0, 1.0);
		}
	}

	E3dRTriangle_MapTextures(PRTriangle,  &PPoint->Position, &PPoint->Normal,  PUV, PSurfaceMaterial, PPoint->STs);

	if(PRayHasDifferentials)
	{
// Calculate new dP/dx and dP/dy : Equation (12) (Igehy)
// Ray-differential : TRANSFER operation
//
		const E3dCoordinate  LDdotN = E_Dot3_vv(PRay->Direction, LNormal);
/*
		const E3dCoordinate  Lt0 = PRay->LastT;
		const E3dCoord3      E_AddScaled3i_vv(LTX0,  PRay->dP_div_dx,  PRay->dD_div_dx, Lt0);
		const E3dCoord3      E_AddScaled3i_vv(LTY0,  PRay->dP_div_dy,  PRay->dD_div_dy, Lt0);
*/
		const E3dCoordinate   Lt1 = -E_Dot3_vv(PRay->Origin, LNormal) / E_Dot3_vv(PRay->Direction, LNormal);
		E3dRayDifferentials*  LRayDifferentials = &(PRay->Differentials);
		const E3dCoord3       E_AddScaled3i_vv  (LTX1,  LRayDifferentials->dP_div_dx,  LRayDifferentials->dD_div_dx, Lt1);
		const E3dCoord3       E_AddScaled3i_vv  (LTY1,  LRayDifferentials->dP_div_dy,  LRayDifferentials->dD_div_dy, Lt1);
		const E3dCoordinate   LNumeratorX = E_Dot3_vv(LTX1, LNormal), LNumeratorY = E_Dot3_vv(LTY1, LNormal);
		const E3dCoordinate   Ldt_div_dx = LNumeratorX / -LDdotN, Ldt_div_dy = LNumeratorY / -LDdotN;
		const E3dCoord3       E_AddScaled3i_vv  (LdP_div_dx,  LTX1,  PRay->Direction, Ldt_div_dx);
		const E3dCoord3       E_AddScaled3i_vv  (LdP_div_dy,  LTY1,  PRay->Direction, Ldt_div_dy);


// Store new ray origin derivatives (dP/dx and dP/dy)
// The ray-direction derivatives don't change on TRANSFER.
//
		LRayDifferentials->dP_div_dx = LdP_div_dx;
		LRayDifferentials->dP_div_dy = LdP_div_dy;


// Derivatives of the position are homogeneous coordinates (W = 0)
//
		const E3dCoordinate  L0dotPx = E_Dot3_vv(PRTriangle->L0, LdP_div_dx);
		const E3dCoordinate  L1dotPx = E_Dot3_vv(PRTriangle->L1, LdP_div_dx);
		const E3dCoordinate  L2dotPx = E_Dot3_vv(PRTriangle->L2, LdP_div_dx);
		const E3dCoordinate  L0dotPy = E_Dot3_vv(PRTriangle->L0, LdP_div_dy);
		const E3dCoordinate  L1dotPy = E_Dot3_vv(PRTriangle->L1, LdP_div_dy);
		const E3dCoordinate  L2dotPy = E_Dot3_vv(PRTriangle->L2, LdP_div_dy);

/*
const E3dCamera*  LCamera = LRenderer->Camera;const E3dOptics*  LOptics = LCamera->Optics;
const EBool       LVerbose = LOptics->PixelPosition.X == 200 && LOptics->PixelPosition.Y == 64;
*/

		if(0)
		{
			printf("dP/dx  % 7.6f % 7.6f % 7.6f  dP/dy  % 7.6f % 7.6f % 7.6f  dD/dx  % 7.6f % 7.6f % 7.6f  dD/dy  % 7.6f % 7.6f % 7.6f\n",  XYZ_s(LRayDifferentials->dP_div_dx),  XYZ_s(LRayDifferentials->dP_div_dy),  XYZ_s(LRayDifferentials->dD_div_dx),  XYZ_s(LRayDifferentials->dD_div_dy));fflush(stdout);
		}

		if(PHasMIPMapping)
		{
			E3dOptics*           LOptics = PCamera->Optics;
			const E3dCoordinate  LDeltaX = LOptics->Pixel_d.X, LDeltaY = LOptics->Pixel_d.Y;
//		EThreadSet*          LThreadSet = &PRenderer->ThreadSet;E3dTHREAD_LOCK

			if(PRay->HasSubRays)
			{
// MIP-mapping with pixel-corner rays
// This is more accurate than ray differentials, so if we have it (primary rays), use it.
//
				E3dGridRay      LMipRay;
				E3dCoord2*      LUVs = PRayTracer_thread->Aniso.UVs;
				E3dAnisoPoint*  LAnisoPoints = PRayTracer_thread->Aniso.Points;
				E3dST*          LSTs = PRayTracer_thread->Aniso.STs;

				if(0)
				{
					LMipRay.Origin = PRay->Origin;
					LMipRay.Direction = PRay->Direction;
					_M_SampleWithOffsetRay(0);

					E_AddScaled3_vvv(PRay->Origin,    LRayDifferentials->dP_div_dx, LDeltaX, LMipRay.Origin);
					E_AddScaled3_vvv(PRay->Direction, LRayDifferentials->dD_div_dx, LDeltaX, LMipRay.Direction);
					_M_SampleWithOffsetRay(1);

					E_AddScaled3_vvv(PRay->Origin,    LRayDifferentials->dP_div_dy, LDeltaY, LMipRay.Origin);
					E_AddScaled3_vvv(PRay->Direction, LRayDifferentials->dD_div_dy, LDeltaY, LMipRay.Direction);
					_M_SampleWithOffsetRay(2);

					E_AddScaled3_vvv(LMipRay.Origin,    LRayDifferentials->dP_div_dx, LDeltaX, LMipRay.Origin);
					E_AddScaled3_vvv(LMipRay.Direction, LRayDifferentials->dD_div_dx, LDeltaX, LMipRay.Direction);
					_M_SampleWithOffsetRay(3);
				}

				if(1)
				{
					LMipRay.Origin = PRay->Origin;
					_E3d_MIPMap(PRay, PRTriangle, 0);
					_E3d_MIPMap(PRay, PRTriangle, 1);
					_E3d_MIPMap(PRay, PRTriangle, 2);
					_E3d_MIPMap(PRay, PRTriangle, 3);
				}

				const E3dTextureMapperIndex  LTxMN = PRPolyGroup->TextureMappers.Count;if(LTxMN > 0)
				{
					EBool  LNeedsTxTangent[LTxMN];EClr(LNeedsTxTangent);

					{
						EIndex  LI = 0;const EIndex  LN = PSurfaceMaterial->Textures.Count;const E3dTextureMapperIndex*  LSTIndices = PRPolyGroup->STIndices.Items;for(;LI < LN;LI++)
						{
							const E3d2DTexture*  LTexture = PSurfaceMaterial->Textures.Items[LI];
							if(LTexture->DoNormalMap && LTexture->NormalMapType == E3dTANGENT_SPACE) LNeedsTxTangent[LSTIndices[LI]] = TRUE;
						}
					}

					E3dTextureMapperIndex  LTxMI = 0;E3dSTWithDiff*  LST = PPoint->STs;for(;LTxMI < LTxMN;LTxMI++, LST++)
					{
						{ int  LI = 0;for(;LI < 4;LI++) E3d_MapTextureNoNormalize(PRTriangle, PSurfaceMaterial, LTxMI, LNeedsTxTangent[LTxMI],  &LAnisoPoints[LI].Position, &LNormal /* TEXTUREMAPPINGFIXME */,  LUVs + LI, LSTs + LI); }

						E3dST  LSpan;

						E3d_Span4_vv(LSTs, S, LSpan);
						E3d_Span4_vv(LSTs, T, LSpan);

						LST->dS = LSpan.S;
						LST->dT = LSpan.T;

/*
	E3dCoord3  LPositionSpan;
	E3d_Span4_vv(Position, X, LPositionSpan);
	E3d_Span4_vv(Position, Y, LPositionSpan);
	E3d_Span4_vv(Position, Z, LPositionSpan);


	LPositionSpan.X /= LDeltaX;
	LPositionSpan.Y /= LDeltaX;
	LPositionSpan.Z /= LDeltaX;

	if(0) { printf("%sPosSpan% 7.6f,% 7.6f % 7.6f%s\n", ECodeYellow,  XYZ_s(LPositionSpan),  ECodeNormal);fflush(stdout); }
*/

						if(0)
						{
							printf(" %s% 7.6f, % 7.6f%s\n", ECodeGreen,  LSpan.S, LSpan.T,  ECodeNormal);fflush(stdout);
						}
					}
				}

				if(PNAnisotropicTextureSamples > 0)
				{
					LMipRay.Origin = PRay->Origin;

					{
						int  LI = 0;for(;LI < PNAnisotropicTextureSamples;LI++)
						{
							LMipRay.Direction = PRay->Directions[LI];
							const E3dCoordinate  LT = E3dRay_IntersectTriangle_KnownnHit_UV(&LMipRay, PRTriangle, LUVs + LI);
							{
								E3dAnisoPoint*	LAnisoPoint = LAnisoPoints + LI;
								E3dCoord3*      LPosition = &LAnisoPoint->Position;
								E_AddScaled3_vvp(LMipRay.Origin, LMipRay.Direction, LT,  LPosition);
							}
						}
					}

					E3dTextureMapperIndex  LTxMI = 0;E3dSTWithDiff*  LST = PPoint->STs;for(;LTxMI < LTxMN;LTxMI++, LST++)
					{
						const E3dTextureMapper*       LTextureMapper = PRPolyGroup->TextureMappers.Items[LTxMI];
						const E3dTextureMapperClass*  LTextureMapperClass = LTextureMapper->Class;

						if(LTextureMapperClass->MapPoint)
						{
							int  LI = 0;for(;LI < PNAnisotropicTextureSamples;LI++)
							{
								LTextureMapperClass->MapPoint(LTextureMapper, &LAnisoPoints[LI].Position, &PPoint->Normal /* TEXTUREMAPPINGFIXME */,  LST->STs + LI);
							}
						}
						else
						{
							const E3dVertexNodeSize  LST0Offset = PRPolyGroup->ST0OffsetNew;
							const E3dST*  LST0 = (E3dST*)(LRVertexNodes8 + LRVertexNodeSize * 0 + LST0Offset) + LTxMI;
							const E3dST*  LST1 = (E3dST*)(LRVertexNodes8 + LRVertexNodeSize * 1 + LST0Offset) + LTxMI;
							const E3dST*  LST2 = (E3dST*)(LRVertexNodes8 + LRVertexNodeSize * 2 + LST0Offset) + LTxMI;

							int  LI = 0;const E3dCoord2*  LUV = LUVs;for(;LI < PNAnisotropicTextureSamples;LI++, LUV++)
							{
								const E3dCoordinate  LU = LUV->X, LV = LUV->Y, LNUV = 1.0 - LU - LV;
								E3dCoordinate        LS, LT;

								LS = LNUV * LST0->S + LST1->S * LU + LST2->S * LV;
								LT = LNUV * LST0->T + LST1->T * LU + LST2->T * LV;
								LST->STs[LI].S = LS;
								LST->STs[LI].T = LT;
								_E3dCheckST_s(LST->STs[LI]);
							}
						}
						LST->NumOfSTs = PNAnisotropicTextureSamples;
					}
				}
			}
			else
			{
				const E3dTextureMapperIndex  LTxMN = PRPolyGroup->TextureMappers.Count;

				{
					int  LTxMI = 0;E3dSTWithDiff*  LST = PPoint->STs;for(;LTxMI < LTxMN;LTxMI++, LST++)
					{
						const E3dTextureMapper*       LTextureMapper = PRPolyGroup->TextureMappers.Items[LTxMI];
						const E3dTextureMapperClass*  LTextureMapperClass = LTextureMapper->Class;

						if(LTextureMapperClass->MapPoint)
						{
							LTextureMapperClass->MapPoint(LTextureMapper,  &PPoint->Position, &PPoint->Normal /* TEXTUREMAPPINGFIXME */,  (E3dST*)LST);
							_E3dCheckST_p(LST);
						}
						else
						{
							const E3dVertexNodeSize  LST0Offset = PRPolyGroup->ST0OffsetNew;
							const E3dST*  LST0 = (E3dST*)(LRVertexNodes8 + LRVertexNodeSize * 0 + LST0Offset) + LTxMI;
							const E3dST*  LST1 = (E3dST*)(LRVertexNodes8 + LRVertexNodeSize * 1 + LST0Offset) + LTxMI;
							const E3dST*  LST2 = (E3dST*)(LRVertexNodes8 + LRVertexNodeSize * 2 + LST0Offset) + LTxMI;

/// The position is *normalized* homogeneous coordinates (W = 1)
//
/*
							const E3dCoord3      LPosition = LPoint.Position;
							const E3dCoordinate  L0dotP = E_Dot4H_vv(PRTriangle->L0, LPosition);
							const E3dCoordinate  L1dotP = E_Dot4H_vv(PRTriangle->L1, LPosition);
							const E3dCoordinate  L2dotP = E_Dot4H_vv(PRTriangle->L2, LPosition);

							LST->S = L0dotP * LST0->S + L1dotP * LST1->S + L2dotP * LST2->S;
							LST->T = L0dotP * LST0->T + L1dotP * LST1->T + L2dotP * LST2->T;
*/
							const E3dCoord2  LdT_div_dx = E3dCoord2_s(L0dotPx * LST0->S + L1dotPx * LST1->S + L2dotPx * LST2->S,  L0dotPx * LST0->T + L1dotPx * LST1->T + L2dotPx * LST2->T);
							const E3dCoord2  LdT_div_dy = E3dCoord2_s(L0dotPy * LST0->S + L1dotPy * LST1->S + L2dotPy * LST2->S,  L0dotPy * LST0->T + L1dotPy * LST1->T + L2dotPy * LST2->T);

							if(0)
							{
								printf("L0, L1, L2  %s% 7.6f, % 7.6f, % 7.6f, % 7.6f   % 7.6f, % 7.6f, % 7.6f, % 7.6f   % 7.6f, % 7.6f, % 7.6f, % 7.6f%s\n", ECodeWhite,  XYZW_s(PRTriangle->L0),  XYZW_s(PRTriangle->L1),  XYZW_s(PRTriangle->L2),  ECodeNormal);fflush(stdout);
							}
							if(0)
							{
								printf("LxdotPx %s% 7.6f, % 7.6f, % 7.6f   LxdotPy % 7.6f, % 7.6f, % 7.6f%s\n", ECodeWhite,  L0dotPx, L1dotPx, L2dotPx,  L0dotPy, L1dotPy, L2dotPy,  ECodeNormal);fflush(stdout);
							}
							if(0)
							{
								printf(" %s% 7.6f, % 7.6f  % 7.6f, % 7.6f%s\n", ECodeWhite,  XY_s(LdT_div_dx),  XY_s(LdT_div_dy),  ECodeNormal);fflush(stdout);
							}

							const E3dCoord2  LDeltaSTx = E3dCoord2_s(E3d_Abs(LdT_div_dx.X * LDeltaX), E3d_Abs(LdT_div_dx.Y * LDeltaX));
							const E3dCoord2  LDeltaSTy = E3dCoord2_s(E3d_Abs(LdT_div_dy.X * LDeltaY), E3d_Abs(LdT_div_dy.Y * LDeltaY));

							LST->dS = E_Max(LDeltaSTx.X, LDeltaSTy.X);
							LST->dT = E_Max(LDeltaSTx.Y, LDeltaSTy.Y);
							if(0) { printf(" %s% 7.6f, % 7.6f%s  % 7.6f, % 7.6f\n", ECodeYellow,  LST->dS, LST->dT,  ECodeNormal,  LDeltaX, LDeltaY);fflush(stdout); }
						}
					}
				}


				if(PNAnisotropicTextureSamples > 0)
				{
					if(1)
					{
						int  LTxMI = 0;E3dSTWithDiff*  LST = PPoint->STs;for(LTxMI = 0;LTxMI < LTxMN;LTxMI++, LST++)
						{
							int  LI = 0;const E3dCoord2*  lTUV = PRay->UVs;for(;LI < PNAnisotropicTextureSamples;LI++, lTUV++)
							{
								const E3dCoordinate  LU = lTUV->U, LV = lTUV->V;

								LST->STs[LI].S = LST->S + LU * LST->dS;
								LST->STs[LI].T = LST->T + LV * LST->dT;
								_E3dCheckST_s(LST->STs[LI]);
							}
							LST->NumOfSTs = PNAnisotropicTextureSamples;
						}
					}

					if(0)
					{
						E3dCoord2*      LUVs = PRayTracer_thread->Aniso.UVs;
						E3dAnisoPoint*  LAnisoPoints = PRayTracer_thread->Aniso.Points;
						E3dGridRay      LMipRay;

						LMipRay.Origin = PRay->Origin;

						{
							int  LI = 0;const E3dCoord2*  lTUV = PRay->UVs;for(;LI < PNAnisotropicTextureSamples;LI++, lTUV++)
							{
								E_AddScaled3_vvv(PRay->Origin,      LRayDifferentials->dP_div_dx, LDeltaX * lTUV->U, LMipRay.Origin);
								E_AddScaled3_vvv(PRay->Direction,   LRayDifferentials->dD_div_dx, LDeltaX * lTUV->U, LMipRay.Direction);
								E_AddScaled3_vvv(LMipRay.Origin,    LRayDifferentials->dP_div_dy, LDeltaY * lTUV->V, LMipRay.Origin);
								E_AddScaled3_vvv(LMipRay.Direction, LRayDifferentials->dD_div_dy, LDeltaY * lTUV->V, LMipRay.Direction);
								_M_SampleWithOffsetRay(LI);

								const E3dCoordinate  LT = E3dRay_IntersectTriangle_KnownnHit_UV(&LMipRay, PRTriangle, LUVs + LI);
								{
									E3dAnisoPoint*  LSurfacePointT = LAnisoPoints + LI;
									E3dCoord3*      LPosition = &LSurfacePointT->Position;
									E_AddScaled3_vvp(LMipRay.Origin, LMipRay.Direction, LT,  LPosition);
								}
							}
						}

						E3dTextureMapperIndex  LTxMI = 0;E3dSTWithDiff*  LST = PPoint->STs;for(;LTxMI < LTxMN;LTxMI++, LST++)
						{
							const E3dTextureMapper*       LTextureMapper = PRPolyGroup->TextureMappers.Items[LTxMI];
							const E3dTextureMapperClass*  LTextureMapperClass = LTextureMapper->Class;

							if(LTextureMapperClass->MapPoint)
							{
								int  LI = 0;for(;LI < PNAnisotropicTextureSamples;LI++)
								{
									LTextureMapperClass->MapPoint(LTextureMapper,  &LAnisoPoints[LI].Position, &PPoint->Normal /* TEXTUREMAPPINGFIXME */,  LST->STs + LI);
								}
							}
							else
							{
								const E3dVertexNodeSize  LST0Offset = PRPolyGroup->ST0OffsetNew;
								const E3dST*  LST0 = (E3dST*)(LRVertexNodes8 + LRVertexNodeSize * 0 + LST0Offset) + LTxMI;
								const E3dST*  LST1 = (E3dST*)(LRVertexNodes8 + LRVertexNodeSize * 1 + LST0Offset) + LTxMI;
								const E3dST*  LST2 = (E3dST*)(LRVertexNodes8 + LRVertexNodeSize * 2 + LST0Offset) + LTxMI;

								int  LI = 0;const E3dCoord2*  LUV = LUVs;for(;LI < PNAnisotropicTextureSamples;LI++, LUV++)
								{
									const E3dCoordinate  LU = LUV->X, LV = LUV->Y, LNUV = 1.0 - LU - LV;
									const E3dCoordinate  LS = LNUV * LST0->S + LST1->S * LU + LST2->S * LV;
									const E3dCoordinate  LT = LNUV * LST0->T + LST1->T * LU + LST2->T * LV;
									LST->STs[LI].S = LS;
									LST->STs[LI].T = LT;
								}
							}
							LST->NumOfSTs = PNAnisotropicTextureSamples;
						}
					}
				}

			}

//printf("\n\n");fflush(stdout);

//E3dTHREAD_UNLOCK
		}


// Compute partial derivatives of the normal, if needed	
//
		if(LSurfaceShader->IsReflective || LSurfaceShader->IsTransparent)
		{
			const E3dCoord3  LPosition = PPoint->Position;
			const E3dCoord3  LRayDirection = PRay->Direction;

			const E3dCoord3  Ldn_div_dx = E3dCoord3_s(L0dotPx * LNormal0->X + L1dotPx * LNormal1->X + L2dotPx * LNormal2->X,  L0dotPx * LNormal0->Y + L1dotPx * LNormal1->Y + L2dotPx * LNormal2->Y,  L0dotPx * LNormal0->Z + L1dotPx * LNormal1->Z + L2dotPx * LNormal2->Z);
			const E3dCoord3  Ldn_div_dy = E3dCoord3_s(L0dotPy * LNormal0->X + L1dotPy * LNormal1->X + L2dotPy * LNormal2->X,  L0dotPy * LNormal0->Y + L1dotPy * LNormal1->Y + L2dotPy * LNormal2->Y,  L0dotPy * LNormal0->Z + L1dotPy * LNormal1->Z + L2dotPy * LNormal2->Z);

			const E3dCoordinate  L0dotP = E_Dot3_vv(PRTriangle->L0, LPosition);
			const E3dCoordinate  L1dotP = E_Dot3_vv(PRTriangle->L1, LPosition);
			const E3dCoordinate  L2dotP = E_Dot3_vv(PRTriangle->L2, LPosition);

			const E3dCoord3      Ln = E3dCoord3_s(L0dotP * LNormal0->X + L1dotP * LNormal1->X + L2dotP * LNormal2->X,  L0dotP * LNormal0->Y + L1dotP * LNormal1->Y + L2dotP * LNormal2->Y,  L0dotP * LNormal0->Z + L1dotP * LNormal1->Z + L2dotP * LNormal2->Z);
			const E3dCoordinate  Ln_dot_n = E_Dot3_vv(Ln, Ln);
			const E3dCoordinate  Ln_dot_npow = E3d_Pow(Ln_dot_n, 1.5), Ln_dot_npowInv = 1.0 / Ln_dot_npow;

			const E3dCoordinate  ln_dot_dn_div_dx = E_Dot3_vv(Ln, Ldn_div_dx);
			const E3dCoordinate  ln_dot_dn_div_dy = E_Dot3_vv(Ln, Ldn_div_dy);

			const E3dCoord3  LdN_div_dx = E3dCoord3_s((Ln_dot_n * Ldn_div_dx.X - ln_dot_dn_div_dx * Ln.X) * Ln_dot_npowInv, (Ln_dot_n * Ldn_div_dx.Y - ln_dot_dn_div_dx * Ln.Y) * Ln_dot_npowInv, (Ln_dot_n * Ldn_div_dx.Z - ln_dot_dn_div_dx * Ln.Z) * Ln_dot_npowInv);
			const E3dCoord3  LdN_div_dy = E3dCoord3_s((Ln_dot_n * Ldn_div_dy.X - ln_dot_dn_div_dy * Ln.X) * Ln_dot_npowInv, (Ln_dot_n * Ldn_div_dy.Y - ln_dot_dn_div_dy * Ln.Y) * Ln_dot_npowInv, (Ln_dot_n * Ldn_div_dy.Z - ln_dot_dn_div_dy * Ln.Z) * Ln_dot_npowInv);

			PRayDiffTmp->dN_div_dx = LdN_div_dx;
			PRayDiffTmp->dN_div_dy = LdN_div_dy;

			PRayDiffTmp->dD_dot_N_div_d.X = E_Dot3_vv(LRayDifferentials->dD_div_dx, LNormal) + E_Dot3_vv(LRayDirection, LdN_div_dx);
			PRayDiffTmp->dD_dot_N_div_d.Y = E_Dot3_vv(LRayDifferentials->dD_div_dy, LNormal) + E_Dot3_vv(LRayDirection, LdN_div_dy);
		}
	}

//	PRayDiffTmp->dN_div_dx.X = PRayDiffTmp->dN_div_dx.Y = PRayDiffTmp->dN_div_dx.Z = 0.0;
//	PRayDiffTmp->dN_div_dy.X = PRayDiffTmp->dN_div_dy.Y = PRayDiffTmp->dN_div_dy.Z = 0.0;


	switch(PSurfaceMaterial->Textures.Count)
	{
		case 0:
// Nothing to do here, we just need to keep this case separate from "default:"
		break;

		default:
			_E3d_SampleTextures(PSurfaceMaterial, PRTriangle, (E3dSurfacePointBase*)PPoint);
		break;
	}

	return(PSurfaceMaterial);
}
