////////////////////////////////////////////////////////////
//                                                        //
//  PC MAGAZIN - PC Underground                           //
//  High Performance Rendering                            //
//  (w)(c)2002 Carsten Dachsbacher                        //
//                                                        //
////////////////////////////////////////////////////////////
#include	<windows.h>			
#include	<stdio.h>			
#include	<stdlib.h>			
#include	<math.h>
#include	<gl\gl.h>			
#include	<gl\glu.h>			
#include	"texture.h"
#include	"oglextension.h"
#include	"vertex3dop.h"
#include	"hprender.h"

//
// Selektion der Rendermodi !
//

// allgemein: renderMode == RM_SHAREDVERTEX oder RM_TRIANGLESTRIP
U32 renderMode = RM_TRIANGLESTRIP;

// für NVIDIA: NV_VIDEOMEMORY, NV_AGPMEMORY oder NV_SYSTEMMEMORY
U32 nvMemoryType = NV_VIDEOMEMORY;

// für ATI: ATI_NO_VAO (keine Extension nutzen), ATI_VAO (Array Objects) oder 
//          ATI_VAO_PLUS_AE (Array Objects und Array Elements)
U32	atiMemoryType = ATI_VAO_PLUS_AE;

// Variablen für NVidia Zeug:
U32			NVvarMemSize = 0;
void		*NVvarMemoryPtr = NULL;
VERTEX3D	*varVertexList, *varNormalList;

// Variablen für ATI Zeug:
GLuint		atiVertexObject = 0;
GLuint		atiArrayElement = 0;
U32			atiElementArrayOK = 0;

//
// ATI
//
bool allocateATIMemory( U32 size, void *data )
{
	if ( supportATI_vao )
	{
		atiVertexObject = glNewObjectBufferATI( size, data, GL_STATIC_ATI );
		if ( atiVertexObject != 0 )
			return true;
	}

	return false;
}

//
// NVIDIA: ganzen VAR Memory Block allokieren
//
void *allocateMemory( U32 size )
{
	size += 64;

	NVvarMemoryPtr = NULL;

	if ( supportVAR )
	{
		if ( nvMemoryType == NV_VIDEOMEMORY )
		{
			NVvarMemoryPtr = (unsigned char *)wglAllocateMemoryNV( size, 0.2f, 0.2f, 1.0f );
			if ( NVvarMemoryPtr == NULL )
				nvMemoryType = NV_AGPMEMORY;
		}
		if ( nvMemoryType == NV_AGPMEMORY )
		{
			NVvarMemoryPtr = (unsigned char *)wglAllocateMemoryNV( size, 0.2f, 0.2f, 0.5f );
		}
	}
	if ( NVvarMemoryPtr == NULL )
	{
		nvMemoryType = NV_SYSTEMMEMORY;

		// conventional memory
		NVvarMemoryPtr = new unsigned char[ size ];
		NVvarMemSize = size - 64;
	} else
	{
		glVertexArrayRangeNV( size, NVvarMemoryPtr );
		NVvarMemSize = size - 64;
	}

	NVvarMemoryPtr = (unsigned char*)( ( (int)NVvarMemoryPtr + 64 ) & ~63 );

	return NVvarMemoryPtr;
}


//
// einfache Shared Vertex Datenstrukturen und simples Fileformat
//
typedef struct
{
	U32			a, b, c;		// indices to vertices
	VERTEX3D	normal;			// normal of triangle
	FLOAT		area;			// area of triangle
}TRIFACE;

static unsigned int	nVertices    = 0, 
					nFaces       = 0;
static VERTEX3D		*pVertexList = NULL;
static TRIFACE		*pFaceList   = NULL;
static VERTEX3D		*pNormalList = NULL;

static void readTriangleMesh( char *filename )
{
	if ( filename == NULL )
	{
		nVertices = nFaces = 0;
	}

	FILE *f = fopen( filename, "rb" );
	fread( &nVertices, 4, 1, f );
	fread( &nFaces, 4, 1, f );

	pVertexList	= new VERTEX3D[ nVertices ];
	pNormalList	= new VERTEX3D[ nVertices ];
	pFaceList	= new TRIFACE[ nFaces ];

	for ( unsigned int i = 0; i < nVertices; i++ )
	{
		fread( &pVertexList[ i ].x, 1, 4, f );
		fread( &pVertexList[ i ].y, 1, 4, f );
		fread( &pVertexList[ i ].z, 1, 4, f );
	}

	for ( i = 0; i < nFaces; i++ )
	{
		fread( &pFaceList[ i ].a, 1, 4, f );
		fread( &pFaceList[ i ].b, 1, 4, f );
		fread( &pFaceList[ i ].c, 1, 4, f );
	}
	
	fclose( f );

	// Normalen berechnen
	for ( i = 0; i < nFaces; i++ )
	{
		VERTEX3D	*a1, *a2, *a3;
		VERTEX3D	a, b;

		a1 = &pVertexList[ pFaceList[ i ].a ];
		a2 = &pVertexList[ pFaceList[ i ].b ];
		a3 = &pVertexList[ pFaceList[ i ].c ];

		a = *a2 - *a1;
		b = *a3 - *a1;

		pFaceList[ i ].normal = a ^ b;

		pFaceList[ i ].area = (float)sqrt( pFaceList[ i ].normal * pFaceList[ i ].normal );

	}

	// Vertexnormalen
	for ( i = 0; i < nVertices; i++ )
		pNormalList[ i ].x =
		pNormalList[ i ].y =
		pNormalList[ i ].z = 0.0f;

	for ( i = 0; i < nFaces; i++ )
	{
		pNormalList[ pFaceList[ i ].a ] += pFaceList[ i ].normal;
		pNormalList[ pFaceList[ i ].b ] += pFaceList[ i ].normal;
		pNormalList[ pFaceList[ i ].c ] += pFaceList[ i ].normal;

		pFaceList[ i ].normal *= 1.0f / pFaceList[ i ].area;
	}

	for ( i = 0; i < nVertices; i++ )
	{
		~pNormalList[ i ];
	}

}

//
// Isotrope Skalierung der Koordinaten
//
static void	scaleIsotropic( VERTEX3D *pVertexList, unsigned int nVertices )
{
	// calculate bounding box of mesh
	FLOAT	xminv = 1e37f, xmaxv = -1e37f;
	FLOAT	yminv = 1e37f, ymaxv = -1e37f;
	FLOAT	zminv = 1e37f, zmaxv = -1e37f;

	for ( unsigned int i = 0; i < nVertices; i++ )
	{

		xminv = min( xminv, pVertexList[ i ].x );
		yminv = min( yminv, pVertexList[ i ].y );
		zminv = min( zminv, pVertexList[ i ].z );
		xmaxv = max( xmaxv, pVertexList[ i ].x );
		ymaxv = max( ymaxv, pVertexList[ i ].y );
		zmaxv = max( zmaxv, pVertexList[ i ].z );
	}

	FLOAT	xmove = -(FLOAT)0.5 * ( xminv + xmaxv );
	FLOAT	ymove = -(FLOAT)0.5 * ( yminv + ymaxv );
	FLOAT	zmove = -(FLOAT)0.5 * ( zminv + zmaxv );

	FLOAT	maxSize = (FLOAT)max( (FLOAT)fabs( xminv - xmaxv ), max( (FLOAT)fabs( yminv - ymaxv ), (FLOAT)fabs( zminv - zmaxv ) ) );

	FLOAT	scale = (FLOAT)0.5 / (FLOAT)maxSize;

	// translate and scale vertices
	for ( i = 0; i < nVertices; i++ )
	{
		pVertexList[ i ].x += xmove;
		pVertexList[ i ].y += ymove;
		pVertexList[ i ].z += zmove;
		pVertexList[ i ].x *= scale;
		pVertexList[ i ].y *= scale;
		pVertexList[ i ].z *= scale;
	}
}

unsigned int *pIndexList;
unsigned int nStripIndices;
unsigned int *pStripIndices;


void	init3DModel( char *triangleMesh )
{
	//
	// Dreiecksnetz laden
	//
	char buf[ 512 ];
	sprintf( buf, "%s.cnd", triangleMesh );
	readTriangleMesh( buf );

	scaleIsotropic( pVertexList, nVertices );

	//
	// Triangle Strips laden
	//
	sprintf( buf, "%s.strip", triangleMesh );
	FILE *f = fopen( buf, "rb" );
	if ( f != NULL )
	{
		fread( &nStripIndices, 4, 1, f );
		pStripIndices = new unsigned int[ nStripIndices ];
		for ( U32 n = 0; n < nStripIndices; n++ )
		{
			unsigned short t;
			fread( &t, 2, 1, f );
			pStripIndices[ n ] = t;
		}
		fclose( f );
	} else
		if ( renderMode == RM_TRIANGLESTRIP )
			renderMode = RM_SHAREDVERTEX;

	//
	// Indexliste bauen
	//
	pIndexList = new unsigned int [ nFaces * 3 ];

	int c = 0;
	for ( U32 i = 0; i < nFaces; i++ )
	{
		pIndexList[ c ++ ] = pFaceList[ i ].a;
		pIndexList[ c ++ ] = pFaceList[ i ].b;
		pIndexList[ c ++ ] = pFaceList[ i ].c;
	}

	getOpenGLExtensions();

	//
	// NVIDIA
	//
	if ( supportVAR )
	{
		U32 memory = nVertices * 3 * 2 * sizeof( FLOAT );

		void *NVvarMemoryPtr = allocateMemory( memory );

		varVertexList = (VERTEX3D*)NVvarMemoryPtr;
		varNormalList = (VERTEX3D*)NVvarMemoryPtr;
		
		varNormalList += nVertices;

		memcpy( varVertexList, pVertexList, sizeof( float ) * 3 * nVertices );
		memcpy( varNormalList, pNormalList, sizeof( float ) * 3 * nVertices );
	} else
	//
	// ATI
	//
	if ( atiMemoryType >= ATI_VAO && supportATI_vao )
	{
		U32 memory = nVertices * 3 * 2 * sizeof( FLOAT );
		VERTEX3D *interleaved = new VERTEX3D[ nVertices * 2 ];

		VERTEX3D *dst = (VERTEX3D*)interleaved;
		for ( i = 0; i < nVertices; i++ )
		{
			dst[ i*2+0 ] = pVertexList[ i ];
			dst[ i*2+1 ] = pNormalList[ i ];
		}

		if ( !allocateATIMemory( memory, interleaved ) )
		{
			MessageBox( 0, "failed to initialize ATI vertex array object !", "Problem", MB_OK );
			exit( 1 );
		}

		if ( atiMemoryType == ATI_VAO_PLUS_AE && supportATI_element_array )
		{
			if ( renderMode == RM_TRIANGLESTRIP )
			{
				memory = sizeof(U32) * nStripIndices;

				// Element Object erzeugen
				atiArrayElement = glNewObjectBufferATI( memory, pStripIndices, GL_STATIC_ATI );
			} else
			if ( renderMode == RM_SHAREDVERTEX )
			{
				memory = sizeof(U32) * 3 * nFaces;

				// Element Object erzeugen
				atiArrayElement = glNewObjectBufferATI( memory, pIndexList, GL_STATIC_ATI );
			}
			
			// sollte es nicht geklappt haben, dann eben ohne Array Element rendern
			if ( atiArrayElement != 0 )
				atiElementArrayOK = 1;
		}
	} else
	// weder VAO noch VAR Extension !!!
	{
		varVertexList = pVertexList;
		varNormalList = pNormalList;

		if ( supportCompiledVertexArray )
			(*pfLockArrays)( 0, nVertices );
	}




}

void render3DModel()
{
	//
	// ATI
	//
	if ( atiMemoryType >= ATI_VAO && supportATI_vao )
	{
		if ( atiMemoryType >= ATI_VAO_PLUS_AE && supportATI_element_array && atiElementArrayOK )
		{
			// mit Vertex Array Objects und Array Elements

			glEnableClientState(GL_VERTEX_ARRAY);
			glEnableClientState(GL_NORMAL_ARRAY);
			glEnableClientState(GL_ELEMENT_ARRAY_ATI);
			
			// Array Pointer mit Vertex Array Object angeben
			glArrayObjectATI(GL_NORMAL_ARRAY, 3, GL_FLOAT, sizeof(VERTEX3D)*2, atiVertexObject, sizeof(float) * 3);
			glArrayObjectATI(GL_VERTEX_ARRAY, 3, GL_FLOAT, sizeof(VERTEX3D)*2, atiVertexObject, sizeof(float) * 0);
			glArrayObjectATI(GL_ELEMENT_ARRAY_ATI, 1, GL_UNSIGNED_INT, 0, atiArrayElement, 0);
			
			// und Zeichnen mit glDrawElementArrayATI 
			if ( renderMode == RM_TRIANGLESTRIP )
				glDrawElementArrayATI( GL_TRIANGLE_STRIP, nStripIndices ); else 
				glDrawElementArrayATI(GL_TRIANGLES, nFaces * 3);
			
			// und ausschalten
			glDisableClientState(GL_VERTEX_ARRAY);
			glDisableClientState(GL_NORMAL_ARRAY);
			glDisableClientState(GL_ELEMENT_ARRAY_ATI);
		} else
		{
			// nur mit Vertex Array Objects 

			glEnableClientState(GL_VERTEX_ARRAY);
			glEnableClientState(GL_NORMAL_ARRAY);
			
			glArrayObjectATI(GL_NORMAL_ARRAY, 3, GL_FLOAT, sizeof(VERTEX3D)*2, atiVertexObject, sizeof(float) * 3);
			glArrayObjectATI(GL_VERTEX_ARRAY, 3, GL_FLOAT, sizeof(VERTEX3D)*2, atiVertexObject, sizeof(float) * 0);
			
			if ( renderMode == RM_TRIANGLESTRIP )
				glDrawElements( GL_TRIANGLE_STRIP, nStripIndices, GL_UNSIGNED_INT, pStripIndices ); else 
				glDrawElements(GL_TRIANGLES, nFaces * 3, GL_UNSIGNED_INT, pIndexList );
			
			glDisableClientState(GL_VERTEX_ARRAY);
			glDisableClientState(GL_NORMAL_ARRAY);
		
		}
	} else
	//
	// NVIDIA + Standard OpenGL
	//
	{
		if ( supportVAR )
			glEnableClientState( GL_VERTEX_ARRAY_RANGE_WITHOUT_FLUSH_NV );
		
		glVertexPointer( 3, GL_FLOAT, 0, varVertexList );
		glNormalPointer( GL_FLOAT, 0, varNormalList );
		
		glEnable( GL_VERTEX_ARRAY );
		glEnable( GL_NORMAL_ARRAY );
		
		if ( renderMode == RM_TRIANGLESTRIP )
			glDrawElements( GL_TRIANGLE_STRIP, nStripIndices, GL_UNSIGNED_INT, pStripIndices ); else
			glDrawElements( GL_TRIANGLES, nFaces*3, GL_UNSIGNED_INT, pIndexList );
			
		glDisableClientState( GL_VERTEX_ARRAY );
		glDisableClientState( GL_NORMAL_ARRAY );
			
		if ( supportVAR )
			glDisableClientState( GL_VERTEX_ARRAY_RANGE_WITHOUT_FLUSH_NV );

	}
	glFlush();
}

void	delete3DModel()
{
	if ( supportCompiledVertexArray )
		(*pfUnlockArrays)();

	if ( NVvarMemoryPtr != NULL && supportVAR && ( nvMemoryType == NV_AGPMEMORY || nvMemoryType == NV_VIDEOMEMORY ) )
	{
		glDisableClientState( GL_VERTEX_ARRAY_RANGE_NV );

		wglFreeMemoryNV( NVvarMemoryPtr );
	}

	if ( atiVertexObject )
		glFreeObjectBufferATI( atiVertexObject );

	if ( atiArrayElement )
		glFreeObjectBufferATI( atiArrayElement );
}


