Source/WRay_Document.c

/* 
 *  WRay_Document.c
 *
 *  QuickDraw 3D 1.6 Sample
 *  Robert Dierkes
 *
 *   07/28/98   RDD     Created.
 */
 
/*------------------*/
/*  Include Files   */
/*------------------*/
#include "QD3D.h"
#include "QD3DCamera.h"
#include "QD3DGroup.h"
#include "QD3DMath.h"
#include "QD3DTransform.h"
#include "QD3DView.h"
 
#include "WRay_Document.h"
#include "WRay_Error.h"
#include "WRay_Main.h"
#include "WRay_Memory.h"
#include "WRay_Scene.h"
 
#include <math.h>
 
/*----------------------*/
/*      Macros          */
/*----------------------*/
#define uIsZero(v)              (((v) >= -kQ3RealZero) && ((v) <= kQ3RealZero))
 
 
/*------------------*/
/*    Constants     */
/*------------------*/
 
/*----------------------*/
/*  Extern Declarations */
/*----------------------*/
 
 
/*----------------------*/
/*  Global Declarations */
/*----------------------*/
 
 
/*----------------------*/
/*  Local Prototypes    */
/*----------------------*/
static
TQ3Status   Document_InitializeWindow(
                TDocumentPtr        pDocument);
static
TQ3Status   Document_DisposeWindow(
                TDocumentPtr        pDocument);
 
 
/*
 *  Document_Initialize
 *
 *  Returns kQ3False if anything fails while creating the document.
 */
TQ3Boolean Document_Initialize(
            TDocumentPtr    pDocument) 
{
    DEBUG_ASSERT(pDocument != NULL, Document_Initialize);
 
    /*------------------------------*
     * Common to this document only *
     *------------------------------*/
    pDocument->fWindow    = NULL;
    pDocument->fView      = NULL;
    pDocument->fModel     = NULL;
 
    Q3Matrix4x4_SetIdentity(&pDocument->fMatrix);
 
    /*================*
     * Create objects *
     *================*/
 
    /* Create window */
    if (Document_InitializeWindow(pDocument) == kQ3Failure) {
        return kQ3False;
    }
 
    /* Create view(s) */
    if ((pDocument->fView = Scene_NewView(pDocument->fWindow)) == NULL) {
        return kQ3False;
    }
 
    /* Create fModel */
    if ((pDocument->fModel = Scene_NewModel()) == NULL) {
        return kQ3False;
    }
 
    /* Center the model */
    if (Document_UpdateCamera(pDocument) == kQ3Failure) {
        return kQ3False;
    }
 
    return kQ3True;
}
 
 
/*
 *  Document_Exit
 */
TQ3Boolean Document_Exit(
            TDocumentPtr    pDocument)
{
    DEBUG_ASSERT(pDocument != NULL, Document_Exit);
 
    Object_Dispose_NULL(&pDocument->fView);
    Object_Dispose_NULL(&pDocument->fModel);
 
    return (Document_DisposeWindow(pDocument) == kQ3Success)
                ? kQ3True
                : kQ3False;
}
 
 
#pragma mark -
 
/*
 *  Document_Draw
 */
TQ3Status Document_Draw(
            TDocumentPtr    pDocument)
{
    TQ3ViewObject   aView;
    TQ3Status       status;
    TQ3ViewStatus   viewStatus;
 
    DEBUG_ASSERT(pDocument != NULL, Document_Draw);
 
    aView = pDocument->fView;
 
    if (Q3View_StartRendering(aView) == kQ3Success) {
        do {
            status = Document_Submit_Objects(pDocument, aView);
 
            viewStatus = Q3View_EndRendering(aView);
        } while (viewStatus == kQ3ViewStatusRetraverse);
 
        if (viewStatus != kQ3ViewStatusDone) {
            return kQ3Failure;
        }
    } else {
        ERROR_DEBUG_STR("Document_Draw: Q3View_StartRendering failed.");
        return kQ3Failure;
    }
 
    return kQ3Success;
}
 
 
/*
 *  Document_Submit_Objects
 *
 * This assumes the model is already centered at the origin.
 * No parameter checking is done.
 */
TQ3Status Document_Submit_Objects(
            TDocumentPtr        pDocument,
            TQ3ViewObject       view)
{
    DEBUG_ASSERT(pDocument != NULL, Document_Submit_Objects);
    DEBUG_ASSERT(view != NULL,      Document_Submit_Objects);
 
    Q3MatrixTransform_Submit(&pDocument->fMatrix, view);
 
    return Q3DisplayGroup_Submit(pDocument->fModel, view);
}
 
 
#pragma mark -
 
/*
 *  Document_InitializeWindow
 */
static
TQ3Status Document_InitializeWindow(
            TDocumentPtr    pDocument)
{
    WindowPtr   pWindow;
 
    DEBUG_ASSERT(pDocument != NULL, Document_InitializeWindow);
 
    pWindow = GetNewCWindow(kWindowRsrcID, NULL, kWindowOnTop);
    pDocument->fWindow = pWindow;
    if (pWindow == NULL) {
        ERROR_DEBUG_STR("Document_InitializeWindow: GetNewCWindow failed.");
        return kQ3Failure;
    }
 
    SetPort((GrafPtr) pWindow);
 
    ShowWindow(pWindow);
 
    return kQ3Success;
}
 
 
/*
 *  Document_DisposeWindow
 */
static
TQ3Status Document_DisposeWindow(
            TDocumentPtr    pDocument)
{
    DEBUG_ASSERT(pDocument != NULL, Document_DisposeWindow);
 
    if (pDocument->fWindow != NULL) {
        DisposeWindow (pDocument->fWindow);
        pDocument->fWindow = NULL;
        return kQ3Failure;
    }
 
    return kQ3Success;
}
 
 
#pragma mark -
 
/*
 *  Document_UpdateCameraAspectRatio
 */
TQ3Status Document_UpdateCameraAspectRatio(
            TDocumentPtr        pDocument,
            Rect                *pPortRect)
{
    TQ3CameraObject     camera;
    TQ3Status           status;
    float               aspectRatioXToY;
 
    status = Q3View_GetCamera(pDocument->fView, &camera);
    if ((status == kQ3Failure)  ||
        (camera == NULL)) {
        ERROR_DEBUG_STR("Document_UpdateCameraAspectRatio: Q3View_GetCamera failed.");
        return status;
    }
 
    status = Q3ViewAngleAspectCamera_GetAspectRatio(camera, &aspectRatioXToY);
    if (status == kQ3Success) {
        aspectRatioXToY =   (float) (pPortRect->right  - pPortRect->left) / 
                            (float) (pPortRect->bottom - pPortRect->top);
        status = Q3ViewAngleAspectCamera_SetAspectRatio(camera, aspectRatioXToY);
    }
 
    Object_Dispose_NULL(&camera);
 
    return status;
}
 
 
/*
 *  Document_UpdateCamera
 */
TQ3Status Document_UpdateCamera(
            TDocumentPtr        pDocument)
{
    TQ3CameraObject             camera;
    TQ3CameraPlacement          placement;
    TQ3CameraRange              range;
    TQ3BoundingBox              boundingBox;
    float                       maxDimension;
    float                       fieldOfView;
    TQ3Status                   status;
 
    status = Q3View_GetCamera(pDocument->fView, &camera);
    if ((status == kQ3Failure)  ||
        (camera == NULL)) {
        ERROR_DEBUG_STR("Document_UpdateCamera: Q3View_GetCamera failed.");
        return kQ3Failure;
    }
 
    if (Q3Object_IsType(camera, kQ3CameraTypeViewAngleAspect) == kQ3False) {
        return kQ3Failure;
    }
 
    /* Set FOV */
    fieldOfView = kDefaultFieldOfView;
    Q3ViewAngleAspectCamera_SetFOV(camera, fieldOfView);
 
    /* Get maxDimension and bounding box */
    if (Document_GetMaximumDimension(pDocument, &maxDimension, &boundingBox) == kQ3Failure) {
        return kQ3Failure;
    }
 
    /* Set cameraLocation and pointOfInterest */
    Q3Camera_GetPlacement(camera, &placement);
 
    Q3Point3D_Set(&placement.pointOfInterest,
                    (boundingBox.max.x - boundingBox.min.x) / 2.0f + boundingBox.min.x,
                    (boundingBox.max.y - boundingBox.min.y) / 2.0f + boundingBox.min.y,
                    (boundingBox.max.z - boundingBox.min.z) / 2.0f + boundingBox.min.z);
 
    Q3Point3D_Set(&placement.cameraLocation,
                    placement.pointOfInterest.x,
                    placement.pointOfInterest.y,
                    ((maxDimension / 2.5) / atan(fieldOfView / 2.0)));/* Using 2.5 brings camera closer */
 
    if ((status = Q3Camera_SetPlacement(camera, &placement)) == kQ3Success) {
        /* Set hither and yon */
        #define kRangeMargin    (maxDimension / 2.0)
 
        range.hither = placement.cameraLocation.z - placement.pointOfInterest.z - kRangeMargin;
        range.yon    = range.hither + maxDimension + kRangeMargin;
 
        status = Q3Camera_SetRange(camera, &range);
    }
 
    Object_Dispose_NULL(&camera);
 
    return status;
}
 
 
/*
 *  Document_BoundingBox
 *
 *  Computes bounding box for fModel.
 */
TQ3Status Document_BoundingBox(
            TDocumentPtr        pDocument,
            TQ3BoundingBox      *pBoundingBox)
{
    TQ3Status       status;
    TQ3ViewStatus   viewStatus;
    TQ3ViewObject   aView;
 
    if (pDocument == NULL) {
        ERROR_DEBUG_STR("Document_BoundingBox: pDocument == NULL.");
        return kQ3Failure;
    }
 
    if (pBoundingBox == NULL) {
        ERROR_DEBUG_STR("Document_BoundingBox: pBoundingBox == NULL.");
        return kQ3Failure;
    }
 
    aView = pDocument->fView;
 
    status = kQ3Success;
 
    if (Q3View_StartBoundingBox(aView, kQ3ComputeBoundsExact) == kQ3Failure) {
        ERROR_DEBUG_STR("Document_BoundingBox: Q3View_StartBoundingBox failed.");
        return kQ3Failure;
    }
 
    do {
        Document_Submit_Objects(pDocument,aView);
 
        viewStatus = Q3View_EndBoundingBox(aView, pBoundingBox);
    }
    while (viewStatus == kQ3ViewStatusRetraverse);
 
    if (viewStatus != kQ3ViewStatusDone) {
        ERROR_DEBUG_STR("Document_BoundingBox: viewStatus != kQ3ViewStatusDone.");
        status = kQ3Failure;
    }
 
    return status;
}
 
 
/*
 *  Document_GetMaximumDimension
 *
 *  Get model's maximum diagonal dimension.
 */
TQ3Status Document_GetMaximumDimension(
    TDocumentPtr    pDocument,
    float           *pMaxDimension,
    TQ3BoundingBox  *pReturnedBoundingBox)
{
    TQ3BoundingBox      localBoundingBox,
                        *pBoundingBox;
    TQ3Vector3D         diagonalVector;
    float               dimension;
 
    #define         kAntiSingularity    0.0001
 
    if ((pDocument     == NULL) ||
        (pMaxDimension == NULL)) {
        ERROR_DEBUG_STR("Document_GetMaximumDimension: NULL parameter.");
        return kQ3Failure;
    }
 
    pBoundingBox = (pReturnedBoundingBox != NULL) ? pReturnedBoundingBox
                                                  : &localBoundingBox;
 
    /* Compute length of diagonal for the bounding box */
    if (Document_BoundingBox(pDocument, pBoundingBox) == kQ3Failure) {
        ERROR_DEBUG_STR("Document_GetMaximumDimension: Document_BoundingBox failed.");
        return kQ3Failure;
    }
 
    /*
     *  If we have a point or flat model, then the "boundingBox" would
     *  end up being a "singularity" at the location of the point.  As
     *  this bounding "box" is used in setting up the camera spec,
     *  we get bogus input into QuickDraw 3D.
     */
    dimension = pBoundingBox->max.x - pBoundingBox->min.x;
    if (uIsZero(dimension)) {
        pBoundingBox->max.x += kAntiSingularity;
        pBoundingBox->min.x -= kAntiSingularity;
    }
 
    dimension = pBoundingBox->max.y - pBoundingBox->min.y;
    if (uIsZero(dimension)) {
        pBoundingBox->max.y += kAntiSingularity;
        pBoundingBox->min.y -= kAntiSingularity;
    }
 
    dimension = pBoundingBox->max.z - pBoundingBox->min.z;
    if (uIsZero(dimension)) {
        pBoundingBox->max.z += kAntiSingularity;
        pBoundingBox->min.z -= kAntiSingularity;
    }
 
    Q3Point3D_Subtract(&pBoundingBox->max,
                       &pBoundingBox->min,
                       &diagonalVector);
 
    *pMaxDimension = Q3Vector3D_Length(&diagonalVector);
 
    #undef  kAntiSingularity
 
    return kQ3Success;
}