Source/SR_ClipUtilities.c

/******************************************************************************
 **                                                                          **
 **     Module:     SR_ClipUtilities.c                                       **
 **                                                                          **
 **                                                                          **
 **     Purpose:    Clipping utilities for Sample Renderer                   **
 **                                                                          **
 **                                                                          **
 **                                                                          **
 **     Copyright (C) 1996-1997 Apple Computer, Inc.  All rights reserved.   **
 **                                                                          **
 **                                                                          **
 *****************************************************************************/
#include "QD3D.h"
#include "QD3DMath.h"
 
#include "SR.h"
#include "SR_ClipUtilities.h"
 
#define TRUNC(X)                (float)((long)(X))
#define SUBPIXEL_SNAP           ((float) 64.0)  
#define SUBPIXEL_SNAP_INVERSE   ((float) 1.0 / (float) 64.0)
 
 
/*===========================================================================*\
 *
 *  Routine:    SRPointList_WDivide()
 *
 *  Comments:   
 *
\*===========================================================================*/
 
void SRPointList_WDivide(
    TQ3RationalPoint4D      *in, 
    unsigned long           numVertices, 
    unsigned long           sizeOfIn)
{
    float                   w;
    long                    i;
    TQ3RationalPoint4D      *s = in;
    long                    ci;
    float                   cf;
    float                   c;
    float                   wInv;
 
    for (i = 0; i < numVertices; i++) {
        w = s->w;
        
        if (s->w != 0.0) {
            wInv = 1.0 / w;
        } else {
            wInv = kQ3MaxFloat;
        }
 
        c = s->x * wInv;
        ci = FLOAT_TRUNC_TO_LONG(c);
        cf = c - (float)ci;
        
        cf = FLOAT_ROUND_TO_LONG_POSITIVE(cf * SUBPIXEL_SNAP);
        cf = cf * SUBPIXEL_SNAP_INVERSE;
 
        s->x = (float)ci + cf;
 
        c = s->y * wInv;
        ci = FLOAT_TRUNC_TO_LONG(c);
        cf = c - (float)ci;
        
        cf = FLOAT_ROUND_TO_LONG_POSITIVE(cf * SUBPIXEL_SNAP);
        cf = cf * SUBPIXEL_SNAP_INVERSE;
 
        s->y = (float)ci + cf;
 
        s->z *= wInv;
 
        s = (TQ3RationalPoint4D *)((unsigned char *)s + sizeOfIn);
 
    }
}
 
 
/*===========================================================================*\
 *
 *  Routine:    SRPointList_ClipTestVertices()
 *
 *  Comments:   Check if we have any clipped-out vertices
 *
\*===========================================================================*/
 
void SRPointList_ClipTestVertices(
    TQ3RationalPoint4D      *deviceVertices, 
    unsigned long           *clipFlags, 
    long                    numVertices, 
    float                   *clipPlanes, 
    long                    *clipFound, 
    long                    *allOut, 
    unsigned long           sizeOfIn)
{
    long                i;
    float               x, y, z, w;
    long                clipFlag;
    TQ3RationalPoint4D  *s;
    float               xMin, xMax, 
                        yMin, yMax, 
                        zMin, zMax;
 
    s = deviceVertices;
 
    xMin = clipPlanes[0];
    xMax = clipPlanes[1];
    yMin = clipPlanes[2];
    yMax = clipPlanes[3];
    zMin = clipPlanes[4];
    zMax = clipPlanes[5];
 
    *clipFound = *allOut = 0;
    
    i = 0;
    while (i < numVertices) {
        clipFlag = 0;
 
        x = s->x;
        y = s->y;
        z = s->z;
        w = s->w;
 
        /*
         *  The view clip planes are defined by xmin, xmax, ymin, ymax, 
         *  zmin, zmax. Clearly these planes are perpendicular to the 
         *  appropriate axis.  For example, xmin is perpendicular to the YZ 
         *  plane and defines the minimum x coordinate for clipping.
         *
         *  In this clip test, we allow the clipping planes to be any
         *  value.  They are not just limited to  -1,1 like in some
         *  clipping environments.  The only stipulation is that the
         *  min actually be less than the max for a plane combination.
         *
         *  The same set of tests is applied consistently to all point 
         *  regardless of the sign of w.  Normally, only the geometry in front
         *  of the camera is desired so the tests for the positive- w space 
         *  should be applied consistently.  Sometimes, the geometry in the 
         *  negative w space is desired too.  Then all the points should be 
         *  retraversed with the negative-w tests applied to all points.
         */
        if (x < xMin * w) {
            clipFlag |= SR_MIN_X;
        }
        if (x > xMax * w) {
            clipFlag |= SR_MAX_X;
        }
 
        if (y < yMin * w) {
            clipFlag |= SR_MIN_Y;
        }
        if (y > yMax * w) {
            clipFlag |= SR_MAX_Y;
        }
 
        if (z - w * zMin < 0.0) {
            clipFlag |= SR_MIN_Z;
        } else if (z - w * zMax > 0.0) {
            clipFlag |= SR_MAX_Z;
        }
 
        /* 
         *  All vertices are out if & of all points is not 0 
         */
        if (i == 0) {
            *allOut = clipFlag;
        } else {
            *allOut &= clipFlag;
        }
 
        *clipFound |= clipFlag;
 
        /* 
         *  Vertices are clipped if | of all clip flags is 0 
         */
        clipFlags[i] = clipFlag;
    
        s = (TQ3RationalPoint4D *)((unsigned char *)s + sizeOfIn);
 
        i++;
    }
}
 
#define OUTPUT_POINT(DEST, SRC_POINT)                           \
{                                                               \
    double  w;                                                  \
                                                                \
    w = SRC_POINT->w;                                           \
                                                                \
    DEST->x = (double)SRC_POINT->x / w;                         \
    DEST->y = (double)SRC_POINT->y / w;                         \
    DEST->z = (double)SRC_POINT->z / w;                         \
    DEST->w = 1.0;                                              \
}
 
#define OUTPUT_POINT_INTERPOLATE(DEST, PREV, CURR, PARAM)       \
{                                                               \
    double  w;                                                  \
    double  oneMinusPARAM;                                      \
                                                                \
    oneMinusPARAM = (double)1.0 - (double)PARAM;                \
                                                                \
    w = oneMinusPARAM * (double)PREV->w + (double)PARAM * (double)CURR->w;              \
                                                                                        \
    DEST->x = (oneMinusPARAM * (double)PREV->x + (double)PARAM * (double)CURR->x) / w;  \
    DEST->y = (oneMinusPARAM * (double)PREV->y + (double)PARAM * (double)CURR->y) / w;  \
    DEST->z = (oneMinusPARAM * (double)PREV->z + (double)PARAM * (double)CURR->z) / w;  \
    DEST->w = 1.0;                                              \
}
 
#define COMPUTE_PLANE_INTERSECTIONS(VALUES, COORDS, CLIP_PLANES)            \
{                                                                           \
    int     j;                                                              \
    double  w = COORDS[3];                                                  \
                                                                            \
    for (j = 0; j < 6; j++) {                                               \
        /* For each clip plane; compute parametric intersection. */         \
        VALUES[j] = (double)COORDS[j >> 1] - w * (double)CLIP_PLANES[j];    \
    }                                                                       \
}
 
 
#define BUMP_DEST_POINTERS                                                  \
    clippedVertices =(TQ3RationalPoint4D *)((unsigned char *)clippedVertices\
        + sizeOfClippedVertex);
 
 
 
/*===========================================================================*\
 *
 *  Routine:    SRPointList_ClipVertices()
 *
 *  Comments:   Given a set of vertices, compute a clipped set.
 *
\*===========================================================================*/
 
void SRPointList_ClipVertices(  
    TQ3RationalPoint4D      *vertices,
    long                    sizeOfVertex,
    TQ3RationalPoint4D      *clippedVertices, 
    long                    sizeOfClippedVertex,
    unsigned long           *clipFlags, 
    long                    *clippedVertexFlags,
    long                    sourceNumberOfPoints, 
    long                    *destNumberOfPoints,
    float                   *clipPlanes,
    long                    mode)
{
    long                    i, j;
    float                   *coords;
    TQ3RationalPoint4D      *sPrevious, 
                            *sCurrent;
    double                  previousValues[6], 
                            currentValues[6];
    double                  prevParametricValue, 
                            currParametricValue;
    double                  parametricValue;
    long                    lineClipped;
    
    (*destNumberOfPoints) = 0;
 
    sPrevious = vertices;
 
    /* if first point not clipped then output it */
    if ((clipFlags[0] & SR_CLIP_ALL) == 0) {
        OUTPUT_POINT(clippedVertices, sPrevious);
        clippedVertexFlags[*destNumberOfPoints] = SR_DRAW_NEXT;
        (*destNumberOfPoints)++;
        BUMP_DEST_POINTERS;
    }
 
    sCurrent = 
        (TQ3RationalPoint4D *)((unsigned char *)sPrevious + sizeOfVertex);
 
    for (i = 1; i < sourceNumberOfPoints; i++) {
        if ((clipFlags[i - 1] & SR_CLIP_ALL) & (clipFlags[i] & SR_CLIP_ALL)) {
            /*  
             *  Previous and current are outside; do nothing 
             */
        } else if (!((clipFlags[i - 1] & SR_CLIP_ALL)   | 
                     (clipFlags[i]     & SR_CLIP_ALL))) {
            /*
             *  Previous and current vertex are inside turn
             *  on draw vector bit and output current vertex
             */
            OUTPUT_POINT(clippedVertices, sCurrent);
            clippedVertexFlags[*destNumberOfPoints] = SR_DRAW_NEXT;
            (*destNumberOfPoints)++;
            BUMP_DEST_POINTERS;
        } else {
            /*
             * For each clip plane; compute parametric clip with
             * previous and current point.
             */
    
            /* point to current XYZW */
            coords = (float *)&sPrevious->x;
            COMPUTE_PLANE_INTERSECTIONS(previousValues, coords, clipPlanes);
 
            /* point to current XYZW */
            coords = (float *)&sCurrent->x;
            COMPUTE_PLANE_INTERSECTIONS(currentValues, coords, clipPlanes);
 
            prevParametricValue = 0.0;
            currParametricValue = 1.0;
 
            lineClipped = 1;
 
            for (j = 0; j < 6; j++) {
                /*
                 *  If previous flag and current differ then we cross
                 *  a clip boundary and we must compute the intersection.
                 */
                if (lineClipped) {
                    if (((clipFlags[i - 1] ^ clipFlags[i]) >> j) & 1) {
 
                        parametricValue = 
                            previousValues[j] / 
                                (previousValues[j] - currentValues[j]);
 
                        if ((clipFlags[i] >> j) & 1) {
                            if (currParametricValue > parametricValue) {
                                currParametricValue = parametricValue;
                            }
                        } else {
                            if (prevParametricValue < parametricValue) {
                                prevParametricValue = parametricValue;
                            }
                        }
                    } else {
                        lineClipped = !((clipFlags[i] >> j) & 1);
                    }
 
                    lineClipped = 
                        lineClipped & 
                            (prevParametricValue < currParametricValue);
                }
            }
 
            if (lineClipped) {
                /*
                 *  Compute clip for previous vertex
                 */
                if (prevParametricValue > 0.0) {
                    OUTPUT_POINT_INTERPOLATE(
                        clippedVertices, 
                        sPrevious, 
                        sCurrent, 
                        prevParametricValue);
                    /*
                     *  Point is clipped on the way into the visible region
                     *  so set draw bit
                     */
                    clippedVertexFlags[*destNumberOfPoints] = SR_DRAW_NEXT;
                    (*destNumberOfPoints)++;
                    BUMP_DEST_POINTERS;
                }
 
                /*
                 *  Compute clip for current vertex
                 */
                if (currParametricValue < 1.0) {
                    OUTPUT_POINT_INTERPOLATE(
                        clippedVertices, 
                        sPrevious, 
                        sCurrent, 
                        currParametricValue);
                    /*
                     *  Point is clipped on the way out of the visible region
                     *  so turn off draw bit
                     */
                    clippedVertexFlags[*destNumberOfPoints] &= ~SR_DRAW_NEXT;
                    (*destNumberOfPoints)++;
                        
                    BUMP_DEST_POINTERS;
                } else {
                    OUTPUT_POINT(clippedVertices, sCurrent);
 
                    /*
                     *  Point is inside visible region so turn on draw bit
                     */
                    clippedVertexFlags[*destNumberOfPoints] = SR_DRAW_NEXT;
                    (*destNumberOfPoints)++;
                    BUMP_DEST_POINTERS;
                }
            }
        }
 
        /*
         *  Update previous and current pointers
         */
        sPrevious = sCurrent;
        sCurrent = (TQ3RationalPoint4D *)
                        ((unsigned char *)sPrevious + sizeOfVertex);
    }
 
    /*
     *  If we have fewer than 3 vertices then don't clip from the last to the
     *  first vertex since that doesn't make any sense.
     */
    if (sourceNumberOfPoints < 3) {
        return;
    }
 
    /*
     *  'mode' is either polygon or polyline.  When mode is polygon (0) then 
     *  we must clip from the last vertex to the first vertex.  When mode is 
     *  polyline (1) then we don't do this clip.
     */
    if (mode == DO_POLYLINE) {
        return;
    }
 
    /*
     *  If first vertex is in then do nothing.
     *  If first vertex is clipped and last vertex is displayed then compute
     *  another clip point from last vertex to first
     */
    if (!((clipFlags[0] & SR_CLIP_ALL) | 
        (clipFlags[sourceNumberOfPoints - 1] & SR_CLIP_ALL))) {
        /* 
         *  First and last are inside; do nothing since
         *  draw bit of last should be set
         */
    } else {
        sPrevious = &vertices[sourceNumberOfPoints - 1];
        
        coords = (float *)&sPrevious->x;
        COMPUTE_PLANE_INTERSECTIONS(previousValues, coords, clipPlanes);
 
        sCurrent = vertices;
 
        coords = (float *)&sCurrent->x;
        COMPUTE_PLANE_INTERSECTIONS(currentValues, coords, clipPlanes);
 
        /* compute clip from last vertex to first vertex */
        prevParametricValue = 0.0;
        currParametricValue = 1.0;
 
        lineClipped = 1;
 
        for (j = 0; j < 6; j++) {
            /*
             *  If previous flag and current differ then we cross
             *  a clip boundary and we must compute the intersection.
             */
            if (lineClipped) {
                if (((clipFlags[sourceNumberOfPoints - 1] ^ clipFlags[0]) >> j) & 1) {
                    parametricValue = previousValues[j] / 
                                        (previousValues[j] - currentValues[j]);
 
                    if ((clipFlags[0] >> j) & 1) {
                        if (currParametricValue > parametricValue) {
                            currParametricValue = parametricValue;
                        }
                    } else {
                        if (prevParametricValue < parametricValue) {
                            prevParametricValue = parametricValue;
                        }
                    }
                } else {
                    lineClipped = !((clipFlags[0] >> j) & 1);
                }
 
                lineClipped = lineClipped & 
                                (prevParametricValue < currParametricValue);
            }
        }
 
        if (lineClipped) {
            /*
             *  Compute clip for previous vertex
             */
            if (prevParametricValue > 0.0) {
                OUTPUT_POINT_INTERPOLATE(
                    clippedVertices, 
                    sPrevious, 
                    sCurrent, 
                    prevParametricValue);
                clippedVertexFlags[*destNumberOfPoints] = SR_DRAW_NEXT;
                (*destNumberOfPoints)++;
                BUMP_DEST_POINTERS;
            }
 
            /*
             *  Compute clip for current vertex
             */
            if (currParametricValue < 1.0) {
                OUTPUT_POINT_INTERPOLATE(
                    clippedVertices, 
                    sPrevious, 
                    sCurrent, 
                    currParametricValue);
                clippedVertexFlags[*destNumberOfPoints] &= ~SR_DRAW_NEXT;
                (*destNumberOfPoints)++;
                BUMP_DEST_POINTERS;
            } else {
                OUTPUT_POINT(clippedVertices, sCurrent);
 
                /*
                 *  Point is clipped so compute last vertex and don't 
                 *  display it
                 */
                clippedVertexFlags[*destNumberOfPoints] = SR_DRAW_NEXT;
                (*destNumberOfPoints)++;
                BUMP_DEST_POINTERS;
            }
        }
    }
}