/*********************************************************************

Name:		Raytracer
Author:		Kamil Szatkowski "Netrix" (C)
Website:	http://www.netrix.org.pl/

Texturing written in collaboration with Adam Jordanek

*********************************************************************/
#include "RayTracer.hpp"
#include <stdio.h>
using namespace NIne;
#include "NLib/nLogger.hpp"

#pragma warning(disable: 4996)

namespace
{
	const NIne::NUint32 DATA_VECTOR_REALLOC_SIZE = 4;
}


RayTracer::RayTracer()
: m_bPreprocessDone(false)
{
	strcpy(parameters.szOutFile, "out_tex.bmp");
	strcpy(parameters.szDescFile, "");
	parameters.bCustomCamera = false;
	parameters.bCustomScreenSize = false;
	parameters.bDirt = false;
	parameters.bResetCache = false;
	parameters.fAngleCoeff = 0.125f;
	parameters.fBrickWidth = 600.0f;
	parameters.fBrickHeight = 125.0f;
	parameters.fBrickMortar = 20.0f;
	parameters.uTextureAttribute = NMAX_UINT32;
	parameters.uPackSize = 32;
}
/********************************************************/
RayTracer::~RayTracer() 
{
}
/********************************************************/
bool RayTracer::load(const char* szSceneDescFile, bool bCameraOnly)
{
	if(szSceneDescFile != null
	&& (strnlen(szSceneDescFile, MAX_PATH_SIZE) > MAX_PATH_LENGTH
	|| strnlen(parameters.szDescFile, MAX_PATH_SIZE) > MAX_PATH_LENGTH))
	{
		NLogError("Invalid string: 'szSceneDescFile' or 'parameters.szDescFile'");
		return true;
	}

	// Copying path
	if(szSceneDescFile != null)	{ strcpy(parameters.szDescFile, szSceneDescFile); }

	// Loading desc and scene
	if(m_sceneDesc.loadDescription(parameters.szDescFile))				{ return true; }

	if(!bCameraOnly)
	{
		if(m_scene.loadScene(m_sceneDesc.getScenePath()))					{ return true; }
		if(m_attributes.loadAttributes(m_sceneDesc.getAttributesPath()))	{ return true; }
		if(m_lights.loadLights(m_sceneDesc.getLightsPath()))				{ return true; }
	}

	if(m_camera.loadCamera(m_sceneDesc.getCameraPath()))				{ return true; }

	// Applying custom camera
	if(parameters.bCustomCamera)
	{
		m_camera.setCamera(parameters.v3CamPos, parameters.v3CamLook);
	}
	if(!parameters.bCustomScreenSize)
	{
		m_camera.getScreenSize(&parameters.uWidth, &parameters.uHeight);
	}

	m_bCameraOnly = bCameraOnly;

	return false;
}
/********************************************************/
void RayTracer::preprocessScene() 
{
	printf("Scene loaded...");		m_scene ? printf("OK\n") : printf("\n");
	printf("Camera loaded...");		m_camera ? printf("OK\n") : printf("\n");
	printf("Attributes loaded...");	m_attributes ? printf("OK\n") : printf("\n");
	printf("Lights loaded...");		m_lights ? printf("OK\n") : printf("\n");

	m_scene.preprocessScene();
	m_camera.applyDomainFactor(m_scene.getDomainFactor());
	m_lights.applyDomainFactor(m_scene.getDomainFactor());

#ifdef KD_TREE
	// Create KDtree
	m_kdtree.buildTree(&m_scene, parameters.bResetCache);
#endif

#ifdef USD
	// Uniform space subdivision
	m_uss.createSubdivision(&m_scene, parameters.bResetCache);
#endif

	// Max Dir
	m_fMaxDirLength = NMVector3fLength(m_camera.getViewPoint() - m_scene.getDomainCenter()) + m_scene.getMaxDomainLength();

	m_v3ViewPoint = m_camera.getViewPoint();
	m_v3ScreenStart = m_camera.getScreenPoint(0);
	m_v3ScreenRight = m_camera.getScreenPoint(1) - m_v3ScreenStart;
	m_v3ScreenDown = m_camera.getScreenPoint(2) - m_v3ScreenStart;

	m_bPreprocessDone = true;
}
/********************************************************/
void RayTracer::run()
{
	NAssert(m_scene == true, "Load scene first");
	NAssert(m_camera == true, "Load camera first");
	NAssert(m_attributes == true, "Load attributes first");
	NAssert(m_lights == true, "Load lights first");
	NAssert(m_bPreprocessDone == true, "Preprocess scene first");

	printf("width: %u, height: %u\n", parameters.uWidth, parameters.uHeight);

	m_bitmap.create(parameters.uWidth, parameters.uHeight);

	// Calculating result in packs sized by 'parameters.uPackSize'
	for(NUint32 y = 0; y < parameters.uHeight; y += parameters.uPackSize)
	{
	#ifdef PRINT_PROGRESS
		printf("%d\n", y);
	#endif
		for(NUint32 x = 0; x < parameters.uWidth; x += parameters.uPackSize)
		{
			// Calculating pixels in pack
			for(NUint32 py = y, pysize = min(parameters.uHeight, y + parameters.uPackSize); py < pysize; ++py)
			{
				for(NUint32 px = x, pxsize = min(parameters.uWidth, x + parameters.uPackSize); px < pxsize; ++px)
				{
					NMVector3f rayStart = m_v3ScreenStart 
						+ ((float)px / (float)parameters.uWidth) * m_v3ScreenRight
						+ ((float)py / (float)parameters.uHeight) * m_v3ScreenDown;
					NMVector3f rayDir = NMVector3fNormalize(rayStart - m_v3ViewPoint);	// Ray 

					NMVector3f vResult = traceRay(m_v3ViewPoint, rayDir, 0, 1.0f);

					// Clamping results color
					NMVector3f color = NMVector3fMax(NMVector3fMin(vResult, NMVector3fLoad(255.0f, 255.0f, 255.0f)), NMVector3fLoadZeros());
					NUint32 uColor = NBITMAP_RGB((NUint8)color.x, (NUint8)color.y, (NUint8)color.z);

					m_bitmap.setPixel(px, py, uColor);
				}
			}
		}
	}

	// Saving result
	m_bitmap.saveToFile(parameters.szOutFile);
}
/********************************************************/
void RayTracer::calculatePack(NUint32 x, NUint32 y, NUint8* opDest)
{
	NAssert(m_scene == true, "Load scene first");
	NAssert(m_camera == true, "Load camera first");
	NAssert(m_attributes == true, "Load attributes first");
	NAssert(m_lights == true, "Load lights first");
	NAssert(m_bPreprocessDone == true, "Preprocess scene first");

	// Calculating pixels in pack
	for(NUint32 py = y, pysize = min(parameters.uHeight, y + parameters.uPackSize), i = 0; py < pysize; ++py)
	{
		for(NUint32 px = x, pxsize = min(parameters.uWidth, x + parameters.uPackSize); px < pxsize; ++px)
		{
			NMVector3f rayStart = m_v3ScreenStart 
							  + ((float)px / (float)parameters.uWidth) * m_v3ScreenRight
							  + ((float)py / (float)parameters.uHeight) * m_v3ScreenDown;
			NMVector3f rayDir = NMVector3fNormalize(rayStart - m_v3ViewPoint);

			NMVector3f vResult = traceRay(m_v3ViewPoint, rayDir, 0, 1.0f);
			NMVector3f color = NMVector3fMax(NMVector3fMin(vResult, NMVector3fLoad(255.0f, 255.0f, 255.0f)), NMVector3fLoadZeros());

			opDest[i++] = (NUint8)color.z;
			opDest[i++] = (NUint8)color.y;
			opDest[i++] = (NUint8)color.x;
		}
	}
}
/********************************************************/
NIne::NMVector3f RayTracer::traceRay(const NMVector3f& rayPos, const NIne::NMVector3f& rayDir, NIne::NUint32 uDepthLevel, float fEta)
{
	const NUint32 TRACE_DEPTH = 4;

	_NALIGN_16 RayTestOutput rto;

#ifdef KD_TREE
	const FaceData* pFace = m_kdtree.findNearestFace(rayPos, rayDir, m_fMaxDirLength, &rto);
#elif defined(USD)
	const FaceData* pFace = m_uss.findNearestFace(rayPos, rayDir, m_fMaxDirLength, &rto);
#else
	const FaceData* pFace = dummyFindTriangle(rayPos, rayDir, &rto);
#endif	// KD_TREE

	if(pFace != null)
	{
		AttributeInstance att = m_attributes.getAttributeInstance(pFace->uAttributeIndex);

		NMVector3f vColor;
		NMVector3f vOutColor = NMVector3fLoadZeros();
		NMVector3f vFaceNormal;
		
		// Normal
		if(att.smoothShading)
		{
			vFaceNormal = pFace->pVertices[0]->v3Normal * rto.u 
						 + pFace->pVertices[1]->v3Normal * rto.v
						 + pFace->pVertices[2]->v3Normal * (1.0f - rto.u - rto.v);
		}
		else
		{
			vFaceNormal = pFace->v3Normal;	
		}

		// Index of wall attribute
		if(pFace->uAttributeIndex == parameters.uTextureAttribute)
		{
			vColor = procedeTexture(pFace, rto, rayDir, &vFaceNormal, &att);
		}
		else
		{
			vColor = att.color;
		}

		// Lighting
		float fLighting = 0.0f;

		for(NSize_t i = 0; i < m_lights.getLightCount(); ++i)
		{
			const LightInfo& light = m_lights.getLightInfo(i);
			NMVector3f vL = light.v3Position - rto.oPoint;

			float d = NMVector3fLength(vL);
			vL /= d;	// Normalizing vL

			float dot = NMVector3fDot(vL, vFaceNormal) * (rto.bFront ? 1.0f : -1.0f); // This covers the leaks and false shadows

			/* Shadow test */
		#ifdef KD_TREE_OCCLUDER
			if((dot < 0.0f) || m_kdtree.findObstacle(rto.oPoint + vL, vL, d))
		#elif defined(USD)
			if((dot < 0.0f) || m_uss.findObstacle(rto.oPoint + vL, vL, d))
		#else
			if((dot < 0.0f) || dummyFindObstacle(rto.oPoint + vL, vL, pFace, d))
		#endif
			{
				continue;
			}

			// Phong specular with lambertian diffuse
			float attenuation = m_scene.getDomainFactor() / d;
			float specular = powf(NMVector3fDot(NMVector3fNormalize(vL - rayDir), vFaceNormal) * (rto.bFront ? 1.0f : -1.0f), (float)att.gs);
			fLighting += (dot * att.kd + specular * att.ks) * attenuation * attenuation * (light.fPower);
		}

		// Compensation of light
		//vOutColor += vColor * (pow(fLighting, 0.7f) + max(log(fLighting) / log(1.03f), 0.0f));
		vOutColor += vColor * NMVector3fLoad(1.0f) * (pow(fLighting, 0.7f) + max(log(fLighting) * 30.0f, 0.0f));

		/* Reflection */
		if(att.kts > 0.0f && uDepthLevel < TRACE_DEPTH)
		{
			NMVector3f vR = NMVector3fReflect(rayDir, vFaceNormal);

			vOutColor = att.kts * (vColor * traceRay(rto.oPoint + vR, vR, uDepthLevel + 1, fEta)) + (1.0f - att.kts) * vOutColor;
		}

		/* Refraction */
		if(att.kf > 0.0f && uDepthLevel < TRACE_DEPTH)
		{
			float n = fEta / att.eta;
			vFaceNormal *= (fEta < att.eta ? 1.0f : -1.0f);
			float cosI = NMVector3fDot(vFaceNormal, rayDir);
			float cosT2 = 1.0f - n * n * (1.0f - cosI * cosI);

			if(cosT2 > 0.0f)
			{
				NMVector3f vNewDir = (n * rayDir) + (n * cosI - sqrtf(cosT2)) * vFaceNormal;
				vOutColor += att.kf * (vColor * traceRay(rto.oPoint + vNewDir, vNewDir, uDepthLevel + 1, att.eta));
			}
		}

		return vOutColor;
	}
	else
	{
		return NMVector3fLoad(1.0f, 0.0f, 1.0f);	// Null color
	}
}
/********************************************************/
void RayTracer::release()
{
	if(!m_bCameraOnly)
	{
		m_bitmap.release();
		m_scene.releaseScene();
		m_attributes.releaseAttributes();
		m_lights.releaseLights();

	#ifdef USD
		m_uss.releaseSubdivision();
	#endif
	#ifdef KD_TREE
		m_kdtree.release();
	#endif
	}
}
/********************************************************/
bool RayTracer::dummyFindObstacle(const NMVector3f& rayPos, const NMVector3f& rayDir, const FaceData* pSrcFace, float fDirLength)
{
	bool bFound = false;

	_NALIGN_16 RayTestOutput rto;
	for(NUint32 iFace = 0; iFace < m_scene.getFaceCount(); ++iFace)
	{
		const FaceData* pFace = m_scene.getFaceData().GetBuffer() + iFace;

		if(pFace != pSrcFace && m_scene.testTriangle(pFace, rayPos, rayDir, fDirLength, &rto))
		{
			return true;
		}
	}

	return bFound;
}
/********************************************************/
const FaceData* RayTracer::dummyFindTriangle(const NMVector3f& rayPos, const NMVector3f& rayDir, RayTestOutput* opRTO)
{
	const FaceData* pOutFace = null;
	bool bChanges = false;
	_NALIGN_16 RayTestOutput rto;
	opRTO->t = m_fMaxDirLength;

	for(NUint32 iFace = 0; iFace < m_scene.getFaceCount(); ++iFace)
	{
		const FaceData* pFace = m_scene.getFaceData().GetBuffer() + iFace;

		if(m_scene.testTriangle(pFace, rayPos, rayDir, opRTO->t, &rto))
		{
			pOutFace = pFace;
			*opRTO = rto;
			bChanges = true;
		}
	}

	return pOutFace;
}
/********************************************************/
template<typename T> 
float Parameterize(T x, T a, T b) 
{
	return float(x - a) / (b - a);
}

float HermiteCurve(float t) 
{
	return t * t * (3 - 2 * t);
}

float HermiteStep(float x, float a, float b) 
{
	if(a == b) return 0;
	if(x < a) return 0;
	if(b < x) return 1;
	return HermiteCurve(Parameterize(x, a, b)); // 3t^2-2t^3
}

float LinearStep(float x, float a, float b) 
{
	if(a == b) return 0;
	if(x < a) return 0;
	if(b < x) return 1;
	return x;
}

float Clamp(float x, float a, float b)
{
	return x < a ? a : (x > b ? b : x);
}

float HermiteCurveDeriverative(float t) 
{
	return 6 * t * (1 - t);
}

float HermiteStepDeriverative(float x, float a, float b)
{
	if(a == b) return 0;
	if(x < a) return 0;
	if(b < x) return 0;
	return HermiteCurveDeriverative(Parameterize(x, a, b));
}

float NoiseFunction(NIne::NUint32 x) 
{ 
	// returns in the interval [0,1]
	x = (x << 13) ^ x;
	return ((x * (x * x * 15731 + 789221) + 1376312589) & 0x7FFFFFFF) / float(0x7FFFFFFF);
}

NIne::NUint32 g_uNoiseState = 2154513;
float NoiseFunctionState() 
{ 
	// returns in the interval [0,1]
	NIne::NUint32 x = g_uNoiseState = (g_uNoiseState << 13) ^ g_uNoiseState;
	return ((x * (x * x * 15731 + 789221) + 1376312589) & 0x7FFFFFFF) / float(0x7FFFFFFF);
}

template<typename T> 
T Interpolate(float t, T a, T b) 
{
	return T(a + (b - a) * t);
}

float SmoothedNoise(NIne::NUint32 x) 
{
	return NoiseFunction(x) / 2 + NoiseFunction(x - 1) / 4 + NoiseFunction(x + 1) / 4;
}

float InterpolatedNoise(float x) 
{
	NIne::NUint32 X = (NIne::NUint32)(x);
	return Interpolate(x - X, SmoothedNoise(X), SmoothedNoise(X + 1));
}

float NoiseFunction(NIne::NUint32 x,NIne::NUint32 y	) 
{ 
	// returns in the interval [0,1]
	x = (x << 13) ^ y;
	y = (y << 6) ^x;
	return ((y * (x * (x+y) * 15731 + 789221) + 1376312589) & 0x7FFFFFFF) / float(0x7FFFFFFF);
}

template<typename T> 
T  Unsigned(T t) 
{
	return (t + 1) / 2;
}

float Turbulence(float x, float y, float fWidth, float fHeight)
{
	int iOctaves = 4;
	float fPersistence = 0.5f; // Persistence [0-1]. 1=All octaves added equally.
	float fResult = 0;
	NIne::NUint32 uFrequency = 1; // Can't see amplitudes less than 1/256.
	for(float fAmplitude = 1; (iOctaves--) && (fAmplitude > 1.0f / 256.0f); fAmplitude *= fPersistence, uFrequency <<= 1) 
	{
		fResult += fAmplitude * fabs(InterpolatedNoise(fWidth * (fHeight * y + x) * uFrequency));
	}
	fResult = Unsigned(fResult);

	return fResult;
}

int BrickColorNoise(NIne::NUint32 x,NIne::NUint32 y, int max) 
{ 
	float value=Turbulence((float)x, (float)y, 15.0f, 15.0f)*max;
	//printf("%d %d : %f\n",x,y,value);
	return (int)(value);
}


float SmoothNoise(float x, float y)
{  
	int noiseWidth=100;
	int noiseHeight=100;
   //get fractional part of x and y
   float fractX = x - int(x);
   float fractY = y - int(y);
   
   //wrap around
   int x1 = (int(x) + noiseWidth) % noiseWidth;
   int y1 = (int(y) + noiseHeight) % noiseHeight;
   
   //neighbor values
   int x2 = (x1 + noiseWidth - 1) % noiseWidth;
   int y2 = (y1 + noiseHeight - 1) % noiseHeight;

   //smooth the noise with bilinear interpolation
   float value = 0.0;
   value += fractX       * fractY       * NoiseFunction(x1,y1);
   value += fractX       * (1 - fractY) * NoiseFunction(x1,y2);
   value += (1 - fractX) * fractY       * NoiseFunction(x2,y1);
   value += (1 - fractX) * (1 - fractY) * NoiseFunction(x2,y2);

   return value;
}

float Cloud(float x,float y, float size, float minSize = 1.0f){
    float value = 0.0f, initialSize = size;    
    while(size >= minSize){
        value += SmoothNoise(x / size, y / size) * size;
        size /= 2.0f;
    }    
    return(value / initialSize);
}


float InterpolateBSpline(float t, float a, float b, float c, float d) 
{
	return 1.0f/6.0f*(t*(t*(t*(-a+3*b-3*c+d)+(3*a-6*b+3*c))+3*(c-a))+(a+4*b+c));
}

void DrawBrick(const NMVector2f& v2BrickPoint,	// brick local space
			   const NMVector2f& v2BrickCenter, // brick local space
			   const NMVector2f& v2BrickSize,	// brick local space
			   const NMVector2f& v2BorderSize,	// 0..1 space
			   float fAngle, 
			   float* pColor, 
			   NMVector3f* pBrickNormal)
{
	NMVector vX = NMVectorLoad(0.0f, v2BrickSize.x, v2BrickSize.x, 0.0f) - NMVectorLoad(v2BrickCenter.x);
	NMVector vY = NMVectorLoad(0.0f, 0.0f, v2BrickSize.y, v2BrickSize.y) - NMVectorLoad(v2BrickCenter.y);

	NMVector vMinX = NMVectorLoad(-v2BrickCenter.x);
	NMVector vMinY = NMVectorLoad(-v2BrickCenter.y);
	NMVector vMaxX = NMVectorLoad(v2BrickSize.x - v2BrickCenter.x);
	NMVector vMaxY = NMVectorLoad(v2BrickSize.y - v2BrickCenter.y);

	float fSin, fCos;
	NMSinCos(&fSin, &fCos, fAngle);

	NMVector vNewX = vX * NMVectorLoad(fCos) + vY * NMVectorLoad(fSin);
	NMVector vNewY = vX * NMVectorLoad(-fSin) + vY * NMVectorLoad(fCos);

	// Cut
	vX = NMVectorMax(vMinX, NMVectorMin(vMaxX, vNewX));
	vY = NMVectorMax(vMinY, NMVectorMin(vMaxY, vNewY));

	vX /= vNewX;
	vY /= vNewY;

	vX = NMVectorMin(vX, vY);	// minXY
	vX = NMVectorMin(vX, NMVectorLoad(vX.y, vX.x, vX.w, vX.z)); //xxyy
	vX = NMVectorMin(vX, NMVectorLoad(vX.z, vX.z, vX.x, vX.x)); //xxxx

	vNewX *= vX;
	vNewY *= vX;

	// Rotating back
	vX = vNewX * NMVectorLoad(fCos) + vNewY * NMVectorLoad(-fSin) + NMVectorLoad(v2BrickCenter.x);
	vY = vNewX * NMVectorLoad(fSin) + vNewY * NMVectorLoad(fCos) + NMVectorLoad(v2BrickCenter.y);

	NMVector2f vMinP = { min(vX.x, vX.w), min(vY.x, vY.y) };
	NMVector2f vMaxP = { min(vX.y, vX.z), min(vY.z, vY.w) };

	// ActualPoint
	NMVector2f vP = { (v2BrickPoint.x - v2BrickCenter.x) * fCos + (v2BrickPoint.y - v2BrickCenter.y) * -fSin, 
					  (v2BrickPoint.x - v2BrickCenter.x) * (fSin) + (v2BrickPoint.y - v2BrickCenter.y) * fCos };

	vP.x += v2BrickCenter.x;
	vP.y += v2BrickCenter.y;	
	
	// Moving to 0..1 space
	vMinP.x /= v2BrickSize.x;
	vMinP.y /= v2BrickSize.y;
	vMaxP.x /= v2BrickSize.x;
	vMaxP.y /= v2BrickSize.y;
	vP.x /= v2BrickSize.x;
	vP.y /= v2BrickSize.y;
	
	/* Color */
	float w = HermiteStep(vP.x, vMinP.x, vMinP.x + v2BorderSize.x) - HermiteStep(vP.x, vMaxP.x - v2BorderSize.x, vMaxP.x);
	float h = HermiteStep(vP.y, vMinP.y, vMinP.y + v2BorderSize.y) - HermiteStep(vP.y, vMaxP.y - v2BorderSize.y, vMaxP.y);
	
	*pColor = w * h;

	if(pBrickNormal != null && *pColor > 0.0f)
	{
		/* Normal */
		float wn = HermiteStepDeriverative(vP.x, vMinP.x, vMinP.x + v2BorderSize.x);
		wn -= HermiteStepDeriverative(vP.x, vMaxP.x - v2BorderSize.x, vMaxP.x);
		float hn = HermiteStepDeriverative(vP.y, vMinP.y, vMinP.y + v2BorderSize.y);
		hn -= HermiteStepDeriverative(vP.y, vMaxP.y - v2BorderSize.y, vMaxP.y);

		float xAngle = atan(wn);
		float yAngle = atan(hn);
		*pBrickNormal = NMVector3fNormalize(NMVector3fLoad(-sinf(xAngle), 0.0f, cosf(xAngle)) 
											+ NMVector3fLoad(0.0f, -sinf(yAngle), cosf(yAngle)));
	}
}


NMVector3f getBrickColour(float fColor,int x,int y)
{
	int value=BrickColorNoise(x,y,3);

	NMVector3f outColor =
	{
		value > 2 ? InterpolateBSpline(fColor, 0.8f, 0.5f, 0.5f, 0.4f) : InterpolateBSpline(fColor, 0.8f, 0.5f, 0.4f, 0.2f),
		InterpolateBSpline(fColor, 0.75f, 0.4f, 0.2f, 0.1f),
		value > 2 ? InterpolateBSpline(fColor, 0.6f, 0.3f, 0.1f, 0.0f) : InterpolateBSpline(fColor, 0.6f, 0.3f, 0.1f, 0.0f)
		// InterpolateBSpline(fColor, 0.4f+0.1f*value, 0.5f+0.1f*value, 0.4f+0.1f*value, 0.2f+0.1f*value),
		// InterpolateBSpline(fColor, 0.75f, 0.4f, 0.2f, 0.1f),
		// InterpolateBSpline(fColor, 0.6f+0.1f*value, 0.3f, 0.1, 0.0f)
	};
	return outColor;
}


float Height(NMVector2f uv, 
			float fWidth, 
			float fHeight, 
			float fBrickWidth, 
			float fBrickHeight, 
			float fMortarThickness)
{
	// Preparing data
	float fBMWidth = fBrickWidth + fMortarThickness;
	float fBMHeight = fBrickHeight + fMortarThickness;
	float fBorderWidth = fMortarThickness / fBMWidth;
	float fBorderHeight = fMortarThickness / fBMHeight;

	float bu = uv.x * fWidth / fBMWidth; // Varies [0,1] over one brick
	float bv = uv.y * fHeight / fBMHeight; // Varies [0,1] over one brick

	// Shift
	float fRandomness = NoiseFunction((int)bv) / 4.0f;
	bu = bu + ((int)bv & 0x1 ? 0.3f + fRandomness : 0.0f);

	int iColumn = (int)bu;
	int iRow = (int)bv;

	bu -= iColumn;
	bv -= iRow;

	float fTurb = NoiseFunction(iColumn, iRow);
	float fTurbX = LinearStep(fTurb * 0.617f + 0.387f * fTurb * fTurb, 0.0f, 1.0f) * 0.5f + 0.25f;
	float fTurbY = LinearStep(fTurb * 0.527f + 0.474f * fTurb * fTurb, 0.0f, 1.0f) * 0.5f + 0.25f;
	float fTurbAngle = Clamp(fTurb - 0.16f, -0.16f, 0.16f) * 0.125f;

	// Drawing bricks
	float fColor;
	NMVector2f brickPoint = { bu * fBrickWidth, bv * fBrickHeight };
	NMVector2f brickCenter = { fTurbX * fBrickWidth, fTurbY * fBrickHeight };
	NMVector2f brickSize = { fBrickWidth, fBrickHeight };
	NMVector2f borderSize = { fBorderWidth, fBorderHeight };

	DrawBrick(brickPoint, 
			   brickCenter,
			   brickSize,
			   borderSize,
			   fTurbAngle,
			   &fColor, 
			   null);
			
	// Noise on bricks
	float fMax;	// 64
	float fHeightCoeff = 1.0f;
	
	if(fColor > 0.1f)	{ fMax = 96; }
	else				{ fMax = 8; }
	
	float fA00 = Cloud(uv.x * fWidth, uv.y * fHeight, fMax);
	return Clamp(fColor * fA00, 0.0f, 1.0f) * fHeightCoeff;	// That's height
}

NMVector3f Wall(NMVector2f uv, 
				float fWidth, 
				float fHeight, 
				float fBrickWidth, 
				float fBrickHeight, 
				float fMortarThickness, 
				float fAngleCoeff,
				bool bDirt,
				NMVector3f* pNormal, 
				AttributeInstance* pAtt) 
{
	// Preparing data
	float fBMWidth = fBrickWidth + fMortarThickness;
	float fBMHeight = fBrickHeight + fMortarThickness;
	float fBorderWidth = fMortarThickness / fBMWidth;
	float fBorderHeight = fMortarThickness / fBMHeight;

	float bu =  uv.x * fWidth / fBMWidth; // Varies [0,1] over one brick
	float bv =  uv.y * fHeight / fBMHeight; // Varies [0,1] over one brick

	// Shift
	float fRandomness = NoiseFunction((int)bv) / 4.0f;
	bu = bu + ((int)bv & 0x1 ? 0.3f + fRandomness : 0.0f);

	int iColumn = (int)bu;
	int iRow = (int)bv;

	bu -= iColumn;
	bv -= iRow;

	float fTurb = NoiseFunction(iColumn, iRow);
	float fTurbX = 0.5f;//LinearStep(fTurb * 0.617f + 0.387f * fTurb * fTurb, 0.0f, 1.0f) * 0.5f + 0.25f;
	float fTurbY = 0.5f;//LinearStep(fTurb * 0.527f + 0.474f * fTurb * fTurb, 0.0f, 1.0f) * 0.5f + 0.25f;
	float fTurbAngle = Clamp(fTurb - 0.16f, -0.16f, 0.16f) * fAngleCoeff;

	// Drawing bricks
	float fColor;
	NMVector2f brickPoint = { bu * fBrickWidth, bv * fBrickHeight };
	NMVector2f brickCenter = { fTurbX * fBrickWidth, fTurbY * fBrickHeight };
	NMVector2f brickSize = { fBrickWidth, fBrickHeight };
	NMVector2f borderSize = { fBorderWidth, fBorderHeight };

	DrawBrick(brickPoint, 
			   brickCenter,
			   brickSize,
			   borderSize,
			   fTurbAngle,
			   &fColor, 
			   pNormal);
			
	// Noise on bricks
	float fDelta = 2.0f;
	float fHeightCoeff = 64.0f;
	float fMax;	// 64
	
	if(fColor > 0.1f)	{ fMax = 96; }
	else				{ fMax = 8; }
	
	float fA00 = Cloud( uv.x*fWidth,  uv.y*fHeight, fMax);
	float fA10 = Cloud( uv.x*fWidth + fDelta,  uv.y*fHeight, fMax);
	float fA01 = Cloud( uv.x*fWidth,  uv.y*fHeight + fDelta, fMax);
	
	//printf("fColor: %f, fA00: %f\n", fColor, fA00);
	
	// Brick bumps
	*pNormal += NMVector3fNormalize(NMVector3fCross(NMVector3fLoad(fDelta, 0.0, (fA10 - fA00) * fHeightCoeff), NMVector3fLoad(0.0f, fDelta, (fA01 - fA00) * fHeightCoeff)));

	NMVector3f outColor = getBrickColour(fColor,iColumn,iRow);



	//-----------White turbulnce
	float high= uv.y-0.3f;
	// Normal	
	if(high>1)high=1;
	float ll = (1-high)*Cloud( uv.x*fWidth,  uv.y*fHeight, 228);
	ll=powf(ll, 0.8f);
	if( uv.y<0.3f)ll/=1-(0.3f- uv.y)*3;

	if(ll<0.6f) ll=0.6f;
	else if(ll>0.7f&&ll<0.73f) ll=0.8f;
	else if(ll>1.0f) ll=1.0f;

	if(bDirt) { outColor *= ll; }
	float fHermitWater = HermiteStep(1.0f - ll, 0.0f, 0.7f);

	if(bDirt) { pAtt->kts = fHermitWater; }
	*pNormal = NMVector3fNormalize(*pNormal);

	// //Zielen
	// high= uv.y-1;
	// float green = (1+(high)*Cloud( uv.x*fWidth+0.3f*high,  uv.y*fHeight+0.3f, 248))*Cloud( uv.x*(fWidth+0.1f)+0.3f*high,  uv.y*(fHeight+0.1f)+0.3f, 328)*2;

	// if(green<1.7)green=1.0;
	// if(green>3.7)green=2.0;
	
	// float p1=(green-1)/2;

	// NMVector3f gr=NIne::NMVector3fLoad((1-(p1*p1))/green, green, (1-(p1*p1)*3)/green);
	
	// if(bDirt) { outColor = (outColor / green*green) * gr; }

	// if(green>2){
		// pAtt->kts /=green*70;
	// }

	return outColor;
}

NMVector3f TransformPoint(const NMVector3f& point, const NMVector3f& vTangent, const NMVector3f& vBinormal, const NMVector3f& vNormal)
{
	return NMVector3fLoad(NMVector3fDot(point, vTangent), NMVector3fDot(point, vBinormal), NMVector3fDot(point, vNormal));
}


NMVector3f RayTracer::procedeTexture(const FaceData* pFace, const RayTestOutput& rto, const NIne::NMVector3f vDir, NIne::NMVector3f* pNormal, AttributeInstance* pAtt)
{
	NMVector3f vTangentPos = TransformPoint(rto.oPoint - pFace->v3Min, 
											pFace->v3Tangent, 
											pFace->v3Binormal, 
											pFace->v3Normal);

	vTangentPos.x /= pFace->v3TangentSize.x;
	vTangentPos.y /= pFace->v3TangentSize.y;

	NMVector3f vTanNormal = NMVector3fLoad(0.0f, 0.0f, 1.0f);
	
	NMVector2f vUV = { vTangentPos.x, vTangentPos.y };
	NMVector3f vColor = Wall(vUV, 
							 pFace->v3TangentSize.x, pFace->v3TangentSize.y, 
							 parameters.fBrickWidth, parameters.fBrickHeight, parameters.fBrickMortar, 
							 parameters.fAngleCoeff, 
							 parameters.bDirt, 
							 &vTanNormal, 
							 pAtt);

	*pNormal = TransformPoint(vTanNormal, pFace->v3Tangent, pFace->v3Binormal, pFace->v3Normal);

	return vColor;
}
