/***********************************************************************/
/* Open Visualization Data Explorer                                    */
/* (C) Copyright IBM Corp. 1989,1999                                   */
/* ALL RIGHTS RESERVED                                                 */
/* This code licensed under the                                        */
/*    "IBM PUBLIC LICENSE - Open Visualization Data Explorer"          */
/***********************************************************************/

#include <dxconfig.h>



#include <dx/dx.h>
#include "render.h"

/*
 * The following is a kludge relating to to whether or not the
 * z-buffer is updated when a translucent surface is rendered.
 * When we render two transparent objects, we want the closer to
 * overlay the farther, which itself overlays still farther surfaces.
 * A centroid sort is used to approximately prioritize the transparent
 * surfaces.  If it is incorrect at a given pixel, then (in the absense
 * of any other information) they are overlaid in the incorrect order.
 * Which, if the transparency of the front-most (and rendered first)
 * transparent surface is low, is an OK approximation.  If, however, the
 * frontmost surface is relatively opaque, then this approximation
 * becomes less accurate.  In this case it is better to forget about
 * the second transparent surface (which would erroneously be laid over
 * it).  So when the surface is relatively opaque, we update the z-buffer
 * to eliminate subsequent farther surfaces.
 *
 * Note that this appears in triangles.h also.
 */
#define OPACITY_THRESHOLD 0.5


#define ABS(x) ((x)<0? -(x) : (x))
#define FLOOR(x) ((int)((x)+10000.0)-10000)
#define ROUND(x) ((int)((x)+10000.5)-10000)

/*
 * For translucent lines, we can assume that ->back has
 * already been merged into ->z; furthermore, we reuse ->back
 * in volume rendering, so it is incorrect to compare against
 * ->back here because we may be mixed in with volume faces
 * This also occurs in triangle.c.
 */

#define SCALE (1<<10)	/* integer scaling */
#define MAX (1<<20)	/* max coordinates before overflow */

#define CLIPZ      (z>pix->z && z<pix->front && z>pix->back)
#define NOCLIPZ    (z>pix->z)
#define TRANSCLIPZ (z>pix->z && z<pix->front)

#define LOOP(flat,op,ztest) {						    \
									    \
    for (i=0; i<=nmaj; i++) {						    \
	if (op) {							    \
	    if (0<=x && x<width && 0<=y && y<height && ztest) {		    \
		obar = 1.0 - o;						    \
		pix->c.r = o*r + obar*pix->c.r;				    \
		pix->c.g = o*g + obar*pix->c.g;				    \
		pix->c.b = o*b + obar*pix->c.b;				    \
		if (o > OPACITY_THRESHOLD)                                  \
		    pix->z = z;						    \
	    }								    \
	} else {							    \
	    if (0<=x && x<width && 0<=y && y<height && ztest) {		    \
		pix->c.r = r;						    \
		pix->c.g = g;						    \
		pix->c.b = b;						    \
		pix->z = z;						    \
	    }								    \
	}								    \
	pix += dpix_dmaj;						    \
	x += dx_dmaj;							    \
	y += dy_dmaj;							    \
	if ((frac-=min)<0) {						    \
	    frac += maj;						    \
	    pix += dpix_dmin;						    \
	    x += dx_dmin;						    \
	    y += dy_dmin;						    \
	}								    \
	z += dz;							    \
	if (!flat) {							    \
	    r += dr;							    \
	    g += dg;							    \
	    b += db;							    \
	}								    \
	if (op) o += dop;						    \
    }									    \
}

#define CLIP(flat,op,clipz) {	\
    if (clip)			\
	LOOP(flat,op,clipz)	\
    else			\
	LOOP(flat,op,NOCLIPZ)	\
}

#define ZCLIP(p,p1, c,c1, o,o1) {			\
    float a = (nearPlane-p.z) / (p1.z-p.z); /* XXX */	\
    float abar = 1.0-a;					\
    p.x = p.x*abar + p1.x*a;				\
    p.y = p.y*abar + p1.y*a;				\
    p.z = nearPlane;					\
    c.r = c.r*abar + c1.r*a;				\
    c.g = c.g*abar + c1.g*a;				\
    c.b = c.b*abar + c1.b*a;				\
    o = o*abar + o1*a;					\
    p.z = nearPlane;					\
}

#define XYCLIP(p,p1, c,c1, o,o1, x,y, dx,dy, op, mm) {		\
    float m = mm;						\
    if (p.x op m) {						\
        float d = (m - p.x) / dx;				\
	if (d<.5) {						\
	    p.y += d * dy;					\
	    p.z += d * (p1.z - p.z);				\
	    c.r += d * (c1.r - c.r);				\
	    c.g += d * (c1.g - c.g);				\
	    c.b += d * (c1.b - c.b);				\
	    o += d * (o1 - o);					\
	} else {						\
	    d = (m - p1.x) / dx;				\
	    p.y = p1.y + d * dy;				\
	    p.z = p1.z + d * (p1.z - p.z);			\
	    c.r = c1.r + d * (c1.r - c.r);			\
	    c.g = c1.g + d * (c1.g - c.g);			\
	    c.b = c1.b + d * (c1.b - c.b);			\
	    o = o1 + d * (o1 - o);				\
	}							\
	p.x = m;						\
    }								\
}



static Error
line(struct buffer *buf, struct xfield *xf, Line l,
     int flat, RGBColor c, RGBColor c1, float o, float o1, int clip)
{
    Point p, p1;
    float length, d, dr, dg, db, r, g, b, z;
    float dx, dy, dz, dop, obar, a;
    int maj, dpix_dmaj, dy_dmaj, dx_dmaj;
    int min, dpix_dmin, dy_dmin, dx_dmin;
    int i, nmaj, x, y, frac;
    int width = buf->width, height = buf->height;

    /* positions */
    p = xf->positions[l.p];
    p1 = xf->positions[l.q];

    /* perspective */
    if (xf->tile.perspective) {
	float nearPlane = xf->nearPlane;
	if (p.z>nearPlane && p1.z>nearPlane)
	    return OK;
	if (p.z>nearPlane) {
	    ZCLIP(p,p1, c,c1, o,o1);
	} else if (p1.z>nearPlane) {
	    ZCLIP(p1,p, c1,c, o1,o);
	}
	p.z = (float)-1.0/p.z;  p.x *= p.z;  p.y *= p.z;
	p1.z = (float)-1.0/p1.z;  p1.x *= p1.z;  p1.y *= p1.z;

    }

    /* quick check */
    if ((p.x<buf->min.x-.5 && p1.x<buf->min.x-.5) ||
	(p.y<buf->min.y-.5 && p1.y<buf->min.y-.5) ||
	(p.x>buf->max.x+.5 && p1.x>buf->max.x+.5) ||
	(p.y>buf->max.y+.5 && p1.y>buf->max.y+.5))
	return OK;

#if 0
    /* overflow check - XXX check performance impact */
    if (p.x>=MAX || p.x<=-MAX || p.y>=MAX || p.y<=-MAX
      || p1.x>=MAX || p1.x<=-MAX || p1.y>=MAX || p1.y<=-MAX)
	DXErrorReturn(ERROR_BAD_PARAMETER, "camera causes numerical overflow");
#endif

    /* deltas */
    dx = p1.x - p.x;
    dy = p1.y - p.y;

    /* octant-dependent setup */
    if (ABS(dy) < ABS(dx)) {

	XYCLIP(p,p1, c,c1, o,o1, x,y,  dx, dy, <, buf->min.x-.5);
	XYCLIP(p1,p, c1,c, o1,o, x,y, -dx,-dy, <, buf->min.x-.5);
	XYCLIP(p,p1, c,c1, o,o1, x,y,  dx, dy, >, buf->max.x+.5);
	XYCLIP(p1,p, c1,c, o1,o, x,y, -dx,-dy, >, buf->max.x+.5);
	dx = p1.x - p.x;
	dy = p1.y - p.y;

	x = ROUND(p.x);
	d = x - p.x;
	a = p.y + (dx? dy/dx*d : 0);
	y = ROUND(a);

	if (dx>0) dx_dmaj =  1,  length =  dx,  nmaj = ROUND(p1.x) - x;
	else      dx_dmaj = -1,  length = -dx,  nmaj = x - ROUND(p1.x);
	maj = length * SCALE;
	frac = (a - y) * maj;
	if (dy>0) dy_dmin =  1,  min =  dy*SCALE,  frac = maj/2 - frac;
	else      dy_dmin = -1,  min = -dy*SCALE,  frac = maj/2 + frac;
	dx_dmin = 0;
	dy_dmaj = 0;

    } else {

	XYCLIP(p,p1, c,c1, o,o1, y,x,  dy, dx, <, buf->min.y-.5);
	XYCLIP(p1,p, c1,c, o1,o, y,x, -dy,-dx, <, buf->min.y-.5);
	XYCLIP(p,p1, c,c1, o,o1, y,x,  dy, dx, >, buf->max.y+.5);
	XYCLIP(p1,p, c1,c, o1,o, y,x, -dy,-dx, >, buf->max.y+.5);
	dx = p1.x - p.x;
	dy = p1.y - p.y;

	y = ROUND(p.y);
	d = y - p.y;
	a = p.x + (dy? dx/dy*d : 0);
	x = ROUND(a);

	if (dy>0) dy_dmaj =  1,  length =  dy,  nmaj = ROUND(p1.y) - y;
	else      dy_dmaj = -1,  length = -dy,  nmaj = y - ROUND(p1.y);
	maj = length * SCALE;
	frac = (a - x) * maj;
	if (dx>0) dx_dmin =  1,  min =  dx*SCALE,  frac = maj/2 - frac;
	else      dx_dmin = -1,  min = -dx*SCALE,  frac = maj/2 + frac;
	dy_dmin = 0;
	dx_dmaj = 0;

	length = ABS(dy);
    }

    /* delta z, color, taking care not to divide by 0 */
    dz  = length? (p1.z - p.z) / length : 0.0;

    if (! flat)
    {
	dop = length? (o1   - o  ) / length : 0.0;
	dr  = length? (c1.r - c.r) / length : 0.0;
	dg  = length? (c1.g - c.g) / length : 0.0;
	db  = length? (c1.b - c.b) / length : 0.0;
	r = c.r + dr * d;
	g = c.g + dg * d;
	b = c.b + db * d;
	o += dop * d;
    }
    else
    {
	r = c.r;
	g = c.g;
	b = c.b;
    }

    z = p.z + dz * d;

    /* delta pix */
    dpix_dmaj = dy_dmaj*width + dx_dmaj;
    dpix_dmin = dy_dmin*width + dx_dmin;

    /* offset */
    x += buf->ox;
    y += buf->oy;

    if (buf->pix_type==pix_fast) {
	struct fast *pix = buf->u.fast + y*width + x;
	ASSERT(!clip);
	if (flat)
	    if (xf->opacities)
		LOOP(1,1, NOCLIPZ)
	    else
		LOOP(1,0, NOCLIPZ)
	else
	    if (xf->opacities)
		LOOP(0,1, NOCLIPZ)
	    else
		LOOP(0,0, NOCLIPZ)
    } else if (buf->pix_type==pix_big) {
	struct big *pix = buf->u.big + y*width + x;
	if (flat)
	    if (xf->opacities)
		CLIP(1,1, TRANSCLIPZ)
	    else
		CLIP(1,0, CLIPZ)
	else
	    if (xf->opacities)
		CLIP(0,1, TRANSCLIPZ)
	    else
		CLIP(0,0, CLIPZ)
    }

    return OK;
}


#define COLOR(i, dst) \
{ \
    if (cmap) \
	dst = cmap[((unsigned char *)colors)[i]]; \
    else if (fbyte) \
    { \
	ubyte *ptr = ((ubyte *)colors + 3*i); \
	dst.r = _dxd_ubyteToFloat[*ptr++]; \
	dst.g = _dxd_ubyteToFloat[*ptr++]; \
	dst.b = _dxd_ubyteToFloat[*ptr++]; \
    } \
    else \
	dst = ((RGBColor *)colors)[i]; \
}

#define OPACITY(i, dst) \
{ \
    if (opacities) { \
	if (omap) \
	    dst = omap[((unsigned char *)opacities)[i]]; \
	else if (obyte) \
	    dst = _dxd_ubyteToFloat[((unsigned char *)opacities)[i]]; \
	else \
	    dst = ((float *)opacities)[i]; \
    } else \
	dst = 1.0; \
}

Error
_dxf_Line(struct buffer *b, struct xfield *xf,
	int n, Line *lines, int *indices, int clip, inv_stat inv)
{
    unsigned char *colors = (unsigned char *)xf->fcolors;
    unsigned char *opacities = (unsigned char *)xf->opacities;
    RGBColor *cmap = xf->cmap;
    float *omap = xf->omap;
    int i;
    InvalidComponentHandle ich = xf->iElts;
    RGBColor cbuf[2];
    char fbyte = xf->fbyte;
    char obyte = xf->obyte;
    char fcst = xf->fcst;
    char ocst = xf->ocst;
    float pop, qop;
    int odep = xf->colors_dep == dep_connections;

    if (ocst) {
	OPACITY(0, pop);
	OPACITY(0, qop);
    }

    if (fbyte || obyte)
	_dxf_initUbyteToFloat();

    if (xf->lights)
    {

	_dxfInitApplyLights(xf->kaf, xf->kdf, xf->ksf, xf->kspf,
			    xf->kab, xf->kdb, xf->ksb, xf->kspb,
			    xf->fcolors, xf->bcolors, xf->cmap, 
			    xf->normals, xf->lights, 
			    xf->colors_dep, xf->normals_dep==dep_positions,
			    cbuf, NULL, 2, xf->fcst, xf->bcst, xf->ncst, 
			    fbyte, 0);
	
	for (i=0; i<n; i++)
	{
	    int index = (indices) ? indices[i] : i;
	    Line *l = &lines[index];
	    
	    if (ich && DXIsElementInvalid(ich, index))
		continue;
	    
	    if (! _dxfApplyLights((int *)l, &index, 1))
		return ERROR;

	    if (!ocst) {
		if (odep) {
		    OPACITY(index, pop);
		    qop = pop;
		} else {
		    OPACITY(l->p, pop);
		    OPACITY(l->q, qop);
		}
	    }

	    if (!line(b, xf, *l, 0, cbuf[0], cbuf[1], pop, qop, clip))
		return ERROR;
	}
    }
    else
    {
	RGBColor pColor, qColor;

	if (!colors)
	    DXErrorReturn(ERROR_BAD_PARAMETER, "lines must have front colors");
	
	if (fcst)
	{
	    COLOR(0, qColor);
	    pColor = qColor;
	}
	
	for (i=0; i<n; i++) {
	    int index = (indices) ? indices[i] : i;
	    Line *l = &lines[index];
	    Error rc;
	    
	    if (ich && DXIsElementInvalid(ich, index))
		continue;
	    
	    if (!ocst) {
		if (odep) {
		    OPACITY(index, pop);
		    qop = pop;
		} else {
		    OPACITY(l->p, pop);
		    OPACITY(l->q, qop);
		}
	    }

	    if (! fcst) {
		if (odep) {
		    COLOR(index, pColor);
		    qColor = pColor;
		} else {
		    COLOR(l->p, pColor);
		    COLOR(l->q, qColor);
		}
	    }

	    rc = line(b, xf, *l, 0, pColor, qColor, pop, qop, clip);

	    if (!rc) return ERROR;
	}
    }

    return OK;
}


Error
_dxf_LineFlat(struct buffer *b, struct xfield *xf, int n, Line *lines,
	  int *indices,
	  Pointer colors, Pointer opacities, int clip, inv_stat inv)
{
    RGBColor *cmap = xf->cmap;
    float *omap = xf->omap;
    char obyte = xf->obyte;
    int i;
    InvalidComponentHandle ich = xf->iElts;
    RGBColor pColor, qColor;
    char fcst = xf->fcst;
    char fbyte = xf->fbyte;
    char ocst = xf->ocst;
    float pop, qop;
    int odep = xf->colors_dep == dep_connections;

    if (ocst) {
	OPACITY(0, pop);
	OPACITY(0, qop);
    }

    if (!xf->fcolors)
	DXErrorReturn(ERROR_BAD_PARAMETER, "lines must have front colors");

    if (fcst)
    {
	COLOR(0, pColor);
	qColor = pColor;
    }

    for (i=0; i<n; i++) {
	int index = (indices) ? indices[i] : i;
	Line *l = &lines[index];
	Error rc;

	if (ich && DXIsElementInvalid(ich, index))
	    continue;

	if (!ocst) {
	    if (odep) {
		OPACITY(index, pop);
		qop = pop;
	    } else {
		OPACITY(l->p, pop);
		OPACITY(l->q, qop);
	    }
	}

	if (! fcst) {
	    if (odep) {
		COLOR(index, pColor);
		qColor = pColor;
	    } else {
		COLOR(l->p, pColor);
		COLOR(l->q, qColor);
	    }
	}

	rc = line(b, xf, *l, 1, pColor, qColor, pop, qop, clip);

	if (!rc) return ERROR;
    }

    return OK;
}

Error
_dxf_Polyline(struct buffer *b, struct xfield *xf,
	int n, int *polylines, int *indices, int clip, inv_stat inv)
{
    unsigned char *colors = (unsigned char *)xf->fcolors;
    unsigned char *opacities = (unsigned char *)xf->opacities;
    RGBColor *cmap = xf->cmap;
    float *omap = xf->omap;
    int i;
    InvalidComponentHandle ich = xf->iElts;
    char fbyte = xf->fbyte;
    char obyte = xf->obyte;
    char fcst = xf->fcst;
    char ocst = xf->ocst;
    float pop, qop;
    dependency cdep = xf->colors_dep;
    int start, last, knt, j;
    Line *l;
    RGBColor *cbuf = NULL;
    int cbufknt = 0;

    if (indices)
    {
	DXSetError(ERROR_INTERNAL, "non-null indices in PolyLine");
	return ERROR;
    }

    if (ocst) {
	OPACITY(0, pop);
	OPACITY(0, qop);
    }

    if (fbyte || obyte)
	_dxf_initUbyteToFloat();

    if (xf->lights)
    {
	for (i=0; i<n; i++)
	{
	    start = polylines[i];
	    last  = (i == n-1) ? xf->nedges-1 : polylines[i+1]-1;
	    knt   = last - start;

	    if (ich && DXIsElementInvalid(ich, i))
		continue;

	    if (knt+1 > cbufknt)
	    {
		cbuf = (RGBColor *)DXReAllocate(cbuf, (knt+1)*sizeof(RGBColor));
		if (! cbuf)
		    goto error;
		
		cbufknt = (knt+1);
	    }

	    l = (Line *)(xf->edges + start);

	    /*
	     * As far as appliyong lights goes, we pretend that polylines
	     * are effectively connections elements of varying numbers
	     * of vertices
	     */

	    _dxfInitApplyLights(xf->kaf, xf->kdf, xf->ksf, xf->kspf,
				xf->kab, xf->kdb, xf->ksb, xf->kspb,
				xf->fcolors, xf->bcolors, xf->cmap, 
				xf->normals, xf->lights, 
				xf->colors_dep, xf->normals_dep, cbuf,
				NULL, (knt+1), xf->fcst, xf->bcst, xf->ncst, 
				fbyte, 0);
	

	    if (! _dxfApplyLights((int *)l, &i, 1))
		return ERROR;

	    if (!ocst && cdep == dep_positions)
		OPACITY(l->q, qop);

	    for (j = 0; j < knt; j++, l = (Line *)(((int *)l) + 1))
	    {
		if (!ocst && cdep == dep_positions)
		{
		    pop = qop;
		    OPACITY(l->q, qop);
		}

		if (!line(b, xf, *l, 0, cbuf[j], cbuf[j+1], pop, qop, clip))
		    return ERROR;
	    }
	}
    }
    else
    {
	RGBColor pColor, qColor;

	if (!colors)
	    DXErrorReturn(ERROR_BAD_PARAMETER, "lines must have front colors");
	
	if (fcst)
	{
	    COLOR(0, qColor);
	    pColor = qColor;
	}
	
	for (i=0; i<n; i++)
	{
	    start = polylines[i];
	    last  = (i == n-1) ? xf->nedges-1 : polylines[i+1]-1;
	    knt   = last - start;
	    
	    if (ich && DXIsElementInvalid(ich, i))
		continue;

	    l = (Line *)(xf->edges + start);

	    if (!ocst)
	    {
		if (cdep == dep_polylines)
		{
		    OPACITY(i, pop);
		    qop = pop;
		}
		else if (cdep == dep_positions)
		{
		    OPACITY(l->p, qop);
		}
	    }

	    if (!fcst)
	    {
		if (cdep == dep_polylines)
		{
		    COLOR(i, pColor);
		    qColor = pColor;
		}
		else if (cdep == dep_positions)
		{
		    COLOR(l->p, qColor);
		}
	    }

	    for (j = 0; j < knt; j++, l = (Line *)(((int *)l) + 1))
	    {
	    
		if (!ocst && cdep == dep_positions)
		{
		    pop = qop;
		    OPACITY(l->q, qop);
		}

		if (!fcst && cdep == dep_positions)
		{
		    pColor = qColor;
		    COLOR(l->q, qColor);
		}

		if (!line(b, xf, *l, 0, pColor, qColor, pop, qop, clip))
		    return ERROR;
	    }
	}
    }

    if (cbuf)
	DXFree((Pointer)cbuf);

    return OK;

error:
    if (cbuf)
	DXFree((Pointer)cbuf);

    return ERROR;
}


Error
_dxf_PolylineFlat(struct buffer *b, struct xfield *xf, int n, int *polylines,
	  int *indices,
	  Pointer colors, Pointer opacities, int clip, inv_stat inv)
{
    RGBColor *cmap = xf->cmap;
    float *omap = xf->omap;
    char obyte = xf->obyte;
    int i;
    InvalidComponentHandle ich = xf->iElts;
    RGBColor pColor, qColor;
    char fcst = xf->fcst;
    char fbyte = xf->fbyte;
    char ocst = xf->ocst;
    float pop, qop;
    dependency cdep = xf->colors_dep;
    int start, last, knt, j;
    Line *l;

    if (ocst) {
	OPACITY(0, pop);
	OPACITY(0, qop);
    }

    if (!xf->fcolors)
	DXErrorReturn(ERROR_BAD_PARAMETER, "polylines must have front colors");

    if (fcst)
    {
	COLOR(0, pColor);
	qColor = pColor;
    }

    for (i=0; i<n; i++) {

	start = polylines[i];
	last  = (i == n-1) ? xf->nedges-1 : polylines[i+1]-1;
	knt   = last - start;

	if (ich && DXIsElementInvalid(ich, i))
	    continue;

	l = (Line *)(xf->edges + start);

	if (!ocst)
	{
	    if (cdep == dep_polylines)
	    {
		OPACITY(i, pop);
		qop = pop;
	    }
	    else if (cdep == dep_positions)
	    {
		OPACITY(l->p, qop);
	    }
	}

	if (!fcst)
	{
	    if (cdep == dep_polylines)
	    {
		COLOR(i, pColor);
		qColor = pColor;
	    }
	    else if (cdep == dep_positions)
	    {
		COLOR(l->p, qColor);
	    }
	}

	for (j = 0; j < knt; j++, l = (Line *)(((int *)l) + 1))
	{
	    if (!ocst && cdep == dep_positions)
	    {
		pop = qop;
		OPACITY(l->q, qop);
	    }

	    if (!fcst && cdep == dep_positions)
	    {
		pColor = qColor;
		COLOR(l->q, qColor);
	    }

	    if (! line(b, xf, *l, 1, pColor, qColor, pop, qop, clip))
		return ERROR;
	}

    }

    return OK;
}
