/*
** Copyright 1991, Silicon Graphics, Inc.
** All Rights Reserved.
**
** This is UNPUBLISHED PROPRIETARY SOURCE CODE of Silicon Graphics, Inc.;
** the contents of this file may not be disclosed to third parties, copied or
** duplicated in any form, in whole or in part, without the prior written
** permission of Silicon Graphics, Inc.
**
** RESTRICTED RIGHTS LEGEND:
** Use, duplication or disclosure by the Government is subject to restrictions
** as set forth in subdivision (c)(1)(ii) of the Rights in Technical Data
** and Computer Software clause at DFARS 252.227-7013, and/or in similar or
** successor clauses in the FAR, DOD or NASA FAR Supplement. Unpublished -
** rights reserved under the Copyright Laws of the United States.
*/
#include "precomp.h"
#pragma hdrstop

/*
** The following is a discussion of the math used to do edge clipping against
** a clipping plane.
** 
**     P1 is an end point of the edge
**     P2 is the other end point of the edge
** 
**     Q = t*P1 + (1 - t)*P2
**     That is, Q lies somewhere on the line formed by P1 and P2.
** 
**     0 <= t <= 1
**     This constrains Q to lie between P1 and P2.
** 
**     C is the plane equation for the clipping plane
** 
**     D1 = P1 dot C
**     D1 is the distance between P1 and C.  If P1 lies on the plane
**     then D1 will be zero.  The sign of D1 will determine which side
**     of the plane that P1 is on, with negative being outside.
** 
**     D2 = P2 dot C
**     D2 is the distance between P2 and C.  If P2 lies on the plane
**     then D2 will be zero.  The sign of D2 will determine which side
**     of the plane that P2 is on, with negative being outside.
** 
** Because we are trying to find the intersection of the P1 P2 line
** segment with the clipping plane we require that:
** 
**     Q dot C = 0
** 
** Therefore
** 
**     (t*P1 + (1 - t)*P2) dot C = 0
** 
**     (t*P1 + P2 - t*P2) dot C = 0
** 
**     t*P1 dot C + P2 dot C - t*P2 dot C = 0
** 
** Substituting D1 and D2 in
** 
**     t*D1 + D2 - t*D2 = 0
** 
** Solving for t
** 
**     t = -D2 / (D1 - D2)
** 
**     t = D2 / (D2 - D1)
*/

/*
** Clip a line against the frustum clip planes and any user clipping planes.
** If an edge remains after clipping then compute the window coordinates
** and invoke the renderer.
**
** Notice:  This algorithim is an example of an implementation that is
** different than what the spec says.  This is equivalent in functionality
** and meets the spec, but doesn't clip in eye space.  This clipper clips
** in NTVP (clip) space.
**
** Trivial accept/reject has already been dealt with.
*/
#ifdef NT
void FASTCALL __glClipLine(__GLcontext *gc, __GLvertex *a, __GLvertex *b,
                           GLuint flags)
#else
void __glClipLine(__GLcontext *gc, __GLvertex *a, __GLvertex *b)
#endif
{
#ifdef NT
    __GLvertex *provokingA = a;
    __GLvertex *provokingB = b;
#else
    __GLvertex *provoking = b;
#endif
    __GLvertex np1, np2;
    __GLcoord *plane;
    GLuint needs, allClipCodes, clipCodes;
    PFN_VERTEX_CLIP_PROC clip;
    __GLfloat zero;
    __GLfloat winx, winy;
    __GLfloat vpXCenter, vpYCenter, vpZCenter;
    __GLfloat vpXScale, vpYScale, vpZScale;
    __GLviewport *vp;
    __GLfloat x, y, z, wInv;
    GLint i;

    // We have to turn rounding on.  Otherwise, the fast FP-comparison
    // routines below can fail:
    FPU_SAVE_MODE();
    FPU_ROUND_ON_PREC_HI();

    /* Check for trivial pass of the line */
    allClipCodes = a->clipCode | b->clipCode;

    /*
    ** For each clippling plane that something is out on, clip
    ** check the verticies.  Note that no bits will be set in
    ** allClipCodes for clip planes that are not enabled.
    */
    zero = __glZero;
    clip = gc->procs.lineClipParam;

    /* 
    ** Do user clip planes first, because we will maintain eye coordinates
    ** only while doing user clip planes.  They are ignored for the
    ** frustum clipping planes.
    */
    clipCodes = allClipCodes >> 6;
    if (clipCodes) {
	plane = &gc->state.transform.eyeClipPlanes[0];
	do {
	    /*
	    ** See if this clip plane has anything out of it.  If not,
	    ** press onward to check the next plane.  Note that we
	    ** shift this mask to the right at the bottom of the loop.
	    */
	    if (clipCodes & 1) {
		__GLfloat t, d1, d2;

		d1 = (plane->x * ((POLYDATA *)a)->eye.x) +
		     (plane->y * ((POLYDATA *)a)->eye.y) +
		     (plane->z * ((POLYDATA *)a)->eye.z) +
		     (plane->w * ((POLYDATA *)a)->eye.w);
		d2 = (plane->x * ((POLYDATA *)b)->eye.x) +
		     (plane->y * ((POLYDATA *)b)->eye.y) +
		     (plane->z * ((POLYDATA *)b)->eye.z) +
		     (plane->w * ((POLYDATA *)b)->eye.w);
		if (__GL_FLOAT_LTZ(d1)) {
		    /* a is out */
		    if (__GL_FLOAT_LTZ(d2)) {
			/* a & b are out */
                        FPU_RESTORE_MODE();
			return;
		    }

		    /*
		    ** A is out and B is in.  Compute new A coordinate
		    ** clipped to the plane.
		    */
		    t = d2 / (d2 - d1);
		    (*clip)(&np1, a, b, t);
		    ((POLYDATA *)&np1)->eye.x =
			t*(((POLYDATA *)a)->eye.x - ((POLYDATA *)b)->eye.x) +
			((POLYDATA *)b)->eye.x;
		    ((POLYDATA *)&np1)->eye.y =
			t*(((POLYDATA *)a)->eye.y - ((POLYDATA *)b)->eye.y) +
			((POLYDATA *)b)->eye.y;
		    ((POLYDATA *)&np1)->eye.z =
			t*(((POLYDATA *)a)->eye.z - ((POLYDATA *)b)->eye.z) +
			((POLYDATA *)b)->eye.z;
		    ((POLYDATA *)&np1)->eye.w =
			t*(((POLYDATA *)a)->eye.w - ((POLYDATA *)b)->eye.w) +
			((POLYDATA *)b)->eye.w;
		    a = &np1;
		    a->has = b->has;
		    ASSERTOPENGL(!(a->has & __GL_HAS_FIXEDPT), "clear __GL_HAS_FIXEDPT flag!\n");
		} else {
		    /* a is in */
		    if (__GL_FLOAT_LTZ(d2)) {
			/*
			** A is in and B is out.  Compute new B
			** coordinate clipped to the plane.
			**
			** NOTE: To avoid cracking in polygons with
			** shared clipped edges we always compute "t"
			** from the out vertex to the in vertex.  The
			** above clipping code gets this for free (b is
			** in and a is out).  In this code b is out and a
			** is in, so we reverse the t computation and the
			** argument order to (*clip).
			*/
			t = d1 / (d1 - d2);
			(*clip)(&np2, b, a, t);
			((POLYDATA *)&np2)->eye.x =
			    t*(((POLYDATA *)b)->eye.x - ((POLYDATA *)a)->eye.x)+
			    ((POLYDATA *)a)->eye.x;
			((POLYDATA *)&np2)->eye.y =
			    t*(((POLYDATA *)b)->eye.y - ((POLYDATA *)a)->eye.y)+
			    ((POLYDATA *)a)->eye.y;
			((POLYDATA *)&np2)->eye.z =
			    t*(((POLYDATA *)b)->eye.z - ((POLYDATA *)a)->eye.z)+
			    ((POLYDATA *)a)->eye.z;
			((POLYDATA *)&np2)->eye.w =
			    t*(((POLYDATA *)b)->eye.w - ((POLYDATA *)a)->eye.w)+
			    ((POLYDATA *)a)->eye.w;
			b = &np2;
			b->has = a->has;
			ASSERTOPENGL(!(b->has & __GL_HAS_FIXEDPT), "clear __GL_HAS_FIXEDPT flag!\n");
		    } else {
			/* A and B are in */
		    }
		}
	    }
	    plane++;
	    clipCodes >>= 1;
	} while (clipCodes);
    }

    allClipCodes &= __GL_FRUSTUM_CLIP_MASK;
    if (allClipCodes) {
	i = 0;
	do {
	    /*
	    ** See if this clip plane has anything out of it.  If not,
	    ** press onward to check the next plane.  Note that we
	    ** shift this mask to the right at the bottom of the loop.
	    */
	    if (allClipCodes & 1) {
		__GLfloat t, d1, d2;

                if (i & 1)
                {
                    d1 = a->clip.w -
                        *(__GLfloat *)((GLubyte *)a + __glFrustumOffsets[i]);
                    d2 = b->clip.w -
                        *(__GLfloat *)((GLubyte *)b + __glFrustumOffsets[i]);
                }
                else
                {
                    d1 = *(__GLfloat *)((GLubyte *)a + __glFrustumOffsets[i]) +
                        a->clip.w;
                    d2 = *(__GLfloat *)((GLubyte *)b + __glFrustumOffsets[i]) +
                        b->clip.w;
                }

		if (__GL_FLOAT_LTZ(d1)) {
		    /* a is out */
		    if (__GL_FLOAT_LTZ(d2)) {
			/* a & b are out */
                        FPU_RESTORE_MODE();
			return;
		    }

		    /*
		    ** A is out and B is in.  Compute new A coordinate
		    ** clipped to the plane.
		    */
		    t = d2 / (d2 - d1);
		    (*clip)(&np1, a, b, t);
		    a = &np1;
		    a->has = b->has;
		    ASSERTOPENGL(!(a->has & __GL_HAS_FIXEDPT), "clear __GL_HAS_FIXEDPT flag!\n");
		} else {
		    /* a is in */
		    if (__GL_FLOAT_LTZ(d2)) {
			/*
			** A is in and B is out.  Compute new B
			** coordinate clipped to the plane.
			**
			** NOTE: To avoid cracking in polygons with
			** shared clipped edges we always compute "t"
			** from the out vertex to the in vertex.  The
			** above clipping code gets this for free (b is
			** in and a is out).  In this code b is out and a
			** is in, so we reverse the t computation and the
			** argument order to (*clip).
			*/
			t = d1 / (d1 - d2);
			(*clip)(&np2, b, a, t);
			b = &np2;
			b->has = a->has;
			ASSERTOPENGL(!(b->has & __GL_HAS_FIXEDPT), "clear __GL_HAS_FIXEDPT flag!\n");
		    } else {
			/* A and B are in */
		    }
		}
	    }
            i++;
	    allClipCodes >>= 1;
	} while (allClipCodes);
    }

    vp = &gc->state.viewport;
    vpXCenter = vp->xCenter;
    vpYCenter = vp->yCenter;
    vpZCenter = vp->zCenter;
    vpXScale = vp->xScale;
    vpYScale = vp->yScale;
    vpZScale = vp->zScale;

    /* Compute window coordinates for vertices generated by clipping */
    if (provokingA->clipCode != 0)
    {
        wInv = __glOne / a->clip.w;
        x = a->clip.x; 
        y = a->clip.y; 
        z = a->clip.z;
        winx = x * vpXScale * wInv + vpXCenter;
        winy = y * vpYScale * wInv + vpYCenter;

        if (winx < gc->transform.fminx)
            winx = gc->transform.fminx;
        else if (winx >= gc->transform.fmaxx)
            winx = gc->transform.fmaxx - gc->constants.viewportEpsilon;

        if (winy < gc->transform.fminy)
            winy = gc->transform.fminy;
        else if (winy >= gc->transform.fmaxy)
            winy = gc->transform.fmaxy - gc->constants.viewportEpsilon;

        a->window.z = z * vpZScale * wInv + vpZCenter;
        a->window.w = wInv;
        a->window.x = winx;
        a->window.y = winy;

        // Update color pointer since this vertex is a new one
        // generated by clipping
        if (gc->state.light.shadingModel == GL_FLAT)
        {
            a->color = &provokingA->colors[__GL_FRONTFACE];
        }
        else
        {
            a->color = &a->colors[__GL_FRONTFACE];
        }
    }

    if (provokingB->clipCode != 0)
    {
        wInv = __glOne / b->clip.w;
        x = b->clip.x; 
        y = b->clip.y; 
        z = b->clip.z;
        winx = x * vpXScale * wInv + vpXCenter;
        winy = y * vpYScale * wInv + vpYCenter;

        if (winx < gc->transform.fminx)
            winx = gc->transform.fminx;
        else if (winx >= gc->transform.fmaxx)
            winx = gc->transform.fmaxx - gc->constants.viewportEpsilon;

        if (winy < gc->transform.fminy)
            winy = gc->transform.fminy;
        else if (winy >= gc->transform.fmaxy)
            winy = gc->transform.fmaxy - gc->constants.viewportEpsilon;

        b->window.z = z * vpZScale * wInv + vpZCenter;
        b->window.w = wInv;
        b->window.x = winx;
        b->window.y = winy;
        
        if (gc->state.light.shadingModel == GL_FLAT)
        {
            b->color = &provokingB->colors[__GL_FRONTFACE];
        }
        else
        {
            b->color = &b->colors[__GL_FRONTFACE];
        }
    }

    // Restore the floating-point mode for rendering:
    FPU_RESTORE_MODE();

    /* Validate line state */
    if (gc->state.light.shadingModel == GL_FLAT) {
	// Add the vertices then restore the b color pointer
        //
        // Note that although b is the only new vertex, up
        // to two vertices can be added because each new vertex
        // generated by clipping must be added.  For a line where
        // both endpoints are out of the clipping region, both
        // an entry and an exit vertex must be added
        if (provokingA->clipCode != 0)
        {
            // a was out so a new vertex was added at the point of
            // entry
            flags |= __GL_LVERT_FIRST;
        }
        // b is always added since either:
        // b was in and is new so it needs to be added
        // b was out so a new vertex was added at the exit point
        (*gc->procs.renderLine)(gc, a, b, flags);
        
#ifndef NT
	b->color = &b->colors[__GL_FRONTFACE];
#endif
    } else {
        if (provokingA->clipCode != 0)
        {
            flags |= __GL_LVERT_FIRST;
        }
        (*gc->procs.renderLine)(gc, a, b, flags);
    }
}