#ifdef GL_GRAPHICS

#include <stdio.h>
#include <math.h>
#include <gl/gl.h>

#include "../h/cdefs.h"
#include "grf4.h"
#include "grf4Internal.h"
#include "grf4GenObject.h"

extern TRSF* glTransPtr;
extern int glPerspectiveMode;
extern float glNearPlane;
extern void *malloc();

static TRSF Identity = { 1.0, 0.0, 0.0, 
			 0.0, 1.0, 0.0,
			 0.0, 0.0, 1.0,
			 0.0, 0.0, 0.0 };

static float _v3f[3];
static float _n3f[3];
#define V3F(x,y,z)	(_v3f[0]=(x), _v3f[1]=(y), _v3f[2]=(z), v3f(_v3f))
#define N3F(x,y,z)	(_n3f[0]=(x), _n3f[1]=(y), _n3f[2]=(z), n3f(_n3f))

static float *crossV3f ();
static float dotV3f ();
static float *normalizeV3f ();
static float *multTrsfV3f();
static float *multRotV3f();
static void buildNormals();

static float *crossV3f (vr, v1, v2)
float *vr, *v1, *v2;
{
	vr[0] = v1[1]*v2[2] - v1[2]*v2[1];
	vr[1] = v1[2]*v2[0] - v1[0]*v2[2];
	vr[2] = v1[0]*v2[1] - v1[1]*v2[0];
	return(vr);
}

static float dotV3f (v1, v2)
float *v1, *v2;
{
	return (v1[0]*v2[0] + v1[1]*v2[1] + v1[2]*v2[2]);
}

static float *normalizeV3f (vr, v1)
float *vr, *v1;
{
	float mag;

	mag = sqrt(v1[0]*v1[0] + v1[1]*v1[1] + v1[2]*v1[2]);
	vr[0] = v1[0]/mag;
	vr[1] = v1[1]/mag;
	vr[2] = v1[2]/mag;
	return(vr);
}

static float *multTrsfV3f(vr, t1, v1)
float *vr, *v1;
register TRSF *t1;
{
	register float x, y, z;

	x = v1[0];
	y = v1[1];
	z = v1[2];
	
	vr[0] = t1->n.x*x + t1->o.x*y + t1->a.x*z + t1->p.x;
	vr[1] = t1->n.y*x + t1->o.y*y + t1->a.y*z + t1->p.y;
	vr[2] = t1->n.z*x + t1->o.z*y + t1->a.z*z + t1->p.z;
}
	
static float *multRotV3f(vr, t1, v1)
float *vr, *v1;
register TRSF *t1;
{
	register float x, y, z;

	x = v1[0];
	y = v1[1];
	z = v1[2];
	
	vr[0] = t1->n.x*x + t1->o.x*y + t1->a.x*z;
	vr[1] = t1->n.y*x + t1->o.y*y + t1->a.y*z;
	vr[2] = t1->n.z*x + t1->o.z*y + t1->a.z*z;
}

printV3f (fmt, v1)
char *fmt;
float *v1;
{
	char buf[128];

	sprintf (buf, "%s %s %s\n", fmt, fmt, fmt);
	printf (buf, v1[0], v1[1], v1[2]);
}

#define NEXT_IDX(k,num)	(((k)==((num)-1)) ? 0 : (k)+1)
#define PREV_IDX(k,num)	((k)==0 ? ((num)-1) : (k)-1)

static void buildNormals(cylin)
PRISM *cylin;
{
	CEDGE *ce;
	float v1[3], v2[3];
	int i, k, n, p;
	float *nrmls;
	float *fnk, *fnn, *fnp;
	float *vn1, *vn2;

	ce = cylin->edges;

	nrmls = (float*)malloc(sizeof(float)*3*3*cylin->size);
	
	for (k=0; k<cylin->size; k++)
	 { n = NEXT_IDX(k,cylin->size);

	   v1[0] = ce[n].x_b - ce[k].x_b;
	   v1[1] = ce[n].y_b - ce[k].y_b;
	   v1[2] = 0.0;

	   v2[0] = ce[k].x_t - ce[k].x_b;
	   v2[1] = ce[k].y_t - ce[k].y_b;
	   v2[2] = cylin->depth;

	   fnk = nrmls; 
	   nrmls += 3;
	   normalizeV3f (fnk, crossV3f(fnk, v1, v2));
	   ce[k].faceNrml = fnk;
	 }

	for (k=0; k<cylin->size; k++)
	 { n = NEXT_IDX(k,cylin->size);
	   p = PREV_IDX(k,cylin->size);

	   vn1 = nrmls; 
	   nrmls += 3;
	   vn2 = nrmls;
	   nrmls += 3;
	   fnk = ce[k].faceNrml;
	   fnn = ce[n].faceNrml;
	   fnp = ce[p].faceNrml;

	   for (i=0; i<3; i++)
	    { if (ce[k].hard && ce[n].hard)
	       { vn1[i] = vn2[i] = fnk[i];
	       }
	      else if (!ce[k].hard && !ce[n].hard)
	       { vn1[i] = (fnp[i] + fnk[i])/2;
		 vn2[i] = (fnk[i] + fnn[i])/2;
	       }
	      else if (ce[k].hard && !ce[n].hard)
	       { vn1[i] = (3*fnk[i] - fnn[i])/2;
		 vn2[i] = (fnk[i] + fnn[i])/2;
	       }
	      else
	       { vn1[i] = (fnp[i] + fnk[i])/2;
		 vn2[i] = (3*fnk[i] - fnp[i])/2;
	       }
	    }
	   normalizeV3f (vn1, vn1);
	   normalizeV3f (vn2, vn2);
	   ce[k].vrtxNrml1 = vn1;
	   ce[k].vrtxNrml2 = vn2;
	 }
}

PRISM *glGenDisc(npts, radius, depth)
int npts;
float radius;
float depth;
{
	PRISM *cylin;

	cylin = gdrGenDisc (npts, radius, depth);
	buildNormals (cylin);
	return(cylin);
}

PRISM *glGenCone(npts, radius1, radius2, depth)
int npts;
float radius1;
float radius2;
float depth;
{
	PRISM *cylin;

	cylin = gdrGenCone(npts, radius1, radius2, depth);
	buildNormals (cylin);
	return(cylin);
}

PRISM *glGenPrism (npts, bot, top, hidden, depth)
int npts;
float *bot, *top;
char *hidden;
float depth;
{
	PRISM *cylin;

	cylin = gdrGenPrism (npts, bot, top, hidden, depth);
	buildNormals (cylin);
	return(cylin);
}

POLYGON *glGenPolygon (npts, vtxs)
int npts;
float *vtxs;
{
	POLYGON *pgn;

	pgn = gdrGenPolygon (npts, vtxs);
	return(pgn);
}

POLYGON *glGenRegPolygon (npts, size)
int npts;
float size;
{
	POLYGON *pgn;

	pgn = gdrGenRegPolygon (npts, size);
	return(pgn);
}

LINESET *glGenLineSet (num, lines)
int num;
float *lines;
{
	LINESET *lset;

	lset = gdrGenLineSet (num, lines);
	return(lset);
}

double glSetDepth (obj, depth)
Grf4Object *obj;
float depth;
{
	float old;

	old = obj->ptr.prism->depth;
	obj->ptr.prism->depth = depth;
	return (old);
}

#define NEXT_IDX(k,num)	(((k)==((num)-1)) ? 0 : (k)+1)
#define PREV_IDX(k,num)	((k)==0 ? ((num)-1) : (k)-1)

glDrawPrism (obj)
Grf4Object *obj;
{
	PRISM *prism = obj->ptr.prism;

	TRSF *T;
	int topVisible;
	int bottomVisible;
	int visible[256];
	float nrml[3];
	float vpnt[3];
	float depth;
	float dex, dey, dez;

	register CEDGE *ep;
	register int k, n;

	if (prism->size < 2)
	 { fprintf (stderr, "drawPrism: less than 2 points\n");
	   exit (-1);
	 }
	T = (glTransPtr ? glTransPtr : &Identity);
	ep = prism->edges;
	depth = prism->depth;

	dex = T->a.x * depth;
	dey = T->a.y * depth;
	dez = T->a.z * depth;

	if (!glPerspectiveMode)
	 { bottomVisible = (T->a.z < 0.0);
	   topVisible = !bottomVisible;
	 }
	else
	 { float w;
	   w = T->p.x*T->a.x + T->p.y*T->a.y + T->p.z*T->a.z;
	   bottomVisible = (w > 0.0);
	   topVisible = (w + dex*T->a.x + dey*T->a.y + dez*T->a.z <= 0.0);
	 }

	for (k=0; k<prism->size; k++)
	 { 
	   multRotV3f (nrml, T, ep[k].faceNrml);
	   if (!glPerspectiveMode)
	    { visible[k] = (nrml[2] > 0.0);
	    }
	   else
	    { vpnt[0] = ep[k].x_b;
	      vpnt[1] = ep[k].y_b;
	      vpnt[2] = 0.0;
	      multTrsfV3f (vpnt, T, vpnt);
	      visible[k] = (dotV3f (vpnt, nrml) < 0.0);
	    }
	 }
	for (k=0; k<prism->size; k++)
	 { int n;
	   
	   n = NEXT_IDX(k,prism->size);

	   if (visible[k])
	    { bgnpolygon();
	      n3f (ep[k].vrtxNrml1);
	      V3F (ep[k].x_t, ep[k].y_t, depth);
	      V3F (ep[k].x_b, ep[k].y_b, 0.0);
	      n3f (ep[k].vrtxNrml2);
	      V3F (ep[n].x_b, ep[n].y_b, 0.0);
	      V3F (ep[n].x_t, ep[n].y_t, depth);
	      endpolygon();
	    }
	 }
	if (topVisible)
	 { bgnpolygon();
	   N3F (0.0, 0.0, 1.0);
	   for (k=0; k<prism->size; k++)
	    { V3F (ep[k].x_t, ep[k].y_t, depth);
	    }
	   endpolygon();
	 }
	else if (bottomVisible)
	 { bgnpolygon();
	   N3F (0.0, 0.0, -1.0);
	   for (k=prism->size-1; k>=0; k--)
	    { V3F (ep[k].x_b, ep[k].y_b, 0.0);
	    }
	   endpolygon();
	 }
}

Matrix identmat = { { 1, 0, 0, 0 },
		    { 0, 1, 0, 0 },
		    { 0, 0, 1, 0 },
		    { 0, 0, 0, 1 } };

typedef struct _sphereTemplate {
  struct _sphereTemplate *next;
  int nTheta;
  int nPhi;
  float (*pnts)[3];
  float (*pntNormals)[3];
  float (*ctrNormals)[3];
  } SPHERE_TEMPLATE;

SPHERE_TEMPLATE *sphereList = 0;
	
#define SPHERE_IDX(s,t,p) (((t)*((s)->nPhi+1))+(p))

float *subtractV3f (vr, v1, v2)
float *vr, *v1, *v2;
{
	vr[0] = v1[0] - v2[0];
	vr[1] = v1[1] - v2[1];
	vr[2] = v1[2] - v2[2];
	return(vr);
}

float magV3f (v1)
float *v1;
{
	return (sqrt(v1[0]*v1[0] + v1[1]*v1[1] + v1[2]*v1[2]));
}

multMatrix(mr, m1, m2)
Matrix mr, m1, m2;
{
	Matrix tmp;
	double x;
	int i, j, k;

	for (i=0; i<4; i++)
	 { for (j=0; j<4; j++)
	    { x = 0;
	      for (k=0; k<4; k++)
	       { x += m1[i][k]*m2[k][j];
	       }
	      tmp[i][j] = x;
	    }
	 }
	bcopy((char*)tmp, (char*)mr, sizeof(Matrix));
}

multV3fMatrix(vr, v1, m1)
float *vr, *v1;
Matrix m1;
{
	float tmp[3];

	tmp[0] = v1[0]*m1[0][0] + v1[1]*m1[1][0] + v1[2]*m1[2][0] + m1[3][0];
	tmp[1] = v1[0]*m1[0][1] + v1[1]*m1[1][1] + v1[2]*m1[2][1] + m1[3][1];
	tmp[2] = v1[0]*m1[0][2] + v1[1]*m1[1][2] + v1[2]*m1[2][2] + m1[3][2];
	bcopy((char*)tmp, (char*)vr, 3*sizeof(float));
}

SPHERE_TEMPLATE *makeSphereTemplate(n)
int n;
{
	SPHERE_TEMPLATE *s, *l;
	int tindex;
	int pindex;
	int i;
	double theta;
	double phi;
	double ct;
	double st;
	double cp;
	double sp;

	s = (SPHERE_TEMPLATE*)malloc(sizeof(SPHERE_TEMPLATE));
	s->nTheta = n;
	s->nPhi = n/2;
	s->pnts = (float(*)[3])malloc(sizeof(float)*3*(n+1)*(n/2+1));
	s->pntNormals = (float(*)[3])malloc(sizeof(float)*3*(n+1)*(n/2+1));
	s->ctrNormals = (float(*)[3])malloc(sizeof(float)*3*(n+1)*(n/2+1));

	for (tindex=0;tindex<=s->nTheta;tindex++)
	 { theta = (tindex * 2 * M_PI) / s->nTheta;
	   ct = cos(theta);
	   st = sin(theta);
	   for (pindex=0;pindex<=s->nPhi;pindex++)
	    { phi = (pindex * M_PI) / s->nPhi;
	      cp = cos(phi);
	      sp = sin(phi);
	      i = SPHERE_IDX(s,tindex,pindex);
	      s->pnts[i][0] = sp * ct;
	      s->pnts[i][1] = sp * st;
	      s->pnts[i][2] = cp;
	      s->pntNormals[i][0] = s->pnts[i][0];
	      s->pntNormals[i][1] = s->pnts[i][1];
	      s->pntNormals[i][2] = s->pnts[i][2];
	    }
	 }
	for (theta=0;theta<s->nTheta;theta++)
	 { for (phi=0;phi<s->nPhi;phi++)
	    { 
	      float v1[3], v2[3], nrml[3];
	      int i1, i2, i3, i4;

	      i1 =      SPHERE_IDX(s,theta,phi);
	      i2 = i1 + SPHERE_IDX(s,0,1);
	      i3 = i2 + SPHERE_IDX(s,1,0);
	      i4 = i3 - SPHERE_IDX(s,0,1);

	      if (phi==0)
	       { subtractV3f(v1, s->pnts[i2], s->pnts[i1]);
		 subtractV3f(v2, s->pnts[i3], s->pnts[i4]);
	       }
	      else if (phi==(s->nPhi-1))
	       { subtractV3f(v1, s->pnts[i4], s->pnts[i3]);
		 subtractV3f(v2, s->pnts[i1], s->pnts[i2]);
	       }
	      else
	       { subtractV3f(v1, s->pnts[i3], s->pnts[i2]);
		 subtractV3f(v2, s->pnts[i1], s->pnts[i2]);
	       }
	      crossV3f (nrml, v1, v2);
	      normalizeV3f (s->ctrNormals[i1], nrml);
	    }
	 }
	return(s);
}

SPHERE_TEMPLATE *getSphereTemplate(n)
{
	SPHERE_TEMPLATE *s;

	for (s=sphereList; s; s = s->next)
	 { if (s->nTheta == n && s->nPhi == n/2)
	    { return(s);
	    }
	 }
	s = makeSphereTemplate(n);
	s->next = sphereList;
	sphereList = s;	
	return(s);
}

int solidsphere(s, smooth, n, r)
SPHERE *s;
int smooth, n;
float r;
{
	Matrix m;
	int i;
	int phi;
	int theta;
	float v1[3], nrml[3];
	float *vp;

	getmatrix(m);

	for (theta=0;theta<s->nTheta;theta++)
	 { for (phi=0;phi<s->nPhi;phi++)
	    { 
	      i = SPHERE_IDX(s,theta,phi);

	      multV3fMatrix (v1, s->pnts[i], m);
	      vp = s->ctrNormals[i];
	      nrml[0] = vp[0]*m[0][0] + vp[1]*m[1][0] + vp[2]*m[2][0];
	      nrml[1] = vp[0]*m[0][1] + vp[1]*m[1][1] + vp[2]*m[2][1];
	      nrml[2] = vp[0]*m[0][2] + vp[1]*m[1][2] + vp[2]*m[2][2];

	      if (dotV3f(nrml, v1) < 0.0)
	       { 
		 bgnpolygon();
		 if (!smooth)
		  { n3f(s->ctrNormals[i]);
		  }
		 else
		  { n3f(s->pntNormals[i]);
		  }
		 V3F(r*s->pnts[i][0], r*s->pnts[i][1], r*s->pnts[i][2]);
		 i += SPHERE_IDX(s,0,1);
		 if (smooth)
		  { n3f(s->pntNormals[i]);
		  }
		 V3F(r*s->pnts[i][0], r*s->pnts[i][1], r*s->pnts[i][2]);
		 i += SPHERE_IDX(s,1,0);
		 if (smooth)
		  { n3f(s->pntNormals[i]);
		  }
		 V3F(r*s->pnts[i][0], r*s->pnts[i][1], r*s->pnts[i][2]);
		 i -= SPHERE_IDX(s,0,1);
		 if (smooth)
		  { n3f(s->pntNormals[i]);
		  }
		 V3F(r*s->pnts[i][0], r*s->pnts[i][1], r*s->pnts[i][2]);
		 endpolygon();
	       }
	    }
	 }
	return(0);
}

SPHERE *glGenSphere (npts, radius)
int npts;
float radius;
{
	SPHERE *s;
	SPHERE_TEMPLATE *t;

	s = ALLOC(SPHERE);
	bzero((char*)s, sizeof(SPHERE));
	s->radius = radius;
	s->npts = npts;
	t = getSphereTemplate(npts);
	s->nTheta = t->nTheta;
	s->nPhi = t->nPhi;
	s->pnts = t->pnts;
	s->pntNormals = t->pntNormals;
	s->ctrNormals = t->ctrNormals;	

	return(s);
}

void glDrawSphere (obj)
Grf4Object *obj;
{
	SPHERE *s = obj->ptr.sphere;

	solidsphere (s, /*smooth=*/1, s->npts, s->radius);
}

void glDrawPolygon(obj)
Grf4Object *obj;
{
	POLYGON *pgn = obj->ptr.polygon;
	float *ep;
	int k;

	bgnpolygon();
	N3F (0.0, 0.0, 1.0);
	for (k=0, ep=pgn->edges; k<pgn->size; k++, ep+=2)
	 { V3F (ep[0], ep[1], 0.0);
	 }
	endpolygon();
}

void glDrawLineSet(obj)
Grf4Object *obj;
{
	LINESET *lset = obj->ptr.lineSet;
	register float *l;
	float *last_l;

	for (l=lset->lines, last_l=l+lset->num*4; l<last_l; l+=4)
	 { move2 (l[0], l[1]);
	   draw2 (l[2], l[3]);
	 }
}

#endif /* GL_GRAPHICS */
