////////////////////////////////////////////////////////////////////////////////
//
// Copyright 2016 RWS Inc, All Rights Reserved
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of version 2 of the GNU General Public License as published by
// the Free Software Foundation
//
// 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, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
//
#include "System.h"
#ifdef PATHS_IN_INCLUDES
#include "GREEN/3D/pipeline.h"
#include "GREEN/3D/render.h"
#include "../../../../PVR/pvr.h"
#else
#include "pipeline.h"
#endif
///////////////////////////////////////////////////////////////
// This is the highest level considered actually part of the 3d engine.
// It is the highest level control -> it decides how 3d pts map to 2d.
// You can customize 3d efects by instantiating your own versions of the 3d pipeline!
///////////////////////////////////////////////////////////////
//	PIPELINE - History
///////////////////////////////////////////////////////////////
//
//	07/23/97	JRD	Added support for generating shadows
//
///////////////////////////////////////////////////////////////

extern PVR::cRenderMgr *g_pvrRenderer;

int32_t RPipeLine::ms_lNumPts = 0;
int32_t RPipeLine::ms_lNumPipes = 0;

RP3d *RPipeLine::ms_pPts = NULL;

RPipeLine::RPipeLine()
{
	ms_lNumPipes++; // Track for deletion purposes!
	Init();
}

void RPipeLine::Init()
{	
	m_sUseBoundingRect = FALSE;	
	m_tScreen.Make1();
	m_tView.Make1();	
}

// assume the clip rect is identical situation to zBUF:
//
int16_t RPipeLine::Create(int32_t lNum, int16_t sW)
{
	//----------
	if (!lNum)
		return 0;

	if ((ms_pPts != NULL) && (lNum > ms_lNumPts))
	{
		free(ms_pPts);
		ms_pPts = NULL;
	}

	if (ms_pPts == NULL)
	{
		ms_pPts = (RP3d *)malloc(sizeof(RP3d) * lNum);
	}

	return 0;
}

RPipeLine::~RPipeLine()
{	
	ms_lNumPipes--;	   // prepare for full death:
	if (!ms_lNumPipes) // free ms_pPts
	{
		if (ms_pPts)
			free(ms_pPts);
		ms_lNumPts = 0;
		ms_pPts = NULL;
	}
}

void RPipeLine::Transform(RSop *pPts, RTransform &tObj)
{
	RTransform tFull;
	int32_t i;	

	tFull.Make1();
	tFull.Mul(m_tView.T, tObj.T);
	// If there were inhomogeneous transforms, you would need to
	// trasnform each pt by two transforms separately!
	tFull.PreMulBy(m_tScreen.T);
	// todo??
	tFull.UpdateMatr();

	// todo: h/w cache stuff?
	for (i = 0; i < pPts->m_lNum; i++)
	{		
  		__asm__ __volatile__ (
    		"fschg\n\t"
			"pref @%[vt1]\n\t" 			
    		"fschg\n"
			:
    		: [vt1] "r" (&pPts->m_pArray[i+1])			
    		: "memory" // clobbers
  		);

		tFull.TransformInto(pPts->m_pArray[i], ms_pPts[i]);
		// Note that you can now use RP3d directly with the renderers!
	}
}

// returns 0 if pts are ClockWise! (Hidden)
// (to be used AFTER the view or screen transformation)
int16_t RPipeLine::NotCulled(RP3d *p1, RP3d *p2, RP3d *p3)
{
	REAL ax, ay, bx, by;
	ax = p2->x - p1->x;
	ay = p2->y - p1->y;
	bx = p3->x - p1->x;
	by = p3->y - p1->y;

	if ((ax * by - ay * bx) >= 0)
		return 0;
	else
		return 1;
}

bool RPipeLine::RenderPVR(
	int16_t sDstX,
	int16_t sDstY,
	RMesh *pMesh,
	int16_t layer,
	RTexture *pTexColors,
	int16_t sFogOffset,	
	pvr_poly_hdr_t *hdr,
	int16_t sOffsetX /* = 0*/, // In: 2D offset for pimDst and pZB.
	int16_t sOffsetY /* = 0*/,
	int16_t sOffsetZ /* = 0*/) // In: 2D offset for pimDst and pZB.
{
	int32_t i;	
	uint16_t *psVertex = pMesh->m_pArray;
	uint8_t *pColor = pTexColors->m_pIndices;
					
	pvr_dr_state_t state;
	pvr_vertex_t *vert;

	RFixedS32 z1;
	RFixedS32 z2;
	RFixedS32 z3;
	uint32_t colour = 0xffffffff;
	// todo: move into pvr.h
	
	int16_t sApproxVerts = int16_t(rspSlowDiv(pMesh->m_sNum * 3.f, 1.5f));
	int16_t sNumVerts = 0;

	if (g_pvrRenderer->m_lNumVerts + sApproxVerts > PVR_MAX_MESH_VERTS)
	{		
		g_pvrRenderer->m_bIncFrameTokenLevel = true;
		return false;
	}

	if (g_pvrRenderer->m_fGlobalRScale != 1.f || g_pvrRenderer->m_fGlobalGScale != 1.f || g_pvrRenderer->m_fGlobalBScale != 1.f)
	{
		colour = PACK_ARGB8888(255, uint8_t(255.f * g_pvrRenderer->m_fGlobalRScale) , uint8_t(255.f * g_pvrRenderer->m_fGlobalGScale), uint8_t(255.f * g_pvrRenderer->m_fGlobalBScale));
	}
		
	float zOffset = (float(sOffsetZ)*0.01f) + ((layer + 1) * 1000);
	pvr_prim(hdr, sizeof(pvr_poly_hdr_t));
	pvr_dr_init(state);

	// todo: h/w cache stuff?
	for (i = 0; i < pMesh->m_sNum; i++, pColor++)
	{
		RP3d point1, point2, point3;		
		RP3d *p1 = ms_pPts + *psVertex++;
		RP3d *p2 = ms_pPts + *psVertex++;
		RP3d *p3 = ms_pPts + *psVertex++;
		float col = rspFastDiv(float(*pColor), 255.f);

		// Prefetch next colour & set of vertices into the cache
  		__asm__ __volatile__ (
    		"fschg\n\t"
			"pref @%[vt1]\n\t" 
			"pref @%[vt2]\n\t" 
			"pref @%[vt3]\n\t"    
			"pref @%[colour]\n\t"
    		"fschg\n"
			:
    		: [vt1] "r" (psVertex), [vt2] "r" (psVertex + 1), [vt3] "r" (psVertex + 2),			 
			  [colour] "r" (pColor + 1)
    		: "memory" // clobbers
  		);

		// todo: HW cull?
		if (NotCulled(p1, p2, p3))
		{					
			sNumVerts += 3;
			// todo: cleanup and rename these
			float x1 = p1->x;
			float x2 = p2->x;
			float x3 = p3->x;

			if (x3 < x1 && x1 < x2) // x3 - x1 - x2
			{
				x3 = MATH_Fast_Floorf(x3);
				x2 = MATH_Fast_Ceilf(x2);				
			}				
			else if (x2 < x1 && x1 < x3) // x2 - x1 - x3
			{
				x2 = MATH_Fast_Floorf(x2);					
				x3 = MATH_Fast_Ceilf(x3);				
			}			

			else if (x3 < x2 && x2 < x1) // x3 - x2 - x1
			{
				x1 = MATH_Fast_Ceilf(x1);
				x3 = MATH_Fast_Floorf(x3);				
			}
			else if (x1 < x2 && x2 < x3) // x1 - x2 -x3
			{
				x1 = MATH_Fast_Floorf(x1);
				x3 = MATH_Fast_Ceilf(x3);				
			}
			else if (x1 < x3 && x3 < x2) // x1 - x3 - x2
			{
				x1 = MATH_Fast_Floorf(x1);
				x2 = MATH_Fast_Ceilf(x2);
			}  
			else if (x2 < x3 && x3 < x1) // x2 - x3 - x1
			{
				x2 = MATH_Fast_Floorf(x2);					
				x1 = MATH_Fast_Ceilf(x1);				
			}
			else if (x1 == x2 && x2 < x3) // x1 x2 - x3
			{
				x1 = MATH_Fast_Floorf(x1);
				x2 = x1;
				x3 = MATH_Fast_Ceilf(x3);
			}		
			else if (x1 == x3 && x1 < x2 ) // x1 x3 - x2
			{
				x1 = MATH_Fast_Floorf(x1);
				x2 = MATH_Fast_Ceilf(x2);	
				x3 = x1;
			}			
			else if (x2 == x3 && x2 < x1 ) // x2 x3 - x1
			{
				x1 = MATH_Fast_Ceilf(x1);
				x2 = MATH_Fast_Floorf(x2);	
				x3 = x2;
			}
			else if (x2 == x3 && x2 > x1) // x1 - x2 x3
			{
				x1 = MATH_Fast_Floorf(x1);
				x2 = MATH_Fast_Ceilf(x2);
				x3 = x2;
			}		
			else if (x1 == x3 && x1 > x2) // x2 - x1 x3
			{
				x1 = MATH_Fast_Ceilf(x1);
				x2 = MATH_Fast_Floorf(x2);	
				x3 = x1;
			}			
			else if (x1 == x2 && x2 > x3 ) // x3 - x1 x2
			{
				x1 = MATH_Fast_Ceilf(x1);
				x2 = x1;	
				x3 = MATH_Fast_Floorf(x3);
			}

			// todo: clean up
			point1.x = x1;
			point2.x = x2;
			point3.x = x3;
			

			x1 = p1->y;
			x2 = p2->y;
			x3 = p3->y;

			
				if (x3 < x1 && x1 < x2) // x3 - x1 - x2
			{
				x3 = MATH_Fast_Floorf(x3);
				x2 = MATH_Fast_Ceilf(x2);				
			}				
			else if (x2 < x1 && x1 < x3) // x2 - x1 - x3
			{
				x2 = MATH_Fast_Floorf(x2);					
				x3 = MATH_Fast_Ceilf(x3);				
			}			

			else if (x3 < x2 && x2 < x1) // x3 - x2 - x1
			{
				x1 = MATH_Fast_Ceilf(x1);
				x3 = MATH_Fast_Floorf(x3);				
			}
			else if (x1 < x2 && x2 < x3) // x1 - x2 -x3
			{
				x1 = MATH_Fast_Floorf(x1);
				x3 = MATH_Fast_Ceilf(x3);				
			}
			else if (x1 < x3 && x3 < x2) // x1 - x3 - x2
			{
				x1 = MATH_Fast_Floorf(x1);
				x2 = MATH_Fast_Ceilf(x2);
			}  
			else if (x2 < x3 && x3 < x1) // x2 - x3 - x1
			{
				x2 = MATH_Fast_Floorf(x2);					
				x1 = MATH_Fast_Ceilf(x1);				
			}
			else if (x1 == x2 && x2 < x3) // x1 x2 - x3
			{
				x1 = MATH_Fast_Floorf(x1);
				x2 = x1;
				x3 = MATH_Fast_Ceilf(x3);
			}		
			else if (x1 == x3 && x1 < x2 ) // x1 x3 - x2
			{
				x1 = MATH_Fast_Floorf(x1);
				x2 = MATH_Fast_Ceilf(x2);	
				x3 = x1;
			}			
			else if (x2 == x3 && x2 < x1 ) // x2 x3 - x1
			{
				x1 = MATH_Fast_Ceilf(x1);
				x2 = MATH_Fast_Floorf(x2);	
				x3 = x2;
			}
			else if (x2 == x3 && x2 > x1) // x1 - x2 x3
			{
				x1 = MATH_Fast_Floorf(x1);
				x2 = MATH_Fast_Ceilf(x2);
				x3 = x2;
			}		
			else if (x1 == x3 && x1 > x2) // x2 - x1 x3
			{
				x1 = MATH_Fast_Ceilf(x1);
				x2 = MATH_Fast_Floorf(x2);	
				x3 = x1;
			}			
			else if (x1 == x2 && x2 > x3 ) // x3 - x1 x2
			{
				x1 = MATH_Fast_Ceilf(x1);
				x2 = x1;	
				x3 = MATH_Fast_Floorf(x3);
			}

			// todo: clean up
			point1.y = x1;
			point2.y = x2;
			point3.y = x3;

			point1.x += sDstX + sOffsetX;			
			point1.y += sDstY + sOffsetY;
			z1.mod = int16_t(p1->z);
			point1.z = ((p1->z + 1000) * .001f) + zOffset + 1; // Hack to ensure 2D & 3D objects are layered correctly
					
			point2.x += sDstX + sOffsetX;
			point2.y += sDstY + sOffsetY;
			z2.mod = int16_t(p2->z);
			point2.z = ((p2->z + 1000) * .001f) + zOffset + 1; // Hack to ensure 2D & 3D objects are layered correctly
			
			point3.x += sDstX + sOffsetX;
			point3.y += sDstY + sOffsetY;
			z3.mod = int16_t(p3->z);
			point3.z = ((p3->z + 1000) * .001f) + zOffset + 1; // Hack to ensure 2D & 3D objects are layered correctly					
	
			vert = pvr_dr_target(state);
			vert->flags = PVR_CMD_VERTEX;
			vert->x = point3.x;
			vert->y = point3.y;
			vert->z = point3.z;
			vert->u = col;
			vert->v = rspFastDiv(float(sFogOffset + z3.upper), 255.f);
			vert->argb = colour;
			pvr_dr_commit(vert);

			vert = pvr_dr_target(state);
			vert->flags = PVR_CMD_VERTEX;
			vert->x = point2.x;
			vert->y = point2.y;
			vert->z = point2.z;
			vert->u = col;
			vert->v =  rspFastDiv(float(sFogOffset + z2.upper), 255.f);
			vert->argb = colour;
			pvr_dr_commit(vert);

			vert = pvr_dr_target(state);
			vert->flags = PVR_CMD_VERTEX_EOL;
			vert->x = point1.x;
			vert->y = point1.y;
			vert->z = point1.z;
			vert->u = col;
			vert->v =  rspFastDiv(float(sFogOffset + z1.upper), 255.f);
			vert->argb = colour;
			pvr_dr_commit(vert);						
		}		
	}
	g_pvrRenderer->m_lNumVerts += sNumVerts;
	return true;
}

// THIS IS HACKED!  WILL NOT WORK WITH DISTORTED GUYS!
//
void RPipeLine::BoundingSphereToScreen(RP3d &ptCenter, RP3d &ptRadius,
									   RTransform &tObj)
{
	// THIS IS HARD WIRED TO WORK WITH OUR CURRENT STYLE OF
	// PROJECTION:
	RTransform tFull;
	tFull.Make1();
	tFull.Mul(m_tView.T, tObj.T); // hold off on screen -> get raw distance:
	tFull.PreMulBy(m_tScreen.T);

	// THIS IS IN UNSCALED OBJECT VIEW
	double dModelRadius = rspSqrtf(
		SQR(ptCenter.x - ptRadius.x) +
		SQR(ptCenter.y - ptRadius.y) +
		SQR(ptCenter.z - ptRadius.z)); // Randy Units

	// Convert from Model To Screen...
	double dScreenRadius = dModelRadius * m_tScreen.T[0];

	// Project the center onto the screen:
	// todo??
	tFull.UpdateMatr();
	RP3d ptCen /*,ptEnd*/;
	tFull.TransformInto(ptCenter, ptCen); // z is now distorted

	// store in pieline variables...(ALL OF THEM)
	m_sCenX = int16_t(ptCen.x);
	m_sCenY = int16_t(ptCen.y);
	m_sCenZ = int16_t(round(rspFastDiv(ptCen.z, 256.0))); // Scale Z's by 256 for lighting later

	int16_t sScreenRadius = int16_t(dScreenRadius + 1);

	m_sX = m_sCenX - sScreenRadius;
	m_sY = m_sCenY - sScreenRadius;
	m_sZ = m_sCenZ - sScreenRadius;

	m_sW = m_sH = (sScreenRadius * 2.);
	m_sD = (sScreenRadius * 2.);

	m_sUseBoundingRect = TRUE;
}
