/* Copyright (c) 2001, Stanford University
 * All rights reserved
 *
 * See the file LICENSE.txt for information on redistributing this software.
 */

#include <float.h>
#include "cr_bbox.h"

/**
 * \mainpage Util 
 *
 * \section UtilIntroduction Introduction
 *
 * Chromium consists of all the top-level files in the cr
 * directory.  The util module basically takes care of API dispatch,
 * and OpenGL state management.
 *
 */
static float _vmult(const float *m, float x, float y, float z) 
{
	return m[0]*x + m[4]*y + m[8]*z + m[12];
}

void
crTransformBBox( float xmin, float ymin, float zmin,
                 float xmax, float ymax, float zmax,
                 const CRmatrix *m,
                 float *out_xmin, float *out_ymin, float *out_zmin,
                 float *out_xmax, float *out_ymax, float *out_zmax )
{
	float x[8], y[8], z[8], w[8];
	int i,j;
    
	/*  Here is the arrangement of the bounding box
	 *  
	 *           0 --- 1
	 *           |\    .\
	 *           | 2 --- 3 
	 *           | |   . |
	 *           | |   . |
	 *           4.|...5 |
	 *            \|    .|
	 *             6 --- 7
	 *  
	 *  c array contains the edge connectivitiy list
	 */

	static const int c[8][3] = {	
		{1, 2, 4}, 
		{0, 3, 5}, 
		{0, 3, 6}, 
		{1, 2, 7},
		{0, 5, 6}, 
		{1, 4, 7}, 
		{2, 4, 7}, 
		{3, 5, 6} 
	};

	x[0] = _vmult(&(m->m00), xmin, ymin, zmin);
	x[1] = _vmult(&(m->m00), xmax, ymin, zmin);
	x[2] = _vmult(&(m->m00), xmin, ymax, zmin);
	x[3] = _vmult(&(m->m00), xmax, ymax, zmin);
	x[4] = _vmult(&(m->m00), xmin, ymin, zmax);
	x[5] = _vmult(&(m->m00), xmax, ymin, zmax);
	x[6] = _vmult(&(m->m00), xmin, ymax, zmax);
	x[7] = _vmult(&(m->m00), xmax, ymax, zmax);

	y[0] = _vmult(&(m->m01), xmin, ymin, zmin);
	y[1] = _vmult(&(m->m01), xmax, ymin, zmin);
	y[2] = _vmult(&(m->m01), xmin, ymax, zmin);
	y[3] = _vmult(&(m->m01), xmax, ymax, zmin);
	y[4] = _vmult(&(m->m01), xmin, ymin, zmax);
	y[5] = _vmult(&(m->m01), xmax, ymin, zmax);
	y[6] = _vmult(&(m->m01), xmin, ymax, zmax);
	y[7] = _vmult(&(m->m01), xmax, ymax, zmax);

	z[0] = _vmult(&(m->m02), xmin, ymin, zmin);
	z[1] = _vmult(&(m->m02), xmax, ymin, zmin);
	z[2] = _vmult(&(m->m02), xmin, ymax, zmin);
	z[3] = _vmult(&(m->m02), xmax, ymax, zmin);
	z[4] = _vmult(&(m->m02), xmin, ymin, zmax);
	z[5] = _vmult(&(m->m02), xmax, ymin, zmax);
	z[6] = _vmult(&(m->m02), xmin, ymax, zmax);
	z[7] = _vmult(&(m->m02), xmax, ymax, zmax);

	w[0] = _vmult(&(m->m03), xmin, ymin, zmin);
	w[1] = _vmult(&(m->m03), xmax, ymin, zmin);
	w[2] = _vmult(&(m->m03), xmin, ymax, zmin);
	w[3] = _vmult(&(m->m03), xmax, ymax, zmin);
	w[4] = _vmult(&(m->m03), xmin, ymin, zmax);
	w[5] = _vmult(&(m->m03), xmax, ymin, zmax);
	w[6] = _vmult(&(m->m03), xmin, ymax, zmax);
	w[7] = _vmult(&(m->m03), xmax, ymax, zmax);

	/* Now, the object-space bbox has been transformed into 
	 * clip-space. */

	/* Find the 2D bounding box of the 3D bounding box */
	xmin = ymin = zmin = FLT_MAX;
	xmax = ymax = zmax = -FLT_MAX;

	for (i=0; i<8; i++) 
	{
		float xp = x[i];
		float yp = y[i];
		float zp = z[i];
		float wp = w[i];

		/* If corner is to be clipped... */
		if (zp < -wp) 
		{

			/* Point has three edges */
			for (j=0; j<3; j++) 
			{
				/* Handle the clipping... */
				int k = c[i][j];
				float xk = x[k];
				float yk = y[k];
				float zk = z[k];
				float wk = w[k];
				float t;

				if (zp+wp-zk-wk == 0.0)
					continue; /* avoid divide by zero */
				else 
					t = (wp + zp) / (zp+wp-zk-wk);

				if (t < 0.0f || t > 1.0f)
				{
					continue;
				}
				wp = wp + (wk-wp) * t;
				xp = xp + (xk-xp) * t;
				yp = yp + (yk-yp) * t;
				zp = -wp;

				xp /= wp;
				yp /= wp;
				zp /= wp;

				if (xp < xmin) xmin = xp;
				if (xp > xmax) xmax = xp;
				if (yp < ymin) ymin = yp;
				if (yp > ymax) ymax = yp;
				if (zp < zmin) zmin = zp;
				if (zp > zmax) zmax = zp;
			}
		} 
		else 
		{
			/* corner was not clipped.. */
			xp /= wp;
			yp /= wp;
			zp /= wp;
			if (xp < xmin) xmin = xp;
			if (xp > xmax) xmax = xp;
			if (yp < ymin) ymin = yp;
			if (yp > ymax) ymax = yp;
			if (zp < zmin) zmin = zp;
			if (zp > zmax) zmax = zp;
		}
	}

	/* Copy for export */
	if (out_xmin) *out_xmin = xmin;
	if (out_ymin) *out_ymin = ymin;
	if (out_zmin) *out_zmin = zmin;
	if (out_xmax) *out_xmax = xmax;
	if (out_ymax) *out_ymax = ymax;
	if (out_zmax) *out_zmax = zmax;
}

/**
 * Given the coordinates of the two opposite corners of a bounding box
 * in object space (x1,y1,z1) and (x2,y2,z2), use the given modelview
 * and projection matrices to transform the coordinates to NDC space.
 * Find the 3D bounding bounding box of those eight coordinates and
 * return the min/max in (x1,y1,x1) and (x2,y2,z2).
 */
void
crProjectBBox(const GLfloat modl[16], const GLfloat proj[16], 
              GLfloat *x1, GLfloat *y1, GLfloat *z1,
              GLfloat *x2, GLfloat *y2, GLfloat *z2) 
{
	CRmatrix m;
	
	/* compute product of modl and proj matrices */
	m.m00  = proj[0] * modl[0]  + proj[4] * modl[1]  + proj[8]  * modl[2]  + proj[12] * modl[3];	
	m.m01  = proj[1] * modl[0]  + proj[5] * modl[1]  + proj[9]  * modl[2]  + proj[13] * modl[3];	
	m.m02  = proj[2] * modl[0]  + proj[6] * modl[1]  + proj[10] * modl[2]  + proj[14] * modl[3];	
	m.m03  = proj[3] * modl[0]  + proj[7] * modl[1]  + proj[11] * modl[2]  + proj[15] * modl[3];	
	m.m10  = proj[0] * modl[4]  + proj[4] * modl[5]  + proj[8]  * modl[6]  + proj[12] * modl[7];	
	m.m11  = proj[1] * modl[4]  + proj[5] * modl[5]  + proj[9]  * modl[6]  + proj[13] * modl[7];	
	m.m12  = proj[2] * modl[4]  + proj[6] * modl[5]  + proj[10] * modl[6]  + proj[14] * modl[7];	
	m.m13  = proj[3] * modl[4]  + proj[7] * modl[5]  + proj[11] * modl[6]  + proj[15] * modl[7];	
	m.m20  = proj[0] * modl[8]  + proj[4] * modl[9]  + proj[8]  * modl[10] + proj[12] * modl[11];	
	m.m21  = proj[1] * modl[8]  + proj[5] * modl[9]  + proj[9]  * modl[10] + proj[13] * modl[11];
	m.m22  = proj[2] * modl[8]  + proj[6] * modl[9]  + proj[10] * modl[10] + proj[14] * modl[11];	
	m.m23  = proj[3] * modl[8]  + proj[7] * modl[9]  + proj[11] * modl[10] + proj[15] * modl[11];	
	m.m30  = proj[0] * modl[12] + proj[4] * modl[13] + proj[8]  * modl[14] + proj[12] * modl[15];	
	m.m31  = proj[1] * modl[12] + proj[5] * modl[13] + proj[9]  * modl[14] + proj[13] * modl[15];	
	m.m32  = proj[2] * modl[12] + proj[6] * modl[13] + proj[10] * modl[14] + proj[14] * modl[15];	
	m.m33  = proj[3] * modl[12] + proj[7] * modl[13] + proj[11] * modl[14] + proj[15] * modl[15]; 
	
    crTransformBBox( *x1, *y1, *z1,
                     *x2, *y2, *z2,
                     &m,
                     x1, y1, z1,
                     x2, y2, z2 );
}


#define MIN(a, b) ((a) < (b) ? (a) : (b))
#define MAX(a, b) ((a) > (b) ? (a) : (b))


/**
 * Compute union of a and b and put in result.
 */
void
crRectiUnion(CRrecti *result, const CRrecti *a, const CRrecti *b)
{
	result->x1 = MIN(a->x1, b->x1);
	result->x2 = MAX(a->x2, b->x2);
	result->y1 = MIN(a->y1, b->y1);
	result->y2 = MAX(a->y2, b->y2);
}
