/*-----------------------------------------------------------------------------
 * File: scprnd.c
 * Written by: Fredrik Kling & Alexander Boczar, 1997-08-15
 * Description: Rendring driver for scenes...
 *
 * Updates:
 * -- Date -- | ----- Name ----- |-- Did what....
 * 1997-08-15 | Fredrik Kling    | Implementation
 *
 * Kommentar:
 *
 * Todo:
 *	 Z-clipper
 *
 *   Driver uppbyggnad, f”rslag till functioner:
 *
 *    setviewport (Camera, Buffer)
 *  	prepare (V3D *)
 *    render (V3D *)
 *    putobject (Object *)
 *    putobject_pos (OBJECT *,VECTOR *)
 *    putobject_ang (OBJECT *, int angle[3]);
 *    putobject_pos_ang (OBJECT *, VECTOR *, int angle[3]);
 *
 *  F”rslag till variabler:
 *   CAMERA *activecamera;
 *   BUFF *buff;
 *   V3D *scene;
 *
 *   En viss driver beh”ver ju inte utnyttja all info..
 *   Drivers som jag vill ha:
 *			scpRND	- ScenPlay_rendering
 *      rayRND  - Raytrace output..  anv„nd roberts tracer...
 *			objRND  - Ren object rendrare... som "old fashion" 3d-motorer...
 *      wfRND   - Wireframes rendrare...
 *
 *----------------------------------------------------------------------------*/
#include "formats/v3d.h"
#include "formats/v3o.h"
#include "objects/object.h"
#include "objects/camera.h"
#include "objects/light.h"
#include "vmath/vector.h"
#include "vmath/matrix.h"
#include "vmath/vmath.h"
#include "polydraw.h"
#include "drivers/drv8.h"
#include "system/xmath.h"
#include "misc/spline.h"
//#include "debug/mono.h"

#include "render/rnd_util.h"
#include "render/rnddrv.h"

//#define DBS 1

static V3D *currentscene;

static VECTOR camvec;
//static BUFF *buff;

static int numobj,numlight,numpoly,numplot;

// Varf”r extern?
extern RNDDRV scpDRV;

static void setviewport (BUFF *buff, CAMERA *camera)
{
	scpDRV.camera = camera;
	scpDRV.buff = buff;
}

/******************************************************************
 * Function  : v3d_prepare                                        *
 * Input     : V3D *                                              *
 * Return    : nothing                                            *
 * Effect    : Prepares all objects in scene..  lights, v3o's     *
 ******************************************************************/
static prepare (V3D *v3d)
{
	OBJECT *lo;
	VECTOR lvd;

  numobj = numlight = numpoly = numplot = 0;

	currentscene = v3d;

	// Animate Camera
	spline_calcframe( scpDRV.camera->anim, v3d->framecounter);

	// Animate Lights
	for ( lo=v3d->lightlist; lo!=NULL; lo=lo->next)
		spline_calcframe( lo->anim, v3d->framecounter);

	// Animate Objects
	for ( lo=v3d->obj; lo!=NULL; lo=lo->next)
		spline_calcframe( lo->anim, v3d->framecounter);

	// Buildcamera vector...

	if (scpDRV.camera->flags & CAMERAFLAG_OBJTARGET)
	{
		VECTOR cupv;

		render_getvector(
			&camvec,
			scpDRV.camera->anim->value[ANIM_POS_X],
			scpDRV.camera->anim->value[ANIM_POS_Y],
			scpDRV.camera->anim->value[ANIM_POS_Z],
			scpDRV.camera->obj_target->anim->value[ANIM_POS_X],
			scpDRV.camera->obj_target->anim->value[ANIM_POS_Y],
			scpDRV.camera->obj_target->anim->value[ANIM_POS_Z]);

		// Camera Banking...
		cupv.x = sintable[((int)scpDRV.camera->anim->value[ANIM_ANG_Z] & (SINESIZE - 1))];
		cupv.y = -sintable[((int)scpDRV.camera->anim->value[ANIM_ANG_Z] & (SINESIZE - 1)) + (SINESIZE / 4)];
		cupv.z = 0;

		matrixfromvectors (&camvec, &cupv, &scpDRV.camera->rm);
	}
	else
	if (scpDRV.camera->flags & CAMERAFLAG_POSTARGET)
	{
		VECTOR cupv;
		render_getvector(
			&camvec,
			scpDRV.camera->anim->value[ANIM_POS_X],
			scpDRV.camera->anim->value[ANIM_POS_Y],
			scpDRV.camera->anim->value[ANIM_POS_Z],
			scpDRV.camera->target.x,
			scpDRV.camera->target.y,
			scpDRV.camera->target.z);
		// Camera Banking...
		cupv.x = sintable[((int)scpDRV.camera->anim->value[ANIM_ANG_Z] & (SINESIZE - 1))];
		cupv.y = -sintable[((int)scpDRV.camera->anim->value[ANIM_ANG_Z] & (SINESIZE - 1)) + (SINESIZE / 4)];
		cupv.z = 0;
		matrixfromvectors (&camvec, &cupv, &scpDRV.camera->rm);
	}
	else
	{
		buildrotationmatrix(
		 	(int)scpDRV.camera->anim->value[ANIM_ANG_X] & (SINESIZE - 1),
			(int)scpDRV.camera->anim->value[ANIM_ANG_Y] & (SINESIZE - 1),
			(int)scpDRV.camera->anim->value[ANIM_ANG_Z] & (SINESIZE - 1),
			&scpDRV.camera->rm);
	}

	// Prepare all lights in lightlist...

	for (lo=v3d->lightlist;lo!=NULL;lo=lo->next)
	{
		// Fixa dem i relation till världen och restan av livet...
  	lvd.x=(lo->anim->value[ANIM_POS_X] - scpDRV.camera->anim->value[ANIM_POS_X]);
  	lvd.y=(lo->anim->value[ANIM_POS_Y] - scpDRV.camera->anim->value[ANIM_POS_Y]);
	  lvd.z=(lo->anim->value[ANIM_POS_Z] - scpDRV.camera->anim->value[ANIM_POS_Z]);
		// Rotera
  	vecmul (&scpDRV.camera->rm, &lvd, &lo->calcpos);


	// Plot lightsources

		lo->calcpos.z += scpDRV.camera->focus;

		if (lo->calcpos.z>0)
		{
			XYZ p;
			float t = scpDRV.camera->focus/(lo->calcpos.z-scpDRV.camera->focus);
			p.x = lo->calcpos.x*t;
			p.y = lo->calcpos.y*t;
      p.x += scpDRV.buff->xorigo;
      p.y += scpDRV.buff->yorigo;
			p.z = lo->calcpos.z;
			p.l = 32;
      scpDRV.buff->drv->plot (p,255);
		}
		numlight++;
	}
}
static int obj_draw;
/******************************************************************
 * Function  : v3o_render                                         *
 * Input     : OBJECT *, VECTOR *, MATRIX *, float                *
 * Return    : nothing                                            *
 * Effect    : Rotates an object then draws in the viewport       *
 * Comments  : This one recurses if subojects are found...        *
 *						 Uses inline asm, not checked with vtune...         *
 ******************************************************************/
//void v3o_render (V3O *obj, VECTOR *pos, MATRIX *rm, OBJECT *lightlist)
static void v3o_render (V3O *obj, VECTOR *pos, MATRIX *rm)
{
	VECTOR realorigo = *pos;
	int i,flags;
  float xo,yo,s;
	float l1,l2,l3,t;
	XYZ p1,p2;
	int a,b;

	realorigo.z += scpDRV.camera->focus;

	s = scpDRV.scale;

  xo = scpDRV.buff->xorigo;
  yo = scpDRV.buff->yorigo;

	// Rotate and project object as wanted... no in one single loop..
	// Using inline assembler for speedups...  not checked for stalls etc..

	for (i=0;i<obj->numvertex;i++)
	{
		// Functions are inline...
		vecmul (rm,&obj->orgvertex[i],&obj->rotvertex[i]);
		vecmul (rm,&obj->orgnormal[i],&obj->rotnormal[i]);

		obj->rotvertex[i].x+=pos->x;
		obj->rotvertex[i].y+=pos->y;
		obj->rotvertex[i].z+=pos->z + scpDRV.camera->focus;
		// Convert to inline asm ala watcom 11.0 <- hmm...  kanske inte iaf...
/*
#if defined (__X86__)
{
	float focus=scpDRV.camera->focus,xp=pos->x,yp=pos->y,zp=pos->z;
extern float f_project (VECTOR *,VECTOR *);
#pragma aux f_project "*" = \
	"fld 		dword ptr [esi+8]",\
	"fld		focus",\
	"fxch 	st(1)",\
	"fdivp 	st(1),st",\
\
	"fld		dword ptr [esi]",\
	"fmul		st(0),st(1)",\
	"fld		dword ptr [esi+4]",\
	"fmul		st(0),st(2)",\
	"fstp		dword ptr [edi+4]",\
	"fstp		dword ptr [edi]",\
	parm [esi] [edi] value[8087];
	// doit it..

	f_project (&obj->rotvertex[i],&obj->projvertex[i]);
}
#else
{
*/
		t = s*(scpDRV.camera->focus/(obj->rotvertex[i].z-scpDRV.camera->focus));
		obj->projvertex[i].x = obj->rotvertex[i].x*t;
		obj->projvertex[i].y = obj->rotvertex[i].y*t;
		obj->projvertex[i].z = obj->rotvertex[i].z;
/*
}
#endif
*/
		obj->projvertex[i].x+=xo;
		obj->projvertex[i].y+=yo;

	}

	if (!obj_draw) return;
// Draw the fucking(?) stuff...
	for (i=0;i<obj->numsurface;i++)
	{
		if( obj->surface[i].flags & V3OSURFACEFLAG_POLY)
		{
			if( render_inspace(&scpDRV,&obj->projvertex[obj->surface[i].v1],currentscene->zmax,currentscene->zmin) &&
					render_inspace(&scpDRV,&obj->projvertex[obj->surface[i].v2],currentscene->zmax,currentscene->zmin) &&
					render_inspace(&scpDRV,&obj->projvertex[obj->surface[i].v3],currentscene->zmax,currentscene->zmin) &&
					!render_ishidden(obj,&obj->surface[i]))
			{
				flags = obj->material[obj->surface[i].material].flags;
				switch (flags & (V3OMATERIALFLAG_TEXTURE | V3OMATERIALFLAG_FLAT | V3OMATERIALFLAG_GOURADE))
				{
					case V3OMATERIALFLAG_TEXTURE :
              texture_polygon (scpDRV.buff->drv, &obj->surface[i],obj->projvertex,&obj->texture[obj->material[obj->surface[i].material].texture]);
							break;
					case	(V3OMATERIALFLAG_TEXTURE+V3OMATERIALFLAG_FLAT) :
							render_calclight (&scpDRV,obj,&obj->surface[i],currentscene->lightlist,&l1,&l2,&l3,&realorigo);
              texture_flat_polygon (scpDRV.buff->drv, &obj->surface[i],obj->projvertex,48,&obj->texture[obj->material[obj->surface[i].material].texture]);
							break;
					case (V3OMATERIALFLAG_TEXTURE+V3OMATERIALFLAG_GOURADE) :
							render_calclight (&scpDRV,obj,&obj->surface[i],currentscene->lightlist,&l1,&l2,&l3,&realorigo);
              texture_gourade_polygon (scpDRV.buff->drv, &obj->surface[i],obj->projvertex,48,48,48,&obj->texture[obj->material[obj->surface[i].material].texture]);
							break;
					case V3OMATERIALFLAG_FLAT :
		        	render_calclight (&scpDRV,obj,&obj->surface[i],currentscene->lightlist,&l1,&l2,&l3,&realorigo);
              flat_polygon (scpDRV.buff->drv, &obj->surface[i],obj->projvertex,obj->material[obj->surface[i].material].ci,48);
							break;
					case V3OMATERIALFLAG_GOURADE :
					  	render_calclight (&scpDRV,obj,&obj->surface[i],currentscene->lightlist,&l1,&l2,&l3,&realorigo);
              gourade_polygon (scpDRV.buff->drv, &obj->surface[i],obj->projvertex,obj->material[obj->surface[i].material].ci,48,48,48);
							break;
				}
				numpoly++;
			}
		}
		else if( obj->surface[i].flags & V3OSURFACEFLAG_LINE)
		{
			if( render_inspace(&scpDRV, &obj->projvertex[obj->surface[i].v1],currentscene->zmax,currentscene->zmin) &&
					render_inspace(&scpDRV, &obj->projvertex[obj->surface[i].v2],currentscene->zmax,currentscene->zmin))
			{
				a=obj->surface[i].v1;
				b=obj->surface[i].v2;
				p1.x=obj->projvertex[a].x;
				p1.y=obj->projvertex[a].y;
				p1.z=obj->projvertex[a].z;
				p1.l=LIGHTLEVELS - 1;
				p2.x=obj->projvertex[b].x;
				p2.y=obj->projvertex[b].y;
				p2.z=obj->projvertex[b].z;
				p2.l=LIGHTLEVELS - 1;
        scpDRV.buff->drv->line (p1,p2,obj->material[obj->surface[i].material].ci);
			}
		}
		else if( obj->surface[i].flags & V3OSURFACEFLAG_POINT)
		{
			if( render_inspace(&scpDRV, &obj->projvertex[obj->surface[i].v1],currentscene->zmax,currentscene->zmin))
			{
			  p1.x=obj->projvertex[obj->surface[i].v1].x;
			  p1.y=obj->projvertex[obj->surface[i].v1].y;
			  p1.z=obj->projvertex[obj->surface[i].v1].z;
				p1.l=LIGHTLEVELS - 1;
//        scpDRV.buff->drv->plot (p1,obj->material[obj->surface[i].material].ci);
        numplot++;
      }
		}
	}
	numobj++;
}
/******************************************************************
 * Function  : object_draw                                        *
 * Input     : OBJECT *                                           *
 * Return    : nothing                                            *
 * Effect    : Draws an object in the scene..                     *
 ******************************************************************/
static void object_render (OBJECT *obj)
{
	MATRIX rm;
	VECTOR ovd;

	//  Check if parent object is available...
	if (obj->parent!=NULL)
	{
		ovd.x = obj->anim->value[ANIM_POS_X];
		ovd.y = obj->anim->value[ANIM_POS_Y];
		ovd.z = obj->anim->value[ANIM_POS_Z];
		vecmul (&obj->parent->rm, &ovd, &obj->calcpos);
		obj->calcpos.x += obj->parent->calcpos.x;
		obj->calcpos.y += obj->parent->calcpos.y;
		obj->calcpos.z += obj->parent->calcpos.z;
	} else
		{
			// Otherwise we have a clear root object...
			ovd.x=(obj->anim->value[ANIM_POS_X] - scpDRV.camera->anim->value[ANIM_POS_X]);
  		ovd.y=(obj->anim->value[ANIM_POS_Y] - scpDRV.camera->anim->value[ANIM_POS_Y]);
  		ovd.z=(obj->anim->value[ANIM_POS_Z] - scpDRV.camera->anim->value[ANIM_POS_Z]);
  		vecmul (&scpDRV.camera->rm, &ovd, &obj->calcpos);
		}

	switch (obj->type)
	{
		case T_V3O :	if (render_checkspherical (&scpDRV,obj))
									{
										// Just a fix to prevent object drawing on some objects...
										if (obj->flags & OBJFLAG_DRAW) obj_draw = 1;
										  else obj_draw = 0;
										// Fixa till vinklarna...
										obj->anim->value[ANIM_ANG_X]=(int)(obj->anim->value[ANIM_ANG_X] + obj->angleadd[0]) & (SINESIZE - 1);
										obj->anim->value[ANIM_ANG_Y]=(int)(obj->anim->value[ANIM_ANG_Y] + obj->angleadd[1]) & (SINESIZE - 1);
										obj->anim->value[ANIM_ANG_Z]=(int)(obj->anim->value[ANIM_ANG_Z] + obj->angleadd[2]) & (SINESIZE - 1);
	  						  	buildrotationmatrix (obj->anim->value[ANIM_ANG_X],obj->anim->value[ANIM_ANG_Y],obj->anim->value[ANIM_ANG_Z],&rm);
										// Ta med kamera rotation.... eller parent object...
										if (obj->parent!=NULL)
										{
											OBJECT *tmp;
											for (tmp = object->parent; tmp!=NULL; tmp=tmp->parent)
											  matmul (&tmp->parent->rm,&rm,&obj->rm);
										}
										  else matmul (&scpDRV.camera->rm,&rm,&obj->rm);
										// Rendrera
								  	v3o_render (obj->v3o,&obj->calcpos,&obj->rm);
									}
									break;
		case T_NULL:
									break;
		case T_VIO :
								 break;
	}

}
/******************************************************************
 * Function  : v3d_render                                         *
 * Input     : V3D *                                              *
 * Return    : nothing                                            *
 * Effect    : Renders the turbo ultra kewl scene with joy....    *
 ******************************************************************/
static void render (V3D *v3d)
{
	OBJECT *o;
	int i;

	for (i=0,o=v3d->obj;o!=NULL;o=o->next,i++)
	{
		object_render (o);
	}
  //debug_printfxy( 0, 23, "Frame: %d, Objects: %d, Lights: %d, Polygons: %d, Plots: %d     \n", (int)v3d->framecounter, numobj, numlight, numpoly, numplot);
}


/******************************************************************
 * Function  : smp_obj_render                                     *
 * Input     : OBJECT *, VECTOR *, int, int, int                  *
 * Return    : nothing                                            *
 * Effect    : Rotates an object then draws in the viewport       *
 * Comments  : Simplified object rendering routine...             *
 *						                                                    *
 ******************************************************************/
static void smp_obj_render (OBJECT *obj, VECTOR *pos, int xv, int yv, int zv)
{
		buildrotationmatrix((int)xv & (SINESIZE - 1),
		 										(int)yv & (SINESIZE - 1),
												(int)zv & (SINESIZE - 1),
												&obj->rm);

		v3o_render (obj->v3o,pos,&obj->rm);
}

RNDDRV scpDRV =
{
	"Sceneplayer for .V3D files, V1.0",
	NULL,		// Buffer
	NULL,		// Camera
	1.0,		// Scale value..  unused by most drivers:  1.0 refers to 320x200 or Resoloution 1 in a lws-file..
	setviewport,
	prepare,
	render,
	object_render,
	NULL,
	NULL,
	NULL,
	smp_obj_render,
	NULL
};

