// Clouds.cpp: Implementaion of the class CClouds.
//
//////////////////////////////////////////////////////////////////////

#include "stdafx.h"
#include "Clouds.h"
#include "Util\DynamicArray2D.h"

//////////////////////////////////////////////////////////////////////
// Construction / destruction
//////////////////////////////////////////////////////////////////////

IMPLEMENT_SERIAL (CClouds, CObject, 1)

CClouds::CClouds()
{
	// Init member variables
	m_iWidth = 0;
	m_iHeight = 0;
	m_sLastParam.bValid = false;
}

CClouds::~CClouds()
{

}

bool CClouds::GenerateClouds(SNoiseParams *sParam, CWnd *pWndStatus)
{
	//////////////////////////////////////////////////////////////////////
	// Generate the clouds
	//////////////////////////////////////////////////////////////////////

	assert(sParam);

	// Free all old data
	CleanUp();

	// Save the width & height
	m_iWidth = sParam->iWidth;
	m_iHeight = sParam->iHeight;

	// Create a new DC
	m_dcClouds.CreateCompatibleDC(NULL);

	// Create a new bitmap
	VERIFY(m_bmpClouds.CreateBitmap(512, 512, 1, 32, NULL));
	m_dcClouds.SelectObject(m_bmpClouds);

	// Hold the last used parameters
	memcpy(&m_sLastParam, sParam, sizeof(SNoiseParams));
	m_sLastParam.bValid = true;

	// Call the generation function function with the supplied parameters
	CalcCloudsPerlin(sParam, pWndStatus);
	
	return true;
}

void CClouds::DrawClouds(CDC *pDC, LPRECT prcPosition)
{
	//////////////////////////////////////////////////////////////////////
	// Draw the generated cloud texture
	//////////////////////////////////////////////////////////////////////

	assert(pDC);
	assert(prcPosition);

	// Prevents that the overall apperance of the image changes when it
	// is stretched
	pDC->SetStretchBltMode(HALFTONE);

	if (m_dcClouds.m_hDC)
	{
		// Blit it to the destination rectangle
		pDC->StretchBlt(prcPosition->left, prcPosition->top, prcPosition->right,
			prcPosition->bottom, &m_dcClouds, 0, 0, m_iWidth, m_iHeight, SRCCOPY);
	}
	else
	{
		// Just draw a black rectangle
		pDC->StretchBlt(prcPosition->left, prcPosition->top, prcPosition->right,
			prcPosition->bottom, &m_dcClouds, 0, 0, m_iWidth, m_iHeight, BLACKNESS);
	}
}

void CClouds::CalcCloudsPerlin(SNoiseParams *sParam, CWnd *pWndStatus)
{
	//////////////////////////////////////////////////////////////////////
 	// Generate perlin noise based clouds
	//////////////////////////////////////////////////////////////////////

	unsigned int i, j, h;
	COLORREF clrfColor;
	float fValueRange;
	CDynamicArray2D cCloud(sParam->iWidth, sParam->iHeight);
	float fLowestPoint = 256000.0f, fHighestPoint = -256000.0f;
	float fScaledCol;
	CNoise cNoise;
	float fYScale = 255;
	char szStatus[128];

	assert(!IsBadReadPtr(sParam, sizeof(SNoiseParams)));
	assert(sParam->fFrequency);
	assert(sParam->iWidth && sParam->iHeight);
	assert(pWndStatus);
	assert(::IsWindow(pWndStatus->m_hWnd));
	assert(m_dcClouds.m_hDC);
	assert(m_bmpClouds.m_hObject);
	
	//////////////////////////////////////////////////////////////////////
	// Generate the noise array
	//////////////////////////////////////////////////////////////////////

	// Set the random value
	srand(sParam->iRandom);

	// Process layers
	for (i=0; i<sParam->iPasses; i++)
	{
		// Show status
		sprintf(szStatus, "Calculating pass %i of %i...", i + 1, sParam->iPasses);
		pWndStatus->SetWindowText(szStatus);

		// Apply the fractal noise function to the array
		cNoise.FracSynthPass(&cCloud, sParam->fFrequency, fYScale, 
			sParam->iWidth, sParam->iHeight, FALSE);

		// Modify noise generation parameters
		sParam->fFrequency *= sParam->fFrequencyStep;
		fYScale *= sParam->fFade;	
	}

	//////////////////////////////////////////////////////////////////////
	// Paint the clouds into the bitmap
	//////////////////////////////////////////////////////////////////////

	// Apply the exponential function and find the value range
	for (i=0; i<sParam->iWidth; i++)
		for (j=0; j<sParam->iHeight; j++)
		{
			// Apply an exponential function to get separated clouds
			cCloud.m_Array[i][j] = CloudExpCurve(cCloud.m_Array[i][j], 
				sParam->iCover, sParam->iSharpness);

			fHighestPoint = __max(fHighestPoint, cCloud.m_Array[i][j]);
			fLowestPoint = __min(fLowestPoint, cCloud.m_Array[i][j]);
		}

	// Prepare some renormalisation values
	fValueRange = fHighestPoint - fLowestPoint;

	// Smooth the clouds
	for (h=0; h<sParam->iSmoothness; h++)
		for (i=1; i<sParam->iWidth-1; i++)
			for (j=1; j<sParam->iHeight-1; j++)
			{
				cCloud.m_Array[i][j] = 
					(cCloud.m_Array[i][j]     + cCloud.m_Array[i+1][j]   + cCloud.m_Array[i][j+1] +
					 cCloud.m_Array[i+1][j+1] + cCloud.m_Array[i-1][j]   + cCloud.m_Array[i][j-1] + 
					 cCloud.m_Array[i-1][j-1] + cCloud.m_Array[i+1][j-1] + cCloud.m_Array[i-1][j+1])
					 / 9.0f;
			}
	
	for (i=0; i<sParam->iWidth; i++)
		for (j=0; j<sParam->iHeight; j++)
		{
			// Normalize it first
			cCloud.m_Array[i][j] += fLowestPoint;
			cCloud.m_Array[i][j] = cCloud.m_Array[i][j] / fValueRange * 255.0f;
			if (cCloud.m_Array[i][j] > 255)
				cCloud.m_Array[i][j] = 255;		

			// Get the 0-255 index from the array, convert it into 0x00BBGGRR format, (add the
			// blue sky) and draw the pixel it into the bitmap
			if (sParam->bBlueSky)
			{
				fScaledCol = cCloud.m_Array[i][j] / 255.0f;
				clrfColor = ((int) (fScaledCol * 255 + (1 - fScaledCol) * 40)) | 
					((int) (fScaledCol * 255 + (1 - fScaledCol) * 90)) << 8 | 
					((int) (fScaledCol * 255 + (1 - fScaledCol) * 180)) << 16;
			}
			else
			{
				fScaledCol = cCloud.m_Array[i][j] / 255.0f;
				clrfColor = ((int) (fScaledCol * 255 + (1 - fScaledCol))) | 
					((int) (fScaledCol * 255 + (1 - fScaledCol))) << 8 | 
					((int) (fScaledCol * 255 + (1 - fScaledCol))) << 16;
			}

			// Set the pixel
			m_dcClouds.SetPixelV(i, j, clrfColor);
		}
}

void CClouds::CleanUp()
{
	//////////////////////////////////////////////////////////////////////
	// Free all data
	//////////////////////////////////////////////////////////////////////

	// The cloud DC
	if (m_dcClouds.m_hDC)
		m_dcClouds.DeleteDC();

	// Bitmap
	if (m_bmpClouds.m_hObject)
		m_bmpClouds.DeleteObject();
}


float CClouds::CloudExpCurve(float v, unsigned int iCover, float fSharpness)
{
	//////////////////////////////////////////////////////////////////////
	// Exponential function to generate separated clouds
	//////////////////////////////////////////////////////////////////////

	float CloudDensity;
	float c;

	c = v - iCover;

	if (c < 0)
		c = 0;

	CloudDensity = 255.f - (float) ((pow(fSharpness, c)) * 255.0f);

	return CloudDensity;
}

void CClouds::Serialize(CArchive& ar)
{
	////////////////////////////////////////////////////////////////////////
	// Save or restore the class
	////////////////////////////////////////////////////////////////////////

    CObject::Serialize (ar);

    if (ar.IsStoring())
	{
		// Storing
		ar << m_iWidth << m_iHeight;
		ar.Write(&m_sLastParam, sizeof(m_sLastParam));
	}
    else 
	{
		// Loading
		ar >> m_iWidth >> m_iHeight;
		ar.Read(&m_sLastParam, sizeof(m_sLastParam));
	}
}

void CClouds::Serialize( CXmlArchive &xmlAr )
{
	if (xmlAr.bLoading)
	{
		//Load
		XmlNodeRef clouds = xmlAr.root->findChild( "Clouds" );
		if (clouds)
		{
			clouds->getAttr( "Width",m_iWidth );
			clouds->getAttr( "Height",m_iHeight );
		}
	}
	else
	{
		//Save
		XmlNodeRef clouds = xmlAr.root->newChild( "Clouds" );
		clouds->setAttr( "Width",(int)m_iWidth );
		clouds->setAttr( "Height",(int)m_iHeight );
	}
}

/*
void CClouds::CalcCloudsBezierFault(CLOUDPARAM *sParam, CWnd *pWndStatus)
{
	//////////////////////////////////////////////////////////////////////
	// Generate new clouds. We are using quadric Bezier curves 
	// instead of simple lines to smooth out the image. The point in front
	// of line test is done by using a simple Bresenham algorithm to find
	// the intersection points with each scanline. Use the titel of
	// the supplied window to provide status informations
	//////////////////////////////////////////////////////////////////////

	unsigned int i, j, h, iScanline, iScanlineCurX;
	uint8 iColor;
	COLORREF clrfColor;
	POINT p1, p2, p3;
	float fValueRange;
	CDynamicArray2D cCloud(sParam->iWidth, sParam->iHeight);
	unsigned int *pScanlineIntersection = new unsigned int [m_iHeight];
	float fLowestPoint = 65536.0f, fHighestPoint = -65536.0f;
	float fIncreasement = 1.0f;
	KeyPoint sKeyPoints[MAX_KEY_POINTS];
	unsigned int iKeyPointCount;
	int iDeltaX, iDeltaY;
	int iUDeltaX, iUDeltaY;
	char szWindowTitel[32];
	unsigned int iStatus, iLastStatus = 0;

	assert(sParam);
	assert(sParam->iIterations);

	// Paint it black first
	m_dcClouds.BitBlt(0, 0, m_iWidth, m_iHeight, &m_dcClouds, 0, 0, BLACKNESS);

	// Do the iterations
	for (i=0; i<sParam->iIterations; i++)
	{
		// Update status (if necessary)
		iStatus = (int) (i / (float) sParam->iIterations * 100);
		if (iStatus != iLastStatus)
		{
			sprintf(szWindowTitel, "%i%% Done", iStatus);
			pWndStatus->SetWindowText(szWindowTitel);
			iLastStatus = iStatus;
		}

		//////////////////////////////////////////////////////////////////////
		// Generate the curve
		//////////////////////////////////////////////////////////////////////

		// Pick two random points on the border
		RandomPoints(&p1, &p2);
		
		iDeltaX = p2.x - p1.x; // Work out X delta
		iDeltaY = p2.y - p1.y; // Work out Y delta
		iUDeltaX = abs(iDeltaX); // iUDeltaX is the unsigned X delta
		iUDeltaY = abs(iDeltaY); // iUDeltaY is the unsigned Y delta

		// Subdivide the line, use a random point in the heightmap as third control point
		p3.x = (long) (rand() / (float) RAND_MAX * m_iWidth);
		p3.y = (long) (rand() / (float) RAND_MAX * m_iHeight);

		// Get the keypoints of the Bezier curve
		iKeyPointCount = QuadricSubdivide(&p1, &p3, &p2, 
			BEZIER_SUBDIVISON_LEVEL, sKeyPoints, true);
		
		//////////////////////////////////////////////////////////////////////
		// Trace each curve segment into the scanline intersection array
		//////////////////////////////////////////////////////////////////////

		for (j=0; j<iKeyPointCount - 1; j++)
		{
			// Get the start / endpoints of the segment
			p1.x = (long) sKeyPoints[j].fX;
			p1.y = (long) sKeyPoints[j].fY;
			p2.x = (long) sKeyPoints[j + 1].fX;
			p2.y = (long) sKeyPoints[j + 1].fY;

			TraceCurveSegment(&p1, &p2, iUDeltaX, iUDeltaY, pScanlineIntersection);
		}

		//////////////////////////////////////////////////////////////////////
		// Process all scanlines and raise / lower the terrain
		//////////////////////////////////////////////////////////////////////

		// This ensures a good distribution of vallies and mountains
		if (rand() % 2)
		{
			if (iDeltaX > 0)
				iDeltaX = -iDeltaX;
			else
				iDeltaX = abs(iDeltaX);
		}

		// We have to approach the two delta cases different to make sure we are 
		// processing all pixels
		if (iUDeltaX < iUDeltaY)
		{
			// This is necessary because the direction of the line is important for the
			// raising / lowering. Doing it here safes us from using a branch in the
			// inner loop
			if (iDeltaX > 0)
				fIncreasement = (float) fabs(fIncreasement);
			else
				fIncreasement = (float) -fabs(fIncreasement);

			for (iScanline=0; iScanline<m_iHeight; iScanline++)
			{
				// Loop trough each pixel in the scanline
				for (iScanlineCurX=0; iScanlineCurX<m_iWidth; iScanlineCurX++)
				{
					// Raise / lower the terrain on each side of the line
					if (iScanlineCurX > pScanlineIntersection[iScanline])
						cCloud.m_Array[iScanlineCurX][iScanline] += fIncreasement;
					else
						cCloud.m_Array[iScanlineCurX][iScanline] -= fIncreasement;
						
					// Do we have a new highest / lowest value ?
					fLowestPoint = __min(fLowestPoint, cCloud.m_Array[iScanlineCurX][iScanline]);
					fHighestPoint = __max(fHighestPoint, cCloud.m_Array[iScanlineCurX][iScanline]);
				}
			}
		}
		else
		{
			// This is necessary because the direction of the line is important for the
			// raising / lowering. Doing it here safes us from using a branch in the
			// inner loop
			if (iDeltaY > 0)
				fIncreasement = (float) fabs(fIncreasement);
			else
				fIncreasement = (float) -fabs(fIncreasement);

			// Loop trough each pixel in the scanline
			for (iScanlineCurX=0; iScanlineCurX<m_iWidth; iScanlineCurX++)
			{
				for (iScanline=0; iScanline<m_iHeight; iScanline++)
				{
					// Raise / lower the terrain on each side of the line
					if (iScanline > pScanlineIntersection[iScanlineCurX])
						cCloud.m_Array[iScanlineCurX][iScanline] += fIncreasement;
					else
						cCloud.m_Array[iScanlineCurX][iScanline] -= fIncreasement;
											
					// Do we have a new highest / lowest value ?
					fLowestPoint = __min(fLowestPoint, cCloud.m_Array[iScanlineCurX][iScanline]);
					fHighestPoint = __max(fHighestPoint, cCloud.m_Array[iScanlineCurX][iScanline]);
				}
			}
		}
	}

	//////////////////////////////////////////////////////////////////////
	// Paint the clouds into the bitmap
	//////////////////////////////////////////////////////////////////////

	// Prepare some renormalisation values. Shorten the value range to get
	// higher contrast clouds
	fValueRange = (fHighestPoint - fLowestPoint) / 1.3f;

	if (fLowestPoint < 0)
	{ 
		fLowestPoint = (float) fabs(fLowestPoint);
		fHighestPoint += (float) fabs(fLowestPoint);
	}

	// Smooth the clouds
	for (h=0; h<sParam->iSmoothness; h++)
		for (i=1; i<m_iWidth-1; i++)
			for (j=1; j<m_iHeight-1; j++)
			{
				cCloud.m_Array[i][j] = 
					(cCloud.m_Array[i][j]     + cCloud.m_Array[i+1][j]   + cCloud.m_Array[i][j+1] +
					 cCloud.m_Array[i+1][j+1] + cCloud.m_Array[i-1][j]   + cCloud.m_Array[i][j-1] + 
					 cCloud.m_Array[i-1][j-1] + cCloud.m_Array[i+1][j-1] + cCloud.m_Array[i-1][j+1])
					 / 9.0f;
			}

	for (i=0; i<m_iWidth; i++)
		for (j=0; j<m_iHeight; j++)
		{
			// Normalize it first
			cCloud.m_Array[i][j] += fLowestPoint;
			cCloud.m_Array[i][j] = cCloud.m_Array[i][j] / fValueRange * 255.0f;
			if (cCloud.m_Array[i][j] > 255)
				cCloud.m_Array[i][j] = 255;

			// Apply an exponetial function to get separated clouds
			cCloud.m_Array[i][j] = CloudExpCurve(cCloud.m_Array[i][j]);

			// Get the 0-255 index from the array, convert it into 0x00BBGGRR grayscale
			// and draw it into the bitmap
			iColor = (int8) cCloud.m_Array[i][j];
            clrfColor = iColor | iColor << 8 | iColor << 16;
			m_dcClouds.SetPixelV(i, j, clrfColor);
		}

	// Free the scanline intersection data
	delete [] pScanlineIntersection;
	pScanlineIntersection = 0;
}

unsigned int CClouds::QuadricSubdivide(LPPOINT p1, LPPOINT p2, LPPOINT p3, unsigned int iLevel, 
								          KeyPoint *pKeyPointArray, bool bFirstSubdivision)
{
	//////////////////////////////////////////////////////////////////////
	// Add a line (one key point). If level is 0 use the end points. If
	// this is the first subdivision, you have to pass true for
	// bFirstSubdivision. The number of keypoints is returned
	//////////////////////////////////////////////////////////////////////

	POINT pGl[3];
	POINT pGr[3];
	static int iCurKeyPoint = 0;

	// Start with the first keypoint when doing the first subdivision
	if (bFirstSubdivision)
		iCurKeyPoint = 0;

	if (iLevel == 0) 
	{
		// Add it to the list
		pKeyPointArray[iCurKeyPoint].fX = (float) p1->x;
		pKeyPointArray[iCurKeyPoint].fY = (float) p1->y;
		
		// Advance to the next
		iCurKeyPoint++;

		return iCurKeyPoint;
	}

	// New geometry vectors for the left and right halves of the curve

	// Subdivide

	memcpy(&pGl[0], p1, sizeof(POINT));
	
	pGl[1].x = (p1->x + p2->x) >> 1;
	pGl[1].y = (p1->y + p2->y) >> 1;
	
	pGr[1].x = (p2->x + p3->x) >> 1;
	pGr[1].y = (p2->y + p3->y) >> 1;
	
	pGl[2].x = pGr[0].x = (pGl[1].x + pGr[1].x) >> 1;
	pGl[2].y = pGr[0].y = (pGl[1].y + pGr[1].y) >> 1;
	
	memcpy(&pGr[2], p3, sizeof(POINT));

	// Call recursively for left and right halves
	QuadricSubdivide(&pGl[0], &pGl[1], &pGl[2], --iLevel, pKeyPointArray, false);
	QuadricSubdivide(&pGr[0], &pGr[1], &pGr[2], iLevel, pKeyPointArray, false);

	// Add the end of the curve to the keypoint list if this is the first 
	// recursion level
	if (bFirstSubdivision)
	{
		pKeyPointArray[iCurKeyPoint].fX = (float) p3->x;
		pKeyPointArray[iCurKeyPoint].fY = (float) p3->y;

		iCurKeyPoint++;
	}

	return iCurKeyPoint;
}

void CClouds::TraceCurveSegment(LPPOINT p1, LPPOINT p2, int iUCurveDeltaX, 
								   int iUCurveDeltaY, unsigned int *pScanlineIntersection)
{
	//////////////////////////////////////////////////////////////////////
	// Use the Bresenham line drawing algorithm to find the scanline
	// intersection points
	//////////////////////////////////////////////////////////////////////

	int iDeltaX, iDeltaY;
	int iUDeltaX, iUDeltaY;
	int iXAdd, iYAdd;
	int iError, iLoop;

	iDeltaX = p2->x - p1->x; // Work out X delta
	iDeltaY = p2->y - p1->y; // Work out Y delta

	iUDeltaX = abs(iDeltaX); // iUDeltaX is the unsigned X delta
	iUDeltaY = abs(iDeltaY); // iUDeltaY is the unsigned Y delta

	// Work out direction to step in the Y direction
	if (iDeltaX < 0)
		iXAdd = -1;
	else
		iXAdd = 1;

	// Work out direction to step in the Y direction
	if (iDeltaY < 0)
		iYAdd = -1;
	else
		iYAdd = 1;

	iError = 0;
	iLoop = 0;

	if (iUDeltaX > iUDeltaY)
	{
		// Delta X > Delta Y
		do                                              
		{
			iError += iUDeltaY;

			// Time to move up / down ?
			if (iError >= iUDeltaX)	
			{
				iError -= iUDeltaX;
				p1->y += iYAdd;
			}

			iLoop++;

			// Save scanline intersection
			if (iUCurveDeltaX < iUCurveDeltaY)
				pScanlineIntersection[p1->y] = p1->x;
			else
				pScanlineIntersection[p1->x] = p1->y;

			// Move horizontally
			p1->x += iXAdd;			
		}
		while (iLoop < iUDeltaX); // Repeat for x length of line
	}
	else
	{
		// Delta Y > Delta X
		do                                              
		{
			iError += iUDeltaX;

			// Time to move left / right?			
			if (iError >= iUDeltaY)	
			{
				iError -= iUDeltaY;
				// Move across
				p1->x += iXAdd;		
			}

			iLoop++;

			// Save scanline intersection
			if (iUCurveDeltaX < iUCurveDeltaY)
				pScanlineIntersection[p1->y] = p1->x;
			else
				pScanlineIntersection[p1->x] = p1->y;
			
			// Move up / down a row
			p1->y += iYAdd;	
		}
		while (iLoop < iUDeltaY); // Repeat for y length of line
	}
}

void CClouds::RandomPoints(LPPOINT p1, LPPOINT p2)
{
	//////////////////////////////////////////////////////////////////////
	// Make two random points that lay on the bitmap's boundaries
	//////////////////////////////////////////////////////////////////////

	bool bSwapSides;

	assert(p1 && p2);

	bSwapSides = (rand() % 2) ? true : false;

	if (rand() % 2)
	{
		p1->x = (long) (rand() / (float) RAND_MAX * m_iWidth);
		p1->y = bSwapSides ? 0 : m_iHeight - 1;
		p2->x = (long) (rand() / (float) RAND_MAX * m_iWidth);
		p2->y = bSwapSides ? m_iHeight - 1 : 0;
	}
	else
	{
		p1->x = bSwapSides ? m_iWidth - 1 : 0;
		p1->y = (long) (rand() / (float) RAND_MAX * m_iHeight);
		p2->x = bSwapSides ? 0 : m_iWidth - 1;
		p2->y = (long) (rand() / (float) RAND_MAX * m_iHeight);
	}
}
*/
