#include "../Mojo/RuMojo_Helper.h"

#pragma unmanaged

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

BOOL Mojo_ResetHeight(CRuMojo_AttributeParcel<REAL> *hfParcel, REAL height)
{
	for(INT32 z = 0; z < hfParcel->GetParcelHeight(); ++z)
	{
		for(INT32 x = 0; x < hfParcel->GetParcelWidth(); ++x)
		{
			hfParcel->GetParcel()[x + z * hfParcel->GetParcelWidth()] = height;
		}
	}

	return TRUE;
}

BOOL Mojo_PaintHeight(CRuMojo_AttributeParcel<REAL> *hfParcel, const CRuVector3 &center, REAL radius, REAL delta, REAL refHeight, BOOL adaptivePainting, RuFalloffFunction falloffFunction)
{
	// Get parcel data
	const CRuVector3 &apOrigin = hfParcel->GetParcelOrigin();
	REAL apSpacing = hfParcel->GetParcelSpacing();
	INT32 apWidth = hfParcel->GetParcelWidth();
	INT32 apHeight = hfParcel->GetParcelHeight();
	REAL *attributeField = hfParcel->GetParcel();

	// Figure out the area to alter
	CRuVector3 relativeCenter = center - apOrigin;

	INT32 sX = max(0, (INT32) ((relativeCenter.x - radius) / apSpacing));
	INT32 sZ = max(0, (INT32) ((relativeCenter.z - radius) / apSpacing));
	INT32 eX = min(apWidth, (INT32) ((relativeCenter.x + radius) / apSpacing + 1));
	INT32 eZ = min(apHeight, (INT32) ((relativeCenter.z + radius) / apSpacing + 1));

	REAL brushStrength = delta;

	refHeight += brushStrength * falloffFunction(0.0f, radius);

	// Check brush against height field
	for(INT32 z = sZ; z < eZ; ++z)
	{
		for(INT32 x = sX; x < eX; ++x)
		{
			CRuVector3 pointPos(apOrigin.x + x * apSpacing, center.y, apOrigin.z + z * apSpacing);

			REAL distance = (center - pointPos).Magnitude();

			if(distance <= radius)
			{
				REAL heightDelta = brushStrength * falloffFunction(distance, radius);

				if(heightDelta >= 0.0f)
				{
					if(!adaptivePainting || attributeField[x + z * apWidth] <= refHeight)
					{
						// Alter height field value
						attributeField[x + z * apWidth] += heightDelta;

					}
				}
				else
				{
					if(!adaptivePainting || attributeField[x + z * apWidth] >= refHeight)
					{
						// Alter height field value
						attributeField[x + z * apWidth] += heightDelta;
					}
				}
			}
		}
	}

	return TRUE;
}

BOOL Mojo_NoiseHeight(CRuMojo_AttributeParcel<REAL> *hfParcel, const CRuVector3 &center, REAL radius, REAL variation)
{
	// Get parcel data
	const CRuVector3 &apOrigin = hfParcel->GetParcelOrigin();
	REAL apSpacing = hfParcel->GetParcelSpacing();
	INT32 apWidth = hfParcel->GetParcelWidth();
	INT32 apHeight = hfParcel->GetParcelHeight();
	REAL *attributeField = hfParcel->GetParcel();

	// Figure out the area to alter
	CRuVector3 relativeCenter = center - apOrigin;

	INT32 sX = max(0, (INT32) ((relativeCenter.x - radius) / apSpacing));
	INT32 sZ = max(0, (INT32) ((relativeCenter.z - radius) / apSpacing));
	INT32 eX = min(apWidth, (INT32) ((relativeCenter.x + radius) / apSpacing + 1));
	INT32 eZ = min(apHeight, (INT32) ((relativeCenter.z + radius) / apSpacing + 1));

	// Check brush against height field
	for(INT32 z = sZ; z < eZ; ++z)
	{
		for(INT32 x = sX; x < eX; ++x)
		{
			CRuVector3 pointPos(apOrigin.x + x * apSpacing, center.y, apOrigin.z + z * apSpacing);

			REAL distance = (center - pointPos).Magnitude();

			if(distance <= radius)
			{
				// Alter height field value
				attributeField[x + z * apWidth] += (g_mtRand.GenerateFloat() - 0.5f) * variation;
			}
		}
	}

	return TRUE;
}

BOOL Mojo_SetHeight(CRuMojo_AttributeParcel<REAL> *hfParcel, const CRuVector3 &center, float radius, float height, float brushStrength, RuFalloffFunction falloffFunction)
{
	// Get parcel data
	const CRuVector3 &apOrigin = hfParcel->GetParcelOrigin();
	REAL apSpacing = hfParcel->GetParcelSpacing();
	INT32 apWidth = hfParcel->GetParcelWidth();
	INT32 apHeight = hfParcel->GetParcelHeight();
	REAL *attributeField = hfParcel->GetParcel();

	// Figure out the area to alter
	CRuVector3 relativeCenter = center - apOrigin;

	INT32 sX = max(0, (INT32) ((relativeCenter.x - radius) / apSpacing));
	INT32 sZ = max(0, (INT32) ((relativeCenter.z - radius) / apSpacing));
	INT32 eX = min(apWidth, (INT32) ((relativeCenter.x + radius) / apSpacing + 1));
	INT32 eZ = min(apHeight, (INT32) ((relativeCenter.z + radius) / apSpacing + 1));

	if(falloffFunction)
	{
		if(brushStrength < 0.0f)
		{
			brushStrength = -brushStrength;

			// Check brush against height field
			for(INT32 z = sZ; z < eZ; ++z)
			{
				for(INT32 x = sX; x < eX; ++x)
				{
					CRuVector3 pointPos(apOrigin.x + x * apSpacing, center.y, apOrigin.z + z * apSpacing);

					float distance = (center - pointPos).Magnitude();

					if(distance <= radius)
					{
						// Alter height field value
						REAL diff = height - attributeField[x + z * apWidth];

						if(diff > 0.0f)
						{
							attributeField[x + z * apWidth] += diff * brushStrength * 0.05f;
							attributeField[x + z * apWidth] = min(attributeField[x + z * apWidth], height);
						}
					}
				}
			}
		}
		else
		{
			// Check brush against height field
			for(INT32 z = sZ; z < eZ; ++z)
			{
				for(INT32 x = sX; x < eX; ++x)
				{
					CRuVector3 pointPos(apOrigin.x + x * apSpacing, center.y, apOrigin.z + z * apSpacing);

					float distance = (center - pointPos).Magnitude();

					if(distance <= radius)
					{
						// Alter height field value
						REAL diff = height - attributeField[x + z * apWidth];

						if(diff > 0.0f)
						{
							attributeField[x + z * apWidth] += brushStrength * falloffFunction(distance, radius);
							attributeField[x + z * apWidth] = min(attributeField[x + z * apWidth], height);
						}
					}
				}
			}
		}
	}
	else
	{
		// Check brush against height field
		for(INT32 z = sZ; z < eZ; ++z)
		{
			for(INT32 x = sX; x < eX; ++x)
			{
				CRuVector3 pointPos(apOrigin.x + x * apSpacing, center.y, apOrigin.z + z * apSpacing);

				float distance = (center - pointPos).Magnitude();

				if(distance <= radius)
				{
					// Alter height field value
					attributeField[x + z * apWidth] = height;
				}
			}
		}
	}

	return TRUE;
}

BOOL Mojo_SmoothHeight(CRuMojo_AttributeParcel<REAL> *hfParcel, const CRuVector3 &center, float radius, float delta)
{
	// Get parcel data
	const CRuVector3 &apOrigin = hfParcel->GetParcelOrigin();
	REAL apSpacing = hfParcel->GetParcelSpacing();
	INT32 apWidth = hfParcel->GetParcelWidth();
	INT32 apHeight = hfParcel->GetParcelHeight();
	REAL *attributeField = hfParcel->GetParcel();

	// Figure out the area to alter
	CRuVector3 relativeCenter = center - apOrigin;

	INT32 sX = max(1, (INT32) ((relativeCenter.x - radius) / apSpacing));
	INT32 sZ = max(1, (INT32) ((relativeCenter.z - radius) / apSpacing));
	INT32 eX = min(apWidth - 1, (INT32) ((relativeCenter.x + radius) / apSpacing + 1));
	INT32 eZ = min(apHeight - 1, (INT32) ((relativeCenter.z + radius) / apSpacing + 1));

	float erosionRate = 0.0001f * delta;
	float weights[3] = { erosionRate, 1.0f - (2.0f * erosionRate), erosionRate };
	float *smoothedValues = ruNEW float [max(apWidth, apHeight)];

	// Smooth along x axis
	for(INT32 z = sZ; z < eZ; ++z)
	{
		// Calculate smoothed values and update into the smoothedValues array
		for(INT32 x = sX; x < eX; ++x)
		{
			CRuVector3 pointPos(apOrigin.x + x * apSpacing, center.y, apOrigin.z + z * apSpacing);

			float distance = (center - pointPos).Magnitude();

			if(distance <= radius)
			{
				float height = 0.0f;

				for(INT32 a = -1; a < 2; ++a)
				{
					height += attributeField[(x + a) + z * apWidth] * weights[a + 1];
				}

				smoothedValues[x] = height;
			}
			else
			{
				smoothedValues[x] = attributeField[x + z * apWidth];
			}
		}

		// Update smoothed values
		for(INT32 i = sX; i < eX; ++i)
		{
			attributeField[i + z * apWidth] = smoothedValues[i];
		}
	}

	// Smooth along z axis
	for(INT32 x = sX; x < eX; ++x)
	{
		// Calculate smoothed values and update into the smoothedValues array
		for(INT32 z = sZ; z < eZ; ++z)
		{
			CRuVector3 pointPos(apOrigin.x + x * apSpacing, center.y, apOrigin.z + z * apSpacing);

			float distance = (center - pointPos).Magnitude();

			if(distance <= radius)
			{
				float height = 0.0f;

				for(INT32 a = -1; a < 2; ++a)
				{
					height += attributeField[x + (z + a) * apWidth] * weights[a + 1];
				}

				smoothedValues[z] = height;
			}
			else
			{
				smoothedValues[z] = attributeField[x + z * apWidth];
			}
		}

		// Update smoothed values
		for(INT32 i = sZ; i < eZ; ++i)
		{
			attributeField[x + i * apWidth] = smoothedValues[i];
		}
	}

	delete[] smoothedValues;

	return TRUE;
}

CRuMesh *Mojo_BuildMeshFromHeightField(CRuMojo_AttributeParcel<REAL> *hfParcel, CRuMojo_AttributeParcel<RUCOLOR> *cfParcel, CRuMojo_AttributeParcel<BYTE> *flagParcel, BYTE flagValue)
{
	INT32 vertexGap = 1;

	// Get height field parcel data
	const CRuVector3 &hfOrigin = hfParcel->GetParcelOrigin();
	REAL hfSpacing = hfParcel->GetParcelSpacing();
	INT32 hfWidth = hfParcel->GetParcelWidth();
	INT32 hfHeight = hfParcel->GetParcelHeight();
	INT32 hfOverlapGap = hfParcel->GetParcelOverlapGap();
	REAL *heightField = hfParcel->GetParcel();

	// Get height field parcel data
	const CRuVector3 &cfOrigin = cfParcel->GetParcelOrigin();
	REAL cfSpacing = cfParcel->GetParcelSpacing();
	INT32 cfWidth = cfParcel->GetParcelWidth();
	INT32 cfHeight = cfParcel->GetParcelHeight();
	INT32 cfOverlapGap = cfParcel->GetParcelOverlapGap();
	RUCOLOR *colorField = cfParcel->GetParcel();

	// Get flag parcel data
	const CRuVector3 &fpOrigin = flagParcel->GetParcelOrigin();
	REAL fpSpacing = flagParcel->GetParcelSpacing();
	INT32 fpWidth = flagParcel->GetParcelWidth();
	INT32 fpHeight = flagParcel->GetParcelHeight();
	INT32 fpOverlapGap = flagParcel->GetParcelOverlapGap();
	BYTE *attributeField = flagParcel->GetParcel();

	// Calculate dimensions of mesh grid
	INT32 fieldGridWidth = (hfWidth - hfOverlapGap * 2) - 1;
	INT32 fieldGridHeight = (hfHeight - hfOverlapGap * 2) - 1;

	// Create temporary holding arrays for mesh data
	CRuVector3 *vertexArray = ruNEW CRuVector3 [(hfWidth - hfOverlapGap * 2) * (hfHeight - hfOverlapGap * 2)];
	RUCOLOR *diffuseArray = ruNEW RUCOLOR [(hfWidth - hfOverlapGap * 2) * (hfHeight - hfOverlapGap * 2)];
	UINT16 *indexArray = ruNEW UINT16 [fieldGridWidth * fieldGridHeight * 6];

	// Copy mesh vertices
	for(INT32 z = 0, i = 0; z <= fieldGridHeight; ++z)
	{
		for(INT32 x = 0; x <= fieldGridWidth; ++x, ++i)
		{
			vertexArray[i].Set	(
									(x * hfSpacing * vertexGap) + (hfOrigin.m_x + hfSpacing * hfOverlapGap),
									heightField[(x * vertexGap) + hfOverlapGap + ((z * vertexGap) + hfOverlapGap) * hfWidth],
									(z * hfSpacing * vertexGap) + (hfOrigin.m_z + hfSpacing * hfOverlapGap)
								);
		}
	}

	// Set up diffuse color
	if(attributeField)
	{
		for(INT32 z = 0, i = 0; z <= fieldGridHeight; ++z)
		{
			for(INT32 x = 0; x <= fieldGridWidth; ++x, ++i)
			{
				REAL alpha = (REAL) (attributeField[(x * vertexGap) + fpOverlapGap + ((z * vertexGap) + fpOverlapGap) * fpWidth] & 0x3F) / 0x3F;
				diffuseArray[i] = RUCOLOR_ARGB((INT32) (alpha * 255.0f), 0x00, 0x00, 0x00) | (colorField[(x * vertexGap) + cfOverlapGap + ((z * vertexGap) + cfOverlapGap) * cfWidth] & 0x00FFFFFF);
			}
		}
	}

	// Build indices for the mesh body
	INT32 evenFaceConstruction[]	= { 0, 3, 4, 0, 4, 1, 1, 4, 2, 2, 4, 5 };
	INT32 oddFaceConstruction[]		= { 0, 3, 1, 1, 3, 4, 1, 5, 2, 1, 4, 5 };

	INT32 indexCount = 0;
	UINT16 indexSet[6];

	INT32 vaZStride = (fieldGridWidth + 1);

	for(INT32 z = 0; z < fieldGridHeight; ++z)
	{
		// Build indices for four faces at a time
		for(INT32 x = 0; x < fieldGridWidth; x += 2)
		{
			// Calculate the indices used in these four faces
			indexSet[0] = x + z * vaZStride;
			indexSet[1] = indexSet[0] + vertexGap;
			indexSet[2] = indexSet[1] + vertexGap;
			indexSet[3] = indexSet[0] + vaZStride;
			indexSet[4] = indexSet[1] + vaZStride;
			indexSet[5] = indexSet[2] + vaZStride;

			// Alternate between even and odd rows
			INT32 *faceConstruction = (z % 2) ? oddFaceConstruction : evenFaceConstruction;

			BOOL visible = FALSE;
			for(INT32 j = 0; j < 12; ++j)
			{
				if(attributeField[indexSet[faceConstruction[j]]] & flagValue)
				{
					visible = TRUE;
				}
			}

			if(visible)
			{
				for(INT32 j = 0; j < 12; ++j)
				{
					indexArray[indexCount++] = indexSet[faceConstruction[j]];
				}
			}
		}
	}

	CRuMesh *mesh = ruNEW CRuMesh();
	mesh->Initialize(ruPRIMTYPE_TRIANGLELIST, (fieldGridWidth + 1) * (fieldGridHeight + 1), indexCount / 3, ruVERTEXFORMAT_POSITION | ruVERTEXFORMAT_DIFFUSE, 1);

	// Copy vertices
	memcpy(static_cast<CRuMorphTarget *>(mesh->GetMorphTarget(0))->GetPosition(), vertexArray, sizeof(CRuVector3) * (fieldGridWidth + 1) * (fieldGridHeight + 1));

	// Copy diffuse
	memcpy(static_cast<CRuMorphTarget *>(mesh->GetMorphTarget(0))->GetDiffuse(), diffuseArray, sizeof(RUCOLOR) * (fieldGridWidth + 1) * (fieldGridHeight + 1));

	// Copy indices
	memcpy(mesh->GetIndices(), indexArray, sizeof(UINT16) * indexCount);

	// Recalculate bounds
	mesh->CalculateBounds();

	// Free resources
	delete[] vertexArray;
	delete[] diffuseArray;
	delete[] indexArray;

	return mesh;
}

BOOL Mojo_SerializeFrom(CRuMojo_AttributeParcel<REAL> **hfParcel, IRuStream *stream)
{
	// Read field origin
	CRuVector3 parcelOrigin;
	stream->Read(&parcelOrigin.m_x, sizeof(REAL));
	stream->Read(&parcelOrigin.m_y, sizeof(REAL));
	stream->Read(&parcelOrigin.m_z, sizeof(REAL));

	// Read parcel spacing
	REAL parcelSpacing;
	stream->Read(&parcelSpacing, sizeof(REAL));

	// Read overlap gap
	INT32 parcelOverlapGap;
	stream->Read(&parcelOverlapGap, sizeof(INT32));

	// Read dimensions
	INT32 parcelWidth;
	stream->Read(&parcelWidth, sizeof(INT32));

	INT32 parcelHeight;
	stream->Read(&parcelHeight, sizeof(INT32));

	// Initialize parcel
	*hfParcel = ruNEW CRuMojo_AttributeParcel<REAL>(parcelWidth, parcelHeight);
	(*hfParcel)->SetParcelOrigin(parcelOrigin);
	(*hfParcel)->SetParcelSpacing(parcelSpacing);
	(*hfParcel)->SetParcelOverlapGap(parcelOverlapGap);

	// Read minima
	REAL minima;
	stream->Read(&minima, sizeof(REAL));

	// Read maxima
	REAL maxima;
	stream->Read(&maxima, sizeof(REAL));

	// Read quantized height values
	INT32 numHeightValues = parcelWidth * parcelHeight;
	UINT16 *quantizedHFData = ruNEW UINT16 [numHeightValues];
	stream->Read(quantizedHFData, sizeof(UINT16) * numHeightValues);

	// De-quantize height values
	REAL heightRange = maxima - minima;
	REAL *hfData = (*hfParcel)->GetParcel();
	for(INT32 i = 0; i < numHeightValues; ++i)
	{
		hfData[i] = (quantizedHFData[i] / 60000.0f) * heightRange + minima;
	}

	// Release temporaries
	delete[] quantizedHFData;

	return TRUE;
}

BOOL Mojo_SerializeTo(CRuMojo_AttributeParcel<REAL> *hfParcel, IRuStream *stream)
{
	REAL *hfData = hfParcel->GetParcel();
	INT32 numHeightValues = hfParcel->GetParcelWidth() * hfParcel->GetParcelHeight();

	// Scan through height field parcel to find minima and maxima
	REAL minima = FLT_MAX;
	REAL maxima = -FLT_MAX;

	for(INT32 i = 0; i < numHeightValues; ++i)
	{
		minima = min(minima, hfData[i]);
		maxima = max(maxima, hfData[i]);
	}

	REAL heightRange = maxima - minima;

	// Quantize height values into 16-bit integers
	UINT16 *quantizedHFData = ruNEW UINT16 [numHeightValues];
	for(INT32 i = 0; i < numHeightValues; ++i)
	{
		quantizedHFData[i] = (UINT16) (((hfData[i] - minima) / heightRange) * 60000.0f);
	}

	// Write field origin
	stream->Write(&hfParcel->GetParcelOrigin().m_x, sizeof(REAL));
	stream->Write(&hfParcel->GetParcelOrigin().m_y, sizeof(REAL));
	stream->Write(&hfParcel->GetParcelOrigin().m_z, sizeof(REAL));

	// Write parcel spacing
	REAL parcelSpacing = hfParcel->GetParcelSpacing();
	stream->Write(&parcelSpacing, sizeof(REAL));

	// Write overlap gap
	INT32 parcelOverlapGap = hfParcel->GetParcelOverlapGap();
	stream->Write(&parcelOverlapGap, sizeof(INT32));

	// Write dimensions
	INT32 parcelWidth = hfParcel->GetParcelWidth();
	stream->Write(&parcelWidth, sizeof(INT32));

	INT32 parcelHeight = hfParcel->GetParcelHeight();
	stream->Write(&parcelHeight, sizeof(INT32));

	// Write minima
	stream->Write(&minima, sizeof(REAL));

	// Write maxima
	stream->Write(&maxima, sizeof(REAL));

	// Write quantized height values
	stream->Write(quantizedHFData, sizeof(UINT16) * numHeightValues);

	// Release temporaries
	delete[] quantizedHFData;

	return TRUE;
}

REAL Mojo_GetHeightAtPoint(CRuMojo_AttributeParcel<REAL> *hfParcel, const CRuVector3 &point)
{
	// Get parcel data
	const CRuVector3 &apOrigin = hfParcel->GetParcelOrigin();
	REAL apSpacing = hfParcel->GetParcelSpacing();
	INT32 apWidth = hfParcel->GetParcelWidth();
	INT32 apHeight = hfParcel->GetParcelHeight();
	REAL *attributeField = hfParcel->GetParcel();

	// Find point coordinates relative to height field
	CRuVector3 hfPoint = (point - apOrigin) / apSpacing;

	// Find four height field points enclosing the specified point
	INT32 iX0 = max(0, min(apWidth, static_cast<INT32>(hfPoint.x)));
	INT32 iX1 = max(0, min(apWidth, iX0 + 1));
	INT32 iZ0 = max(0, min(apHeight, static_cast<INT32>(hfPoint.z)));
	INT32 iZ1 = max(0, min(apHeight, iZ0 + 1));

	// Find weights along X and Z directions
	REAL xW1 = (hfPoint.x - iX0);
	REAL xW0 = 1.0f - xW1;
	REAL zW1 = (hfPoint.z - iZ0);
	REAL zW0 = 1.0f - zW1;

	// Interpolate along X
	REAL xH0 = attributeField[iX0 + iZ0 * apWidth] * xW0 + attributeField[iX1 + iZ0 * apWidth] * xW1;
	REAL xH1 = attributeField[iX0 + iZ1 * apWidth] * xW0 + attributeField[iX1 + iZ1 * apWidth] * xW1;

	// Interpolate along Z
	REAL interpolatedHeight = xH0 * zW0 + xH1 * zW1;

	return interpolatedHeight;
}

REAL Mojo_GetSlopeAtPoint(CRuMojo_AttributeParcel<REAL> *hfParcel, const CRuVector3 &point)
{
	// Get parcel data
	const CRuVector3 &apOrigin = hfParcel->GetParcelOrigin();
	REAL apSpacing = hfParcel->GetParcelSpacing();
	INT32 apWidth = hfParcel->GetParcelWidth();
	INT32 apHeight = hfParcel->GetParcelHeight();
	REAL *attributeField = hfParcel->GetParcel();

	// Find point coordinates relative to height field
	CRuVector3 hfPoint = (point - apOrigin) / apSpacing;

	// Find four height field points enclosing the specified point
	INT32 iX0 = max(0, min(apWidth, static_cast<INT32>(hfPoint.x)));
	INT32 iX1 = max(0, min(apWidth, iX0 + 1));
	INT32 iZ0 = max(0, min(apHeight, static_cast<INT32>(hfPoint.z)));
	INT32 iZ1 = max(0, min(apHeight, iZ0 + 1));

	// Find weights along X and Z directions
	REAL xW1 = (hfPoint.x - iX0);
	REAL xW0 = 1.0f - xW1;
	REAL zW1 = (hfPoint.z - iZ0);
	REAL zW0 = 1.0f - zW1;

	// Interpolate along X
	REAL xH0 = attributeField[iX0 + iZ0 * apWidth] * xW0 + attributeField[iX1 + iZ0 * apWidth] * xW1;
	REAL xH1 = attributeField[iX0 + iZ1 * apWidth] * xW0 + attributeField[iX1 + iZ1 * apWidth] * xW1;

	// Interpolate along Z
	REAL zH0 = attributeField[iX0 + iZ0 * apWidth] * zW0 + attributeField[iX0 + iZ1 * apWidth] * zW1;
	REAL zH1 = attributeField[iX1 + iZ0 * apWidth] * zW0 + attributeField[iX1 + iZ1 * apWidth] * zW1;

	// Get quick estimate of normal at point
	CRuVector3 normal(zH0 - zH1, apSpacing * 2.0f, xH1 - xH0);
	normal.Normalize();

	return max(0.0f, 1.0f - DotProduct(CRuVector3(0.0f, 1.0f, 0.0f), normal));
}

BOOL Mojo_ResetFlags(CRuMojo_AttributeParcel<BYTE> *parcel, BYTE flagValue, BYTE flagMask)
{
	for(INT32 z = 0; z < parcel->GetParcelHeight(); ++z)
	{
		for(INT32 x = 0; x < parcel->GetParcelWidth(); ++x)
		{
			parcel->GetParcel()[x + z * parcel->GetParcelWidth()] = (parcel->GetParcel()[x + z * parcel->GetParcelWidth()] & ~flagMask) | (flagValue & flagMask);;
		}
	}

	return TRUE;
}

BOOL Mojo_PaintFlags(CRuMojo_AttributeParcel<BYTE> *parcel, const CRuVector3 &center, REAL radius, BYTE flagValue, BYTE flagMask)
{
	// Get parcel data
	const CRuVector3 &apOrigin = parcel->GetParcelOrigin();
	REAL apSpacing = parcel->GetParcelSpacing();
	INT32 apWidth = parcel->GetParcelWidth();
	INT32 apHeight = parcel->GetParcelHeight();
	BYTE *attributeField = parcel->GetParcel();

	// Figure out the area to alter
	CRuVector3 relativeCenter = center - apOrigin;

	INT32 sX = max(0, (INT32) ((relativeCenter.x - radius) / apSpacing));
	INT32 sZ = max(0, (INT32) ((relativeCenter.z - radius) / apSpacing));
	INT32 eX = min(apWidth, (INT32) ((relativeCenter.x + radius) / apSpacing + 1));
	INT32 eZ = min(apHeight, (INT32) ((relativeCenter.z + radius) / apSpacing + 1));

	// Check brush against height field
	for(INT32 z = sZ; z < eZ; ++z)
	{
		for(INT32 x = sX; x < eX; ++x)
		{
			CRuVector3 pointPos(apOrigin.x + x * apSpacing, center.y, apOrigin.z + z * apSpacing);

			REAL distance = (center - pointPos).Magnitude();

			if(distance <= radius)
			{
				// Alter flag value
				attributeField[x + z * apWidth] = (attributeField[x + z * apWidth] & ~flagMask) | (flagValue & flagMask);
			}
		}
	}

	return TRUE;
}

BOOL Mojo_PaintValue(CRuMojo_AttributeParcel<BYTE> *parcel, const CRuVector3 &center, REAL radius, REAL delta, RuFalloffFunction falloffFunction, BYTE flagMask, REAL lowerLimit, REAL upperLimit)
{
	// Get parcel data
	const CRuVector3 &apOrigin = parcel->GetParcelOrigin();
	REAL apSpacing = parcel->GetParcelSpacing();
	INT32 apWidth = parcel->GetParcelWidth();
	INT32 apHeight = parcel->GetParcelHeight();
	BYTE *attributeField = parcel->GetParcel();

	// Figure out the area to alter
	CRuVector3 relativeCenter = center - apOrigin;

	INT32 sX = max(0, (INT32) ((relativeCenter.x - radius) / apSpacing));
	INT32 sZ = max(0, (INT32) ((relativeCenter.z - radius) / apSpacing));
	INT32 eX = min(apWidth, (INT32) ((relativeCenter.x + radius) / apSpacing + 1));
	INT32 eZ = min(apHeight, (INT32) ((relativeCenter.z + radius) / apSpacing + 1));

	// Check brush against height field
	for(INT32 z = sZ; z < eZ; ++z)
	{
		for(INT32 x = sX; x < eX; ++x)
		{
			CRuVector3 pointPos(apOrigin.x + x * apSpacing, center.y, apOrigin.z + z * apSpacing);

			REAL distance = (center - pointPos).Magnitude();

			if(distance <= radius)
			{
				REAL oldValue = (REAL) (attributeField[x + z * apWidth] & flagMask) / flagMask;

				if(delta < 0.0f)
				{
					if(oldValue >= lowerLimit)
					{
						oldValue += delta * falloffFunction(distance, radius);
						oldValue = min(1.0f, max(lowerLimit, oldValue));
					}
				}
				else
				{
					if(oldValue <= upperLimit)
					{
						oldValue += delta * falloffFunction(distance, radius);
						oldValue = min(upperLimit, max(0.0f, oldValue));
					}
				}

				BYTE newValue = (BYTE) (oldValue * flagMask);

				// Alter flag value
				attributeField[x + z * apWidth] = (attributeField[x + z * apWidth] & ~flagMask) | (newValue & flagMask);
			}
		}
	}

	return TRUE;
}

BOOL Mojo_PaintColor(CRuMojo_AttributeParcel<RUCOLOR> *parcel, const CRuVector3 &center, REAL radius, RUCOLOR color, REAL delta, RuFalloffFunction falloffFunction)
{
	// Get parcel data
	const CRuVector3 &cfOrigin = parcel->GetParcelOrigin();
	REAL cfSpacing = parcel->GetParcelSpacing();
	INT32 cfWidth = parcel->GetParcelWidth();
	INT32 cfHeight = parcel->GetParcelHeight();
	RUCOLOR *colorField = parcel->GetParcel();

	// Figure out the area to alter
	CRuVector3 relativeCenter = center - cfOrigin;

	INT32 sX = max(0, (INT32) ((relativeCenter.x - radius) / cfSpacing));
	INT32 sZ = max(0, (INT32) ((relativeCenter.z - radius) / cfSpacing));
	INT32 eX = min(cfWidth, (INT32) ((relativeCenter.x + radius) / cfSpacing + 1));
	INT32 eZ = min(cfHeight, (INT32) ((relativeCenter.z + radius) / cfSpacing + 1));

	CRuColor4 c0(color);

	// Check brush against height field
	for(INT32 z = sZ; z < eZ; ++z)
	{
		for(INT32 x = sX; x < eX; ++x)
		{
			CRuVector3 pointPos(cfOrigin.x + x * cfSpacing, center.y, cfOrigin.z + z * cfSpacing);

			REAL distance = (center - pointPos).Magnitude();

			if(distance <= radius)
			{
				REAL t0 = falloffFunction(distance, radius);
				REAL t1 = 1.0f - t0;

				CRuColor4 c1(colorField[x + z * cfWidth]);

				colorField[x + z * cfWidth] = (c0 * t0 + c1 * t1).GetARGB();
			}
		}
	}

	return TRUE;
}

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

#pragma managed
