/*-----------------------------------------------------------------------------
 * File: pathrnd.c
 * Written by: Fredrik Kling & Alexander Boczar, 1997-08-15
 * Description: Pathrendring device...
 *
 * Updates:
 * -- Date -- | ----- Name ----- |-- Did what....
 * 1997-08-21 | 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...
 *      pathRND   - 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/drv16.h"
#include "system/xmath.h"
#include "misc/col.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 pathDRV;

static int col_yellow,col_red, col_green, col_blue;
static int renderpath[512];
static float accurate[512];
static int num_object;


static void setviewport (BUFF *buff, CAMERA *camera)
{
	int i;
	RGB y = {63<<2,63<<2,0},r = {63<<2,0,0}, g = {0,63<<2,0}, b = {0,0,63<<2};
	pathDRV.camera = camera;
	pathDRV.buff = buff;

	col_yellow = findcolor (buff->drv->palette,y);
	col_red = findcolor (buff->drv->palette,r);
	col_green = findcolor (buff->drv->palette,g);
	col_blue = findcolor (buff->drv->palette,b);
	for (i=0;i<1024;renderpath[i++]=0);
}

/******************************************************************
 * 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;


	num_object = 0;
	for (i=0,o=v3d->obj;o!=NULL;o=o->next,i++)
	{
		object_render (o);
		num_object++;
	}
  //debug_printfxy( 0, 23, "Frame: %d, Objects: %d, Lights: %d, Polygons: %d, Plots: %d     \n", (int)v3d->framecounter, numobj, numlight, numpoly, numplot);
}
/******************************************************************
 * 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( pathDRV.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 (pathDRV.camera->flags & CAMERAFLAG_OBJTARGET)
	{
		VECTOR cupv;

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

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

		matrixfromvectors (&camvec, &cupv, &pathDRV.camera->rm);
	}
	else
	{
		buildrotationmatrix(
		 	(int)pathDRV.camera->anim->value[ANIM_ANG_X] & (SINESIZE - 1),
			(int)pathDRV.camera->anim->value[ANIM_ANG_Y] & (SINESIZE - 1),
			(int)pathDRV.camera->anim->value[ANIM_ANG_Z] & (SINESIZE - 1),
			&pathDRV.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] - pathDRV.camera->anim->value[ANIM_POS_X]);
  	lvd.y=(lo->anim->value[ANIM_POS_Y] - pathDRV.camera->anim->value[ANIM_POS_Y]);
	  lvd.z=(lo->anim->value[ANIM_POS_Z] - pathDRV.camera->anim->value[ANIM_POS_Z]);
		// Rotera
  	vecmul (&pathDRV.camera->rm, &lvd, &lo->calcpos);


	// Plot lightsources

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

		if (lo->calcpos.z>0)
		{
			XYZ p;
			float t = pathDRV.camera->focus/(lo->calcpos.z-pathDRV.camera->focus);
			p.x = lo->calcpos.x*t;
			p.y = lo->calcpos.y*t;
      p.x += pathDRV.buff->xorigo;
      p.y += pathDRV.buff->yorigo;
			p.z = lo->calcpos.z;
			p.l = 32;
      pathDRV.buff->drv->plot (p,col_yellow);
		}
		numlight++;
	}
}
/******************************************************************
 * 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] - pathDRV.camera->anim->value[ANIM_POS_X]);
  		ovd.y=(obj->anim->value[ANIM_POS_Y] - pathDRV.camera->anim->value[ANIM_POS_Y]);
  		ovd.z=(obj->anim->value[ANIM_POS_Z] - pathDRV.camera->anim->value[ANIM_POS_Z]);
  		vecmul (&pathDRV.camera->rm, &ovd, &obj->calcpos);
		}

	switch (obj->type)
	{
		case T_V3O :	//if (render_checkspherical (&pathDRV,obj))
									{
										// 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) matmul (&obj->parent->rm,&rm,&obj->rm);
										  else matmul (&pathDRV.camera->rm,&rm,&obj->rm);
										// Rendrera
								  	v3o_render (obj->v3o,&obj->calcpos,&obj->rm);
										if (renderpath[numobject]) object_drawpath (obj,&obj->calcpos,&obj->rm);
									}
									break;
		case T_NULL:
									break;
		case T_VIO :
								 break;
	}

}
// Putobject_angle is used to switch pathdrawing for an object
static void putobject_ang (OBJECT *obj, int num, int s, int c)
{
	// obj - unused
	// c - unused

	if (renderpath[num]) renderpath[num]=0;
		else renderpath[num]=1;
}
static void object_drawpath (OBJECT *obj,VECTOR *pos, MATRIX *rm)
{
	float maxframe,fc,deltax,s,xo,yo;
	int keynum,i,first;
	ANIM *anim;
	KEY *key;
	VECTOR v,w;
	XYZ p1,p2;

	s = pathDRV.scale;
  xo = pathDRV.buff->xorigo;
  yo = pathDRV.buff->yorigo;


	maxframe = 0;
	anim = obj->anim;
	for (i=0, key=obj->anim->first;i<obj->anim->keys;i++,key = key -> next)
		if (key->frame > maxframe) maxframe = key -> frame;

	deltax = maxframe / 30.0;
	first = 1;
	for (fc = 0,i=0;i<30;i++,fc+=deltax)
	{
		spline_calcframe( obj->anim, fc);
		v.x = obj->anim->value[ANIM_POS_X];
		v.y = obj->anim->value[ANIM_POS_Y];
		v.z = obj->anim->value[ANIM_POS_Z];

		vecmul (rm,&v,&w);
		vecmul (rm,&v,&w);

		w.x+=pos->x;
		w.y+=pos->y;
		w.z+=pos->z + pathDRV.camera->focus;

		t = s*(pathDRV.camera->focus/(w.z-pathDRV.camera->focus));
		w.x = w.x*t;
		w.y = w.y*t;
		w.z = w.z;

		w.x+=xo;
		w.y+=yo;

		if (!first)
		{
			p2 = p1;
			p1.x = w.x;
			p1.y = w.y;
			p1.z = w.z;
			p1.l = 32;
      pathDRV.buff->drv->line (p1,p2,col_red);
		} else
			{
				p1.x = w.x;
				p1.y = w.y;
				p1.z = w.z;
        pathDRV.buff->drv->plot (p1,col_red);
			}
	}
}

/******************************************************************
 * 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;
  float xo,yo;
	float s,t;
	XYZ p1,p2;
	int a,b;

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

	s = pathDRV.scale;
  xo = pathDRV.buff->xorigo;
  yo = pathDRV.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 + pathDRV.camera->focus;
		// Convert to inline asm ala watcom 11.0 <- hmm...  kanske inte iaf...
/*
#if defined (__X86__)
{
	float focus=pathDRV.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*(pathDRV.camera->focus/(obj->rotvertex[i].z-pathDRV.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;

	}

// Draw the fucking(?) stuff...
	for (i=0;i<obj->numsurface;i++)
	{
		if( obj->surface[i].flags & V3OSURFACEFLAG_POLY)
		{
			if( render_inspace(&pathDRV,&obj->projvertex[obj->surface[i].v1],currentscene->zmax,currentscene->zmin) &&
					render_inspace(&pathDRV,&obj->projvertex[obj->surface[i].v2],currentscene->zmax,currentscene->zmin) &&
					render_inspace(&pathDRV,&obj->projvertex[obj->surface[i].v3],currentscene->zmax,currentscene->zmin) &&
					!render_ishidden(obj,&obj->surface[i]))
			{
				wire_polygon (pathDRV.buff->drv, &obj->surface[i],obj->projvertex);
				numpoly++;
			}
		}
		else if( obj->surface[i].flags & V3OSURFACEFLAG_LINE)
		{
			if( render_inspace(&pathDRV, &obj->projvertex[obj->surface[i].v1],currentscene->zmax,currentscene->zmin) &&
					render_inspace(&pathDRV, &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;
        pathDRV.buff->drv->line (p1,p2,obj->material[obj->surface[i].material].ci);
			}
		}
		else if( obj->surface[i].flags & V3OSURFACEFLAG_POINT)
		{
			if( render_inspace(&pathDRV, &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;
        pathDRV.buff->drv->plot (p1,obj->material[obj->surface[i].material].ci);
        numplot++;
      }
		}
	}
	numobj++;
}

RNDDRV pathDRV =
{
	"Path rendring for .V3D files, V1.0",
	NULL,		// Buffer
	NULL,		// Camera
	1.0,
	setviewport,
	prepare,
	render,
	object_render,
	NULL,
	putobject_ang,		// Putobject ang
	NULL,
	NULL,
	NULL
};

