Source/SR_PipelineSetup.c

/******************************************************************************
 **                                                                          **
 **     Module:     SR_PipelineSetup.c                                       **
 **                                                                          **
 **                                                                          **
 **     Purpose:    Sample Renderer setup routines, etc.                     **
 **                                                                          **
 **                                                                          **
 **                                                                          **
 **     Copyright (C) 1996 Apple Computer, Inc.  All rights reserved.        **
 **                                                                          **
 **                                                                          **
 *****************************************************************************/
#include <assert.h>
 
#include "QD3D.h"
#include "QD3DMath.h"
#include "QD3DCamera.h"
#include "QD3DView.h"
#include "QD3DTransform.h"
#include "QD3DRenderer.h"
#include "QD3DShader.h"
 
#include "SR.h"
#include "SR_Math.h"
 
/******************************************************************************
 **                                                                          **
 **                             Setup Routines                               **
 **                                                                          **
 *****************************************************************************/
 
/*===========================================================================*\
 *
 *  Routine:    SR_ClipPlanesInDC()
 *
 *  Comments:   Compute clipping planes in device coordinates
 *
\*===========================================================================*/
 
static void SR_ClipPlanesInDC(
    TQ3Matrix4x4    *frustumToDC, 
    float           *clipPlanesInDC)
{
    TQ3Point3D          clipPlanesInFrustum[2];
    TQ3RationalPoint4D  clipPlanesInDevCoords[2];
    
    assert(frustumToDC      != NULL);
    assert(clipPlanesInDC   != NULL);
 
    clipPlanesInFrustum[0].x = -1.0;    /* xmin */
    clipPlanesInFrustum[0].y = -1.0;    /* ymin */
    clipPlanesInFrustum[0].z = -1.0;    /* zmin */
    
    clipPlanesInFrustum[1].x =  1.0;    /* xmax */
    clipPlanesInFrustum[1].y =  1.0;    /* ymax */
    clipPlanesInFrustum[1].z =  0.0;    /* zmax */
 
    Q3Point3D_To4DTransformArray(
        (TQ3Point3D *)&clipPlanesInFrustum[0],
        frustumToDC, 
        &clipPlanesInDevCoords[0],
        2, 
        sizeof(TQ3Point3D), 
        sizeof(TQ3RationalPoint4D));
 
    /*
     *  xmin, xmax
     */
    clipPlanesInDC[0] = FLOAT_ROUND_TO_LONG(clipPlanesInDevCoords[0].x);
    clipPlanesInDC[1] = FLOAT_ROUND_TO_LONG(clipPlanesInDevCoords[1].x - 1.0);
 
    /*
     *  ymin, ymax
     */
    clipPlanesInDC[2] = FLOAT_ROUND_TO_LONG(clipPlanesInDevCoords[1].y);
    clipPlanesInDC[3] = FLOAT_ROUND_TO_LONG(clipPlanesInDevCoords[0].y - 1.0);
 
    /*
     *  zmin, zmax
     */
    clipPlanesInDC[4] = clipPlanesInDevCoords[0].z;
    clipPlanesInDC[5] = clipPlanesInDevCoords[1].z;
}
 
 
/******************************************************************************
 **                                                                          **
 **                             Attributes Update                            **
 **                                                                          **
 *****************************************************************************/
 
/*===========================================================================*\
 *
 *  Routine:    SR_Update_DiffuseColor()
 *
 *  Comments:   Tracks changes to diffuse color state in the view
 *
\*===========================================================================*/
 
TQ3Status SR_Update_DiffuseColor(
    TQ3ViewObject           view,
    TSRPrivate              *srPrivate,
    TQ3ColorRGB             *diffuseColor)
{
    UNUSED(view);
    
    assert(view         != NULL);
    assert(srPrivate    != NULL);
    assert(diffuseColor != NULL);
 
    srPrivate->viewDiffuseColor = *diffuseColor;
    
    return (kQ3Success);
}
 
 
/*===========================================================================*\
 *
 *  Routine:    SR_Update_HighlightState()
 *
 *  Comments:   Tracks changes to highlight state in the view
 *
\*===========================================================================*/
 
TQ3Status SR_Update_HighlightState(
    TQ3ViewObject           view,
    TSRPrivate              *srPrivate,
    TQ3Boolean              *highlightState)
{
    UNUSED(view);
    
    assert(view         != NULL);
    assert(srPrivate    != NULL);
 
    srPrivate->viewHighlightState = 
        (highlightState == NULL) ? kQ3False : *highlightState;
 
    return (kQ3Success);
}
 
 
/******************************************************************************
 **                                                                          **
 **                                 Styles                                   **
 **                                                                          **
 *****************************************************************************/
 
/*===========================================================================*\
 *
 *  Routine:    SR_Update_BackfacingStyle()
 *
 *  Comments:   Tracks changes to backfacing style state in the view
 *
\*===========================================================================*/
 
TQ3Status SR_Update_BackfacingStyle(
    TQ3ViewObject           view,
    TSRPrivate              *srPrivate,
    TQ3BackfacingStyle      *backfacingStyle)
{
    UNUSED(view);
    
    assert(view             != NULL);
    assert(srPrivate        != NULL);
    assert(backfacingStyle  != NULL);
        
    srPrivate->backfacingStyle = *backfacingStyle;  
    
    return (kQ3Success);
}
 
 
/*===========================================================================*\
 *
 *  Routine:    SR_Update_OrientationStyle()
 *
 *  Comments:   Tracks changes to orientation style state in the view
 *
\*===========================================================================*/
 
TQ3Status SR_Update_OrientationStyle(
    TQ3ViewObject           view,
    TSRPrivate              *srPrivate,
    TQ3OrientationStyle     *orientationStyle)
{
    UNUSED(view);
    
    assert(view             != NULL);
    assert(srPrivate        != NULL);
    assert(orientationStyle != NULL);
        
    srPrivate->orientationStyle = *orientationStyle;
    
    return (kQ3Success);
}
 
 
/*===========================================================================*\
 *
 *  Routine:    SR_Update_FillStyle()
 *
 *  Comments:   Tracks changes to fill style state in the view
 *
\*===========================================================================*/
 
TQ3Status SR_Update_FillStyle(
    TQ3ViewObject           view,
    TSRPrivate              *srPrivate,
    TQ3FillStyle            *fillStyle)
{
    UNUSED(view);
    
    assert(view         != NULL);
    assert(srPrivate    != NULL);
    assert(fillStyle    != NULL);
    
    srPrivate->viewFillStyle = *fillStyle;
    
    return (kQ3Success);
}
 
 
/*===========================================================================*\
 *
 *  Routine:    SR_Update_HighlightStyle()
 *
 *  Comments:   Tracks changes to highlight style state in the view
 *
\*===========================================================================*/
 
TQ3Status SR_Update_HighlightStyle(
    TQ3ViewObject           view,
    TSRPrivate              *srPrivate,
    TQ3AttributeSet         *highlightAttributeSet)
{
    UNUSED(view);
    
    assert(view         != NULL);
    assert(srPrivate    != NULL);
 
    /*
     *  Bump reference count on incoming attribute set, if any 
     */
    if (*highlightAttributeSet != NULL) {
        Q3Shared_GetReference(*highlightAttributeSet);
    }
    
    /*
     *  Get rid of existing highlight attribute set
     */
    if (srPrivate->viewHighlightAttributeSet != NULL) {
        Q3Object_Dispose(srPrivate->viewHighlightAttributeSet);
    }
    
    /*
     *  Set the highlight attribute set
     */
    srPrivate->viewHighlightAttributeSet = *highlightAttributeSet;
        
    return (kQ3Success);
}
 
 
/*===========================================================================*\
 *
 *  Routine:    SR_UpdateNonInvertibleMatrix()
 *
 *  Comments:   Deal with noninvertible local-to-world matrices
 *
\*===========================================================================*/
 
static void SR_UpdateNonInvertibleMatrix(
    TSRPrivate          *srPrivate,
    TQ3Boolean          parallelProjection)
{
    TQ3Matrix3x3    submatrix;
    long            indexPtr[3];
    float           rowInterchanges;
    long            rank;
 
    /* Singular */
    
    /* Copy upper-left 3x3 submatrix to temporary array */
    submatrix.value[0][0]   = srPrivate->transforms.localToWorld.value[0][0];
    submatrix.value[0][1]   = srPrivate->transforms.localToWorld.value[0][1];
    submatrix.value[0][2]   = srPrivate->transforms.localToWorld.value[0][2];
    submatrix.value[1][0]   = srPrivate->transforms.localToWorld.value[1][0];
    submatrix.value[1][1]   = srPrivate->transforms.localToWorld.value[1][1];
    submatrix.value[1][2]   = srPrivate->transforms.localToWorld.value[1][2];
    submatrix.value[2][0]   = srPrivate->transforms.localToWorld.value[2][0];
    submatrix.value[2][1]   = srPrivate->transforms.localToWorld.value[2][1];
    submatrix.value[2][2]   = srPrivate->transforms.localToWorld.value[2][2];
 
    /* 
     *  Decompose singular matrix into lower-triangular/upper-echelon form 
     */
    SRMatrix_LUDecomposeSingular3x3(
        submatrix.value,
        indexPtr, 
        &rowInterchanges,
        &rank);
 
    srPrivate->normalLocalToWorldRank = rank;
 
    if ((rank == 2) &&
        (SRMatrix_ComputeFlatLand(
            &srPrivate->transforms.localToWorld,
            &submatrix,
            parallelProjection,
            &srPrivate->eyeVectorInWorldCoords,
            &srPrivate->eyePointInWorldCoords,
            &srPrivate->eyeVectorInLocalCoords,
            &srPrivate->flatWorld) == kQ3Success)) {
        return;
    }
 
    /*
     *  Fill in some arbitrary values if failure, or rank is 0, 1, or 3
     *
     *  rank = 0
     *
     *  All geometry is transformed to a single point.  Lighting and 
     *  culling have no meaning so choose any arbitrary eye vector.
     *  Renderers should render nothing, but let's set the values
     *  to something in case they try to use them.
     *
     *  rank = 0
     *
     *  All geometry is transformed to a line.   Lighting and
     *  culling have no meaning so choose any arbitrary eye vector.
     *  Renderers should render nothing, but let's set the values
     *  to something in case they try to use them.
     *
     *  rank = 1
     *
     *  TODO:
     *  The 4x4 matrix is singular, but the 3x3 upper-left submatrix
     *  has rank 3 so the element[3][3] is zero.  This means that
     *  all geometry is transformed to infinity.  This is useful concept
     *  for mathematicians, but for now, let's do the wrong thing until
     *  somebody reports a bug.
     */
 
    SRVector4D_Set(&srPrivate->eyeVectorInLocalCoords, 0.0, 0.0, 1.0, 0.0);
    Q3Vector3D_Set(&srPrivate->flatWorld.normal, 0.0, 0.0, 1.0);
    srPrivate->flatWorld.constant = 0.0;
}
 
 
/*===========================================================================*\
 *
 *  Routine:    SR_Update_LocalToWorldMatrix()
 *
 *  Comments:   Track changes to local-to-world transform
 *
\*===========================================================================*/
 
TQ3Status SR_Update_LocalToWorldMatrix(
    TQ3ViewObject           view,
    TSRPrivate              *srPrivate,
    TQ3Matrix4x4            *localToWorld)
{
    assert(view != NULL);
    assert(srPrivate != NULL);
    assert(localToWorld != NULL);
    
    UNUSED(view);
    
    srPrivate->transforms.localToWorld = *localToWorld;
    
    srPrivate->transforms.dirtyLocalToWorld = kQ3True;
    srPrivate->transforms.validLocalToWorld = kQ3True;
    
    return (kQ3Success);
}
 
 
/*===========================================================================*\
 *
 *  Routine:    SR_Update_LocalToFrustumMatrix()
 *
 *  Comments:   Track changes to local-to-frustum transform
 *
\*===========================================================================*/
 
TQ3Status SR_Update_LocalToFrustumMatrix(
    TQ3ViewObject           view,
    TSRPrivate              *srPrivate,
    TQ3Matrix4x4            *localToFrustum)
{
    assert(view             != NULL);
    assert(srPrivate        != NULL);
    assert(localToFrustum   != NULL);
    
    UNUSED(view);
    
    srPrivate->transforms.localToFrustum = *localToFrustum;
    
    srPrivate->transforms.dirtyLocalToFrustum = kQ3True;
    srPrivate->transforms.validLocalToFrustum = kQ3True;
    
    return (kQ3Success);
}
 
 
/*===========================================================================*\
 *
 *  Routine:    SR_Update_WorldToFrustumMatrix()
 *
 *  Comments:   Track changes to world-to-frustum transform
 *
\*===========================================================================*/
 
TQ3Status SR_Update_WorldToFrustumMatrix(
    TQ3ViewObject           view,
    TSRPrivate              *srPrivate,
    TQ3Matrix4x4            *worldToFrustum)
{
    assert(view             != NULL);
    assert(srPrivate        != NULL);
    assert(worldToFrustum   != NULL);
    
    UNUSED(view);
    
    srPrivate->transforms.worldToFrustum = *worldToFrustum;
    
    srPrivate->transforms.dirtyWorldToFrustum = kQ3True;
    srPrivate->transforms.validWorldToFrustum = kQ3True;
    
    return (kQ3Success);
}
 
 
/*===========================================================================*\
 *
 *  Routine:    SR_SetupRegionDependentTransformations()
 *
 *  Comments:   Update the frustum-to-device coordinates transform
 *
\*===========================================================================*/
 
TQ3Status SR_SetupRegionDependentTransformations(
    TSRPrivate  *srPrivate)
{
    float       frustumWindow[6] = { -1.0, 1.0, 1.0, -1.0, -1.0, 0.0 };
    float       deviceViewport[6];
    TQ3Status   status = kQ3Success;
    
    /*
     *  The orientation of Frustum Coordinates is x right, y up, z toward.
     *  The orientation of Device Coordinates is x right, y down, z toward.
     *  The y axis is inverted in the mapping so the min and max values are
     *  reversed in the Frustum boundaries.
     */
    {   
        float   tmp1, tmp2;
        
        /*
         *  xmin
         */
        Q3XDrawRegion_GetDeviceOffsetX(srPrivate->drawRegion, &tmp1);
        deviceViewport[0] =  tmp1;
        
        /*
         *  xmax
         */
        Q3XDrawRegion_GetWindowScaleX(srPrivate->drawRegion, &tmp1);
        Q3XDrawRegion_GetDeviceOffsetX(srPrivate->drawRegion, &tmp2);
        deviceViewport[1] =  tmp1 - 1.0 + tmp2;
        
        /*
         *  ymin
         */      
        Q3XDrawRegion_GetDeviceOffsetY(srPrivate->drawRegion, &tmp1);        
        deviceViewport[2] =  tmp1;
        
        /*
         *  ymax
         */
        Q3XDrawRegion_GetWindowScaleY(srPrivate->drawRegion, &tmp1);
        Q3XDrawRegion_GetDeviceOffsetY(srPrivate->drawRegion, &tmp2);
        deviceViewport[3] =  tmp1 - 1.0  + tmp2;
                             
        /*
         *  zmin, xmax
         */              
        deviceViewport[4] = -1.0;
        deviceViewport[5] =  0.0;
    }
    
    srPrivate->transforms.frustumToDC.value[0][0] 
        = (deviceViewport[1] - deviceViewport[0]) / 
                (frustumWindow[1] - frustumWindow[0]);
    srPrivate->transforms.frustumToDC.value[1][1] 
        = (deviceViewport[3] - deviceViewport[2]) / 
                (frustumWindow[3] - frustumWindow[2]);
    srPrivate->transforms.frustumToDC.value[2][2] 
        = (deviceViewport[5] - deviceViewport[4]) / 
                (frustumWindow[5] - frustumWindow[4]);
    srPrivate->transforms.frustumToDC.value[3][0] 
        = - srPrivate->transforms.frustumToDC.value[0][0] * 
                frustumWindow[0] + deviceViewport[0];
    srPrivate->transforms.frustumToDC.value[3][1] 
        = - srPrivate->transforms.frustumToDC.value[1][1] * 
                frustumWindow[2] + deviceViewport[2];
    srPrivate->transforms.frustumToDC.value[3][2] 
        = - srPrivate->transforms.frustumToDC.value[2][2] * 
                frustumWindow[4] + deviceViewport[4];
    
    {
        TQ3Matrix4x4    *deviceTransform;
 
        if (Q3XDrawRegion_GetDeviceTransform(
                srPrivate->drawRegion, 
                &deviceTransform) != NULL) {
            SR_ClipPlanesInDC(
                deviceTransform, 
                &srPrivate->clipPlanesInDC[0]);
        } else {
            status = kQ3Failure;
        }
    }
    
    srPrivate->transforms.dirtyFrustumToDC = kQ3False;
    srPrivate->transforms.validFrustumToDC = kQ3True;
    
    return (status);
}
 
 
/*===========================================================================*\
 *
 *  Routine:    SR_SetupPipelineInitCamera()
 *
 *  Comments:   Set up intitial camera info.
 *
\*===========================================================================*/
 
void SR_SetupPipelineInitCamera(
    TSRPrivate          *srPrivate,
    TQ3CameraObject     camera)
{
    assert(srPrivate != NULL);
    assert(camera != NULL);
    
    if (srPrivate->camera != NULL) {
        Q3Object_Dispose(srPrivate->camera);
    }
    
    srPrivate->camera       = Q3Shared_GetReference(camera);
    srPrivate->cameraType   = Q3Camera_GetType(camera);
    Q3Camera_GetPlacement(camera, &srPrivate->cameraPlacement);
    
    srPrivate->backfacingStyle  = kQ3BackfacingStyleRemove;
    srPrivate->orientationStyle = kQ3OrientationStyleCounterClockwise;
 
    Q3Matrix4x4_SetIdentity(&srPrivate->transforms.localToWorld);
    srPrivate->transforms.dirtyLocalToWorld = kQ3False;
    srPrivate->transforms.validLocalToWorld = kQ3False;
    
    Q3Matrix4x4_SetIdentity(&srPrivate->transforms.worldToFrustum);
    srPrivate->transforms.dirtyWorldToFrustum = kQ3False;
    srPrivate->transforms.validWorldToFrustum = kQ3False;
    
    Q3Matrix4x4_SetIdentity(&srPrivate->transforms.localToFrustum);
    srPrivate->transforms.dirtyLocalToFrustum = kQ3False;
    srPrivate->transforms.validLocalToFrustum = kQ3False;
    
    Q3Matrix4x4_SetIdentity(&srPrivate->transforms.frustumToDC);
    srPrivate->transforms.dirtyFrustumToDC = kQ3False;
    srPrivate->transforms.validFrustumToDC = kQ3False;
    
    Q3Matrix4x4_SetIdentity(&srPrivate->transforms.localToDC);
    srPrivate->transforms.dirtyLocalToDC = kQ3False;
    srPrivate->transforms.validLocalToDC = kQ3False;
}
 
 
/*===========================================================================*\
 *
 *  Routine:    SR_SetupPipelineExit()
 *
 *  Comments:   Dispose of things to which SR has a reference.
 *
\*===========================================================================*/
 
void SR_SetupPipelineExit(
    TSRPrivate          *srPrivate)
{
    assert(srPrivate != NULL);
    
    /*
     *  Dispose of highlight attribute set
     */
    if (srPrivate->viewHighlightAttributeSet != NULL) {
        Q3Object_Dispose(srPrivate->viewHighlightAttributeSet);
        srPrivate->viewHighlightAttributeSet = NULL;
    }
    
    /*
     *  Dispose of camera
     */
    if (srPrivate->camera != NULL) {
        Q3Object_Dispose(srPrivate->camera);
        srPrivate->camera = NULL;
    }
}
 
 
/*===========================================================================*\
 *
 *  Routine:    SR_UpdatePipeline()
 *
 *  Comments:   
 *
\*===========================================================================*/
 
TQ3Status SR_UpdatePipeline(
    TSRPrivate          *srPrivate)
{
    TQ3Status           status = kQ3Success;
 
    assert(srPrivate != NULL);
    
    /*
     *  If no transforms have changed since the last primitive,
     *  then simply return.
     */
    if (!(srPrivate->transforms.dirtyLocalToWorld       ||
          srPrivate->transforms.dirtyLocalToFrustum     ||
          srPrivate->transforms.dirtyWorldToFrustum     ||
          srPrivate->transforms.dirtyFrustumToDC        ||
          srPrivate->transforms.dirtyLocalToDC)) {
        return (kQ3Success);
    }
            
    /*
     *  Create local-to-device-coordinates transform
     */
    Q3Matrix4x4_Multiply(
        &srPrivate->transforms.localToFrustum, 
        &srPrivate->transforms.frustumToDC, 
        &srPrivate->transforms.localToDC);
        
    /*
     *
     *  Compute eye vector or point in local coordinates by
     *  transforming the eye vector or point in world coordinates
     *  back to local coordinates.  If the local-to-world matrix
     *  is singular and the upper-left 3x3 submatrix has rank 2, 
     *  compute the eye vector in local coordinates and the plane
     *  equation of the transformed geometry in world coordinates
     *  such that the vectors in both coordinate systems points
     *  in the same hemisphere as the eye.
     *
     */
    if (srPrivate->cameraType == kQ3CameraTypeOrthographic)  {  
        TQ3Vector3D     v;
        TSRVector4D     eyeVectorWC;
        TQ3Matrix4x4    inverse;
        
        /* Orthographic projection */
 
        /* Calculate world eye vector from camera placement */
        Q3Point3D_Subtract(
            &srPrivate->cameraPlacement.cameraLocation,
            &srPrivate->cameraPlacement.pointOfInterest,
            &v);
        SRVector3D_To4D(&v, &eyeVectorWC);
        SRVector4D_Normalize(&eyeVectorWC, &srPrivate->eyeVectorInWorldCoords);
        
        if (Q3Matrix4x4_Invert(
                &srPrivate->transforms.localToWorld, 
                &inverse) != NULL)  {
            TSRVector4D     eyeVectorLC;
            TQ3Matrix4x4    transpose;
            
            /* Nonsingular */
            Q3Matrix4x4_Transpose(
                &srPrivate->transforms.localToWorld,
                &transpose);
            SRVector4D_Transform(
                &eyeVectorWC,
                &transpose,
                &eyeVectorLC);
            SRVector4D_Normalize(
                &eyeVectorLC,
                &srPrivate->eyeVectorInLocalCoords);
            
            srPrivate->normalLocalToWorldRank = 3;
        } else {
            SR_UpdateNonInvertibleMatrix(srPrivate, kQ3True);
        }
    } else  /* Non orthographic */ {
        TQ3Matrix4x4        inverse;
        
        /* Perspective projection */
        
        /* Get world eye location from camera placement */
        Q3Point3D_To4D(
            &srPrivate->cameraPlacement.cameraLocation,
            &srPrivate->eyePointInWorldCoords);
        
        if (Q3Matrix4x4_Invert(
                &srPrivate->transforms.localToWorld,
                &inverse) != NULL) {
            float               wValue;
 
            /* Nonsingular */
            Q3RationalPoint4D_Transform(
                &srPrivate->eyePointInWorldCoords, 
                &inverse, 
                &srPrivate->eyePointInLocalCoords);
    
            wValue = 1.0 / srPrivate->eyePointInLocalCoords.w;
            
            srPrivate->eyePointInLocalCoords.x *= wValue;
            srPrivate->eyePointInLocalCoords.y *= wValue;
            srPrivate->eyePointInLocalCoords.z *= wValue;
            srPrivate->eyePointInLocalCoords.w  = 1.0;
            
            srPrivate->normalLocalToWorldRank = 3;
        } else {
            SR_UpdateNonInvertibleMatrix(srPrivate, kQ3False);
        }
    }
    
    /*
     *  Set the states of the transforms to "clean"
     */
    srPrivate->transforms.dirtyLocalToWorld     = kQ3False;
    srPrivate->transforms.dirtyLocalToFrustum   = kQ3False;
    srPrivate->transforms.dirtyWorldToFrustum   = kQ3False;
    srPrivate->transforms.dirtyFrustumToDC      = kQ3False;
    srPrivate->transforms.dirtyLocalToDC        = kQ3False;
    
    /*
     *  Set validity state
     */
    srPrivate->transforms.validLocalToDC        = kQ3True;
    
    return (kQ3Success);
}