/* terrain.cpp: Class that implements the creation and management of a flat plane for the floor of the maze. Derived from H.S. Fortuna's (http://www.hsfortuna.pwp.blueyonder.co.uk/) class of the same name but with some alterations

   Copyright (C) 2008  Stuart William Coulson

   This program is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program.  If not, see <http://www.gnu.org/licenses/>.

*/

#include <sps2lib.h>
#include <sps2tags.h>
#include <sps2util.h>
#include <sps2regstructs.h>
#include "PS2Defines.h"
#include "sps2wrap.h"
#include "dma.h"
#include "pipeline.h"
#include "terrain.h"

CTerrain::CTerrain()
{
	// Initialise some default vaules
	m_iStaticAddr = 0;
	m_World = Matrix4x4::IDENTITY;
	m_WVP = Matrix4x4::IDENTITY;
	m_bWorldLoaded = false;
}

CTerrain::~CTerrain()
{
	// Nothing to destruct
}

void CTerrain::SetWorldMatrix(const Matrix4x4 & matWorld)
{
	m_World = matWorld;
	m_WVP = m_World * Pipeline.GetViewProjection();				//View projection matrix is now obtained from the Pipeline object
	m_bWorldLoaded = true;
}
	
void CTerrain::SetWVPMatrix(const Matrix4x4 & matWVP)
{
	m_WVP = matWVP;
}

void CTerrain::LoadTerrainData(int mazeWidth, int mazeHeight)
{
	// Get the address that we can call to later.
	m_iStaticAddr = VIFStaticDMA.GetPointer();

	VIFStaticDMA.Add32(FLUSH);			// Make sure VU1 isn't busy
	VIFStaticDMA.Add32(STCYCL(1,1));	// Unpack linearly, i.e. don't skip any spaces
	VIFStaticDMA.Add32(BASE(32));		// The double buffers start at VU1 address 16 (giving us 16 QW to store data that won't change)
	VIFStaticDMA.Add32(OFFSET(496));	// The size of each buffer.

	VIFStaticDMA.AddUnpack(V4_32, 0, 1);	// Unpack 8QW of data that won't change

	VIFStaticDMA.AddVector(Pipeline.GetScaleVector());

	// The verts are 3QW each, and the 504QW is split in half. Therefore we have 252QW for each buffer.
	// Each buffer also has 2QW of info (GIFTag + Numverts), so we can process 83 vertices (249QW)
	// in each buffer. 
	
	// Each tile consists of 6 vertices (two triangles),  therefore it is possible to store 13 tiles in each buffer. The other variables are used to keep track of which tile is being drawn and the total number of tiles to draw. The letter x and y are used however this is just for convenience. The plane is drawn on the xz-axis.
	int numberTilesPerChunk = 13, numberTiles = (mazeWidth * mazeHeight), xCell = 0, yCell = 0;
	
	bool bFirst = true;
	
	// Two constants that represent the top left hand corner (from the camera's perspective) of the maze. Centred  on the origin. 
	const float topCornerPosX = -(tileSize * mazeWidth) / 2;
	const float topCornerPosY = -(tileSize * mazeHeight) / 2;
	
	while(numberTiles > 0)			// Loop until all tiles have been loaded to memory
	{
		int numberTilesThisChunk;						// The number of tiles to load into the current buffer

		if(numberTiles > numberTilesPerChunk)			// If there are more tiles left to draw than it is possible to fit in a single buffer
		{
			numberTilesThisChunk = numberTilesPerChunk;	// Fill the buffer with the maximum number of tiles possible
			numberTiles -= numberTilesThisChunk;		// Deduct the number of tiles added to the last buffer from the total to be drawn
		}
		else											// If there are not enough tiles left to fill a complete buffer
		{
			numberTilesThisChunk = numberTiles;			// Put the tiles left into a buffer
			numberTiles -= numberTilesThisChunk;		// Reduce the total number of tiles by that placed into the buffer (numberTiles should now be zero)
		}
		
		
		VIFStaticDMA.AddUnpack(V4_32, 0, ((numberTilesThisChunk * 18) + 2), 1);			// Unpack the correct amount of data into static based on the number of tiles to be drawn into this buffer.
		VIFStaticDMA.Add128((numberTilesThisChunk * 18));								// Value representing the number of QW in this chunk

		// Set up the GIFTag. Have used single triangles in pairs for tiles rather than tristrips as when the texture was applied to the tristrip it was difficult to get it to look right. Plus to get the texture to repeat itself on each tile is easiest with two triangles.
		VIFStaticDMA.Add128(
			GS_GIFTAG_BATCH(numberTilesThisChunk * 6, // NLOOP
					1,	// EOP
					1,	// PRE
					// GS_PRIM(    PRIM, 	      	    IIP,      TME,    FGE,  ABE     , AA1,     FST,      CTXT,    FIX)
					GS_PRIM(GS_TRIANGLE,  1,   1,   0 , 0,   0,   0,    0,   0),	// PRIM
					GIF_FLG_PACKED,
					GS_BATCH_3(GIF_REG_ST, GIF_REG_RGBAQ, GIF_REG_XYZ2)));

		// For each tile to put into the current buffer, add its 6 vertices (18 QWs) to static memory. Use the xCell and yCell values to position tiles correctly based on tile size and the position of top left corner.
		for(int currentTile = 0; currentTile < numberTilesThisChunk; currentTile++)
		{
			VIFStaticDMA.AddVector(Vector4(0, 1, 1, 0));																						// Tex coords + in W, a flag to set ADC or not.
			VIFStaticDMA.AddVector(Vector4(0, 1, 0, 0));																						// Normal
			VIFStaticDMA.AddVector(Vector4((topCornerPosX + (tileSize * xCell)), 0, (topCornerPosY + (tileSize * (yCell + 1))), 1));			// Position
						
			VIFStaticDMA.AddVector(Vector4(0, 0, 1, 0));																						// Tex coords + in W, a flag to set ADC or not.
			VIFStaticDMA.AddVector(Vector4(0, 1, 0, 0));																						// Normal
			VIFStaticDMA.AddVector(Vector4((topCornerPosX + (tileSize * xCell)), 0, (topCornerPosY + (tileSize * yCell)), 1));					// Position
						
			VIFStaticDMA.AddVector(Vector4(1, 0, 1, 0));																						// Tex coords + in W, a flag to set ADC or not.
			VIFStaticDMA.AddVector(Vector4(0, 1, 0, 0));																						// Normal
			VIFStaticDMA.AddVector(Vector4((topCornerPosX + (tileSize * (xCell + 1))), 0, (topCornerPosY + (tileSize * yCell)), 1));			// Position
						
			VIFStaticDMA.AddVector(Vector4(0, 1, 1, 0));																						// Tex coords + in W, a flag to set ADC or not.
			VIFStaticDMA.AddVector(Vector4(0, 1, 0, 0));																						// Normal
			VIFStaticDMA.AddVector(Vector4((topCornerPosX + (tileSize * xCell)), 0, (topCornerPosY + (tileSize * (yCell + 1))), 1));			// Position
						
			VIFStaticDMA.AddVector(Vector4(1, 0, 1, 0));																						// Tex coords + in W, a flag to set ADC or not.
			VIFStaticDMA.AddVector(Vector4(0, 1, 0, 0));																						// Normal
			VIFStaticDMA.AddVector(Vector4((topCornerPosX + (tileSize * (xCell + 1))), 0, (topCornerPosY + (tileSize * yCell)), 1));			// Position
						
			VIFStaticDMA.AddVector(Vector4(1, 1, 1, 0));																						// Tex coords + in W, a flag to set ADC or not.
			VIFStaticDMA.AddVector(Vector4(0, 1, 0, 0));																						// Normal
			VIFStaticDMA.AddVector(Vector4((topCornerPosX + (tileSize * (xCell + 1))), 0, (topCornerPosY + (tileSize * (yCell + 1))), 1));		// Position
						
			if (xCell == (mazeWidth - 1)) {			// If the number of tiles drawn across (xCell value) is equal to the width of the maze being drawn - 1, increment yCell and reset xCell to 0 to begin the next row
				xCell = 0;
				yCell++;
			}
			else xCell++;				// Else just move to the next tile in the row
		}
		if(bFirst)
		{
			bFirst = false;
			// If this is the first buffer then run the VU code from address 0 so that the initialisation
			// VU code is ran.
			VIFStaticDMA.Add32(MSCALL(0));
		}
		else
		{
			// For multiple times MSCNT means to continue where the VU code finished last time at the --cont.
			VIFStaticDMA.Add32(MSCNT);
		}

		// Notice that due to the quad buffer setup we don't need to flush here because we can upload, process, and render
		// all in parallel now. (the XGKick in the vector code will flush to make sure that we don't get ahead of ourselves)
	}

	VIFStaticDMA.Add32(FLUSH);
	VIFStaticDMA.DMARet();
}


void CTerrain::Render(void) const
{
	assert(m_bWorldLoaded && m_iStaticAddr);
	
	// Wait for all to be idle in VU1 land
	// Before uploading the data
	VIFDynamicDMA.Add32(FLUSH);
	
	// Upload the matrices in the dynamic buffer
	// because they can change every frame.
	VIFDynamicDMA.AddUnpack(V4_32, 1, 16);
	VIFDynamicDMA.AddMatrix(Pipeline.GetLightDirs());
	VIFDynamicDMA.AddMatrix(Pipeline.GetLightCols());
	VIFDynamicDMA.AddMatrix(m_WVP);
	VIFDynamicDMA.AddMatrix(m_World);
	
	// Call the packet with the static data that doesn't change per frame.
	VIFDynamicDMA.DMACall(m_iStaticAddr);
}
