Source/WRay_Scene.c

/* 
 *  WRay_Scene.c
 *
 *  QuickDraw 3D 1.6 Sample
 *  Robert Dierkes
 *
 *   07/28/98   RDD     Created.
 */
 
/*------------------*/
/*  Include Files   */
/*------------------*/
#include "QD3D.h"
#include "QD3DDrawContext.h"
#include "QD3DCamera.h"
#include "QD3DLight.h"
#include "QD3DGeometry.h"
#include "QD3DGroup.h"
#include "QD3DMath.h"
#include "QD3DRenderer.h"
#include "QD3DShader.h"
#include "QD3DTransform.h"
#include "QD3DView.h"
 
#include "WRay_Error.h"
#include "WRay_Document.h"
#include "WRay_Main.h"
#include "WRay_Memory.h"
#include "WRay_Scene.h"
#include "WRay_System.h"
 
#include <math.h>
 
 
/*------------------*/
/*    Constants     */
/*------------------*/
#define kDefaultCamLoc      0.0, 0.0, 10.0
#define kDefaultPtOfInt     0.0, 0.0, 0.0
#define kDefaultUpVector    0.0, 1.0, 0.0
#define kAngleDelta         Q3Math_DegreesToRadians(2.0f)
 
 
/*----------------------*/
/*  Global Declarations */
/*----------------------*/
static  TQ3CameraObject gCamera         = NULL;
static  float           gRotationAngle  = Q3Math_DegreesToRadians(90.0f);
static  TQ3Boolean      gDoRotation     = kQ3False;
 
 
/*----------------------*/
/*  Local Prototypes    */
/*----------------------*/
static
TQ3DrawContextObject Scene_NewDrawContext(
            WindowPtr       pWindow);
static
TQ3Status Scene_ChangeViewAttributes(
            TQ3ViewObject   view);
static
TQ3Status Scene_NewRenderer(
            TQ3ViewObject   view,
            TQ3ObjectType   rendererType);
static
TQ3CameraObject Scene_NewCamera(
            Rect            *pWindowRect);
static
TQ3GroupObject Scene_NewLights(
            void);
static
TQ3Status   Scene_NewStyles(
            TQ3GroupObject  group);
static
TQ3Status   Scene_NewScene(
            TQ3GroupObject  group);
static
TQ3Status   Scene_NewBoxSides(
            TQ3GroupObject  group,
            TQ3BoundingBox  *pBBox);
 
 
/*
 *  Scene_NewView
 */
TQ3ViewObject   Scene_NewView(
    WindowPtr       pWindow)
{
    TQ3Status               status = kQ3Failure;
    TQ3ViewObject           view;
    TQ3DrawContextObject    drawContext;
    TQ3CameraObject         camera;
    TQ3GroupObject          lights;
 
    view = Q3View_New();
    DEBUG_ASSERT(view != NULL, Scene_NewView);
    if (view == NULL) {
        goto bail;
    }
 
    /* Create and set draw context */
    DEBUG_ASSERT(pWindow != NULL, Scene_NewView);
    if ((drawContext = Scene_NewDrawContext(pWindow)) == NULL) {
        goto bail;
    }
 
    if ((status = Q3View_SetDrawContext(view, drawContext)) == kQ3Failure) {
        goto bail;
    }
    Object_Dispose_NULL(&drawContext);
 
    /* Change view's attributes */
    if ((status = Scene_ChangeViewAttributes(view)) == kQ3Failure) {
        goto bail;
    }
 
    /* Create and set renderer */
    if ((status = Scene_NewRenderer(view, kQ3RendererTypeInteractive)) == kQ3Failure) {
        goto bail;
    }
 
    /* Create and set camera */
    if ((camera = Scene_NewCamera(&pWindow->portRect)) == NULL) {
        goto bail;
    }
 
    if ((status = Q3View_SetCamera(view, camera)) == kQ3Failure) {
        goto bail;
    }
    /* Save reference to camera */
    gCamera = camera;
    Object_Dispose_NULL(&camera);
 
 
    /* Create and set lights */
    if ((lights = Scene_NewLights()) == NULL) {
        goto bail;
    }
 
    if ((status = Q3View_SetLightGroup(view, lights)) == kQ3Failure) {
        goto bail;
    }
    Object_Dispose_NULL(&lights);
 
    return (view);
 
bail:
    return NULL;
}
 
 
/*
 *  Scene_NewDrawContext
 */
static
TQ3DrawContextObject Scene_NewDrawContext(
    WindowPtr       pWindow)
{
    TQ3DrawContextObject        drawContext;
    TQ3DrawContextData          *pDCData;
    TQ3MacDrawContextData       macDrawContextData;
 
    pDCData = &macDrawContextData.drawContextData;
 
    #define kDefaultClearImageColor     1.0, 0.15, 0.15, 0.15
 
    /* Fill in common draw context data */
    pDCData->clearImageMethod = kQ3ClearMethodWithColor;
    Q3ColorARGB_Set(&pDCData->clearImageColor, kDefaultClearImageColor);
    pDCData->paneState         = kQ3False;
    pDCData->maskState         = kQ3False;
 
 
    /* Mac draw context */
    pDCData->doubleBufferState = kQ3True;
 
    /* This is the window associated with the view */
    DEBUG_ASSERT(pWindow != NULL, Scene_NewDrawContext);
    macDrawContextData.window   = (CGrafPtr) pWindow;
    macDrawContextData.library  = kQ3Mac2DLibraryNone;
    macDrawContextData.viewPort = NULL;
    macDrawContextData.grafPort = NULL;
 
    /* Create draw context and return it, if itÕs nil the caller must handle */
    drawContext = Q3MacDrawContext_New(&macDrawContextData);
 
    DEBUG_ASSERT(drawContext != NULL, Scene_NewDrawContext);
 
    #undef  kDefaultClearImageColor
 
    return drawContext;
}
 
 
/*
 *  Scene_ChangeViewAttributes
 */
TQ3Status Scene_ChangeViewAttributes(
    TQ3ViewObject   view)
{
    TQ3Status           status = kQ3Failure;
    TQ3AttributeSet     attributeSet = NULL;
    TQ3ColorRGB         color = { 1.0, 0.0, 0.0 };
 
    status = Q3View_GetDefaultAttributeSet(view, &attributeSet);
    DEBUG_ASSERT(status == kQ3Success, Q3View_GetDefaultAttributeSet);
    DEBUG_ASSERT(attributeSet != NULL, Q3View_GetDefaultAttributeSet);
    if (status == kQ3Failure) {
        return status;
    }
 
    /* Change view's specular color */
    Q3AttributeSet_Add(attributeSet, kQ3AttributeTypeSpecularColor, &color);
 
    status = Q3View_SetDefaultAttributeSet(view, attributeSet);
    DEBUG_ASSERT(status == kQ3Success, Q3View_SetDefaultAttributeSet);
    Object_Dispose_NULL(&attributeSet);
 
    return status;
}
 
 
/*
 *  Scene_NewRenderer
 */
TQ3Status Scene_NewRenderer(
    TQ3ViewObject   view,
    TQ3ObjectType   rendererType)
{
    TQ3Status           status = kQ3Failure;
    TQ3RendererObject   renderer = NULL;
 
    if (view == NULL) {
        ERROR_DEBUG_STR("Scene_NewRenderer: view == NULL.");
        return status;
    }
 
    /* Use the interactive software renderer if present */
    if ((renderer = Q3Renderer_NewFromType(rendererType)) != NULL) {
 
        if ((status = Q3View_SetRenderer(view, renderer)) == kQ3Success) {
            /* Use hardware accelerator for IR if present */
            if (rendererType == kQ3RendererTypeInteractive) {
                Q3InteractiveRenderer_SetDoubleBufferBypass(renderer, kQ3True);
            } 
        } 
        else {
            ERROR_DEBUG_STR("Scene_NewRenderer: Q3View_SetRenderer failed.");
        }
    }
    else {
        /* Default to wireframe renderer */
        if ((renderer = Q3Renderer_NewFromType(kQ3RendererTypeWireFrame)) != NULL) {
 
            if ((status = Q3View_SetRenderer(view, renderer)) == kQ3Failure) {
                ERROR_DEBUG_STR("Scene_NewRenderer: Q3View_SetRenderer(WF) failed.");
            }
        }
    }
 
    Object_Dispose_NULL(&renderer);
 
    return status;
}
 
 
/*
 *  Scene_NewCamera
 */
static
TQ3CameraObject Scene_NewCamera(
    Rect            *pWindowRect)
{
    TQ3ViewAngleAspectCameraData
                        perspectiveData;
    TQ3CameraObject     camera;
    TQ3Point3D          from    = { kDefaultCamLoc };
    TQ3Point3D          to      = { kDefaultPtOfInt };
    TQ3Vector3D         up      = { kDefaultUpVector };
 
    perspectiveData.cameraData.placement.cameraLocation     = from;
    perspectiveData.cameraData.placement.pointOfInterest    = to;
    perspectiveData.cameraData.placement.upVector           = up;
 
    perspectiveData.cameraData.range.hither = kDefaultHither;
    perspectiveData.cameraData.range.yon    = kDefaultYon;
 
    perspectiveData.cameraData.viewPort.origin.x = -1.0;
    perspectiveData.cameraData.viewPort.origin.y =  1.0;
    perspectiveData.cameraData.viewPort.width    =  2.0;
    perspectiveData.cameraData.viewPort.height   =  2.0;
 
    perspectiveData.fov             = kDefaultFieldOfView;
    perspectiveData.aspectRatioXToY =
        (float) (pWindowRect->right -  pWindowRect->left) / 
        (float) (pWindowRect->bottom - pWindowRect->top);
 
    camera = Q3ViewAngleAspectCamera_New(&perspectiveData);
    DEBUG_ASSERT(camera != NULL, Scene_NewCamera);
 
    return camera;
}
 
 
/*----------------------------------------------------------------------------------*/
 
/*
 *  Scene_NewLights
 */
static
TQ3GroupObject Scene_NewLights(
    void)
{
    TQ3GroupPosition        groupPosition = NULL;
    TQ3GroupObject          lightGroup = NULL;
    TQ3LightObject          light = NULL;
    TQ3DirectionalLightData comboLightData;
 
    /* Create light group and add each of the lights into the group */
    lightGroup = Q3LightGroup_New();
    if (lightGroup == NULL)
        goto bail;
 
 
    /* Create ambient light */
    comboLightData.lightData.isOn       = kQ3True;
    comboLightData.lightData.brightness = 0.6f;
    Q3ColorRGB_Set(&comboLightData.lightData.color, 1.0f, 1.0f, 1.0f);
 
    light = Q3AmbientLight_New(&comboLightData.lightData);
    DEBUG_ASSERT(light != NULL, Q3AmbientLight_New);
    if (light == NULL)
        goto bail;
 
    groupPosition = Q3Group_AddObject(lightGroup, light);
    DEBUG_ASSERT(groupPosition != NULL, Q3Group_AddObject);
    if (groupPosition == NULL)
        goto bail;
    Object_Dispose_NULL(&light);
 
 
    /* Create a yellow directional light */
    comboLightData.lightData.brightness = 0.9f;
    Q3ColorRGB_Set(&comboLightData.lightData.color, 0.9f, 0.9f, 0.0f);
    comboLightData.castsShadows         = kQ3True;
    Q3Vector3D_Set(&comboLightData.direction, 1.0f, -1.0f, -2.0f);
 
    light = Q3DirectionalLight_New(&comboLightData);
    DEBUG_ASSERT(light != NULL, Q3DirectionalLight_New);
    if (light == NULL)
        goto bail;
 
    groupPosition = Q3Group_AddObject(lightGroup, light);
    DEBUG_ASSERT(groupPosition != NULL, Q3Group_AddObject);
    if (groupPosition == NULL)
        goto bail;
    Object_Dispose_NULL(&light);
 
 
    /* Create a magenta directional light */
    comboLightData.lightData.brightness = 0.9f;
    Q3ColorRGB_Set(&comboLightData.lightData.color, 0.9f, 0.0f, 0.9f);
    comboLightData.castsShadows         = kQ3True;
    Q3Vector3D_Set(&comboLightData.direction, -1.0f, 1.0f, 2.0f);
 
    light = Q3DirectionalLight_New(&comboLightData);
    DEBUG_ASSERT(light != NULL, Q3DirectionalLight_New);
    if (light == NULL)
        goto bail;
 
    groupPosition = Q3Group_AddObject(lightGroup, light);
    DEBUG_ASSERT(groupPosition != NULL, Q3Group_AddObject);
    if (groupPosition == NULL)
        goto bail;
    Object_Dispose_NULL(&light);
 
    return (lightGroup);
 
bail:
    Object_Dispose_NULL(&light);
    Object_Dispose_NULL(&lightGroup);
 
    return (NULL);
}
 
 
/*
 *  Scene_NewModel
 */
TQ3GroupObject Scene_NewModel(
    void)
{
    TQ3Status               status = kQ3Failure;
    TQ3GroupObject          model = NULL;
    TQ3DisplayGroupState    state;
    TQ3ShaderObject         illuminationShader = NULL;
 
    if ((model = Q3OrderedDisplayGroup_New()) != NULL) {
 
        /* Make group inline so state isn't pushed */
        if (Q3DisplayGroup_GetState(model, &state) == kQ3Success) {
            state |= kQ3DisplayGroupStateMaskIsInline;
            Q3DisplayGroup_SetState(model, state);
        }
 
        /* Add shader to model */
        if ((illuminationShader = Q3PhongIllumination_New()) != NULL) {
            Q3Group_AddObject(model, illuminationShader);
            Object_Dispose_NULL(&illuminationShader);
 
            /* Add styles to model */
            if ((status = Scene_NewStyles(model)) == kQ3Success) {
 
                /* Add geometries to model */
                status = Scene_NewScene(model);
            }
        }
 
        if (status == kQ3Failure) {
            Object_Dispose_NULL(&model);
        }
    }
 
    return (model);
}
 
 
/*
 *  Scene_NewStyles
 */
static
TQ3Status Scene_NewStyles(
    TQ3GroupObject  group)
{
    TQ3StyleObject      style;
 
    if (group == NULL) {
        ERROR_DEBUG_STR("Scene_NewStyles: group == NULL.");
        return kQ3Failure;
    }
 
    /* Interpolation Style */
    if ((style = Q3InterpolationStyle_New(kQ3InterpolationStyleVertex)) == NULL) {
        ERROR_DEBUG_STR("Scene_NewStyles: Q3InterpolationStyle_New failed.");
        return kQ3Failure;
    }
    Q3Group_AddObject(group, style);
    Object_Dispose_NULL(&style);
 
 
    /* Backfacing Style */
    if ((style = Q3BackfacingStyle_New(kQ3BackfacingStyleBoth)) == NULL) {
        ERROR_DEBUG_STR("Scene_NewStyles: Q3BackfacingStyle_New failed.");
        return kQ3Failure;
    }
    Q3Group_AddObject(group, style);
    Object_Dispose_NULL(&style);
 
 
    /* Fill Style */
    if ((style = Q3FillStyle_New(kQ3FillStyleFilled)) == NULL) {
        ERROR_DEBUG_STR("Scene_NewStyles: Q3FillStyle_New failed.");
        return kQ3Failure;
    }
    Q3Group_AddObject(group, style);
    Object_Dispose_NULL(&style);
 
 
    /* Subdivision Style */
    {
        TQ3SubdivisionStyleData     data;
 
        data.method = kQ3SubdivisionMethodConstant;
        data.c1     = 9.0f;
        data.c2     = 9.0f;
 
        if ((style = Q3SubdivisionStyle_New(&data)) == NULL) {
            ERROR_DEBUG_STR("Scene_NewStyles: Q3SubdivisionStyle_New failed.");
            return kQ3Failure;
        }
        Q3Group_AddObject(group, style);
        Object_Dispose_NULL(&style);
    }
 
    return kQ3Success;
}
 
 
#pragma mark -
 
/*
 *  Scene_NewScene
 */
static
TQ3Status Scene_NewScene(
    TQ3GroupObject  group)
{
    TQ3Status           status = kQ3Success;
    TQ3BoundingBox      sceneBBox;
    TQ3GeometryObject   geometry = NULL;
    TQ3CylinderData     cylinderData;
    TQ3AttributeSet     attr = NULL;
    unsigned long       rx, cy;
 
    #define kNumRows            5   /* X */
    #define kNumColumns         7   /* Y */
 
    #define kCylHeight          4.0f
    #define kCylDiameter        (2.0f * kCylRadius)
    #define kCylGap             (0.9f * kCylRadius)
 
    #define kSceneWidth         ((kNumColumns * kCylDiameter) + ((kNumColumns-1) * kCylGap))
    #define kSceneHeight        ((kNumRows    * kCylDiameter) + ((kNumRows-1)    * kCylGap))
    #define kSceneDepth         kCylHeight
 
    #define kSceneHalfWidth     (kSceneWidth    / 2.0f)
    #define kSceneHalfHeight    (kSceneHeight   / 2.0f)
    #define kSceneHalfDepth     (kSceneDepth    / 2.0f)
 
    if (group == NULL) {
        ERROR_DEBUG_STR("Scene_NewScene: group == NULL.");
        return kQ3Failure;
    }
 
    /* Create a 4 sided box */
    Q3Point3D_Set(&sceneBBox.min, -kSceneHalfWidth, -kSceneHalfHeight, -kSceneHalfDepth);
    Q3Point3D_Set(&sceneBBox.max,  kSceneHalfWidth,  kSceneHalfHeight,  kSceneHalfDepth);
    sceneBBox.isEmpty = kQ3False;
 
    if (Scene_NewBoxSides(group, &sceneBBox) == kQ3Failure) {
        return kQ3Failure;
    }
 
    /* Create cylinders inside box */
    for (rx = 0; rx < kNumColumns; rx++) {
        for (cy = 0; cy < kNumRows; cy++) {
 
            /* Skip middle geometry */
            if ((rx == (kNumColumns / 2))  &&  (cy == (kNumRows / 2))) {
                continue;
            }
            /* Every other geometry */
            if ((rx + cy) & 1) {
                continue;
            }
 
            /* Cylinder is parallel to z-axis */
            Q3Point3D_Set(&cylinderData.origin,
                (((float) rx) * (kCylDiameter + kCylGap)) + kCylRadius - kSceneHalfWidth,
                (((float) cy) * (kCylDiameter + kCylGap)) + kCylRadius - kSceneHalfHeight,
                -kSceneHalfDepth);
 
            Q3Vector3D_Set(&cylinderData.orientation,   0.0,        0.0,        kCylHeight);
            Q3Vector3D_Set(&cylinderData.majorRadius,   kCylRadius, 0.0,        0.0);
            Q3Vector3D_Set(&cylinderData.minorRadius,   0.0,        kCylRadius, 0.0);
 
            cylinderData.uMin = cylinderData.vMin = 0.0f;
            cylinderData.uMax = cylinderData.vMax = 1.0f;
            cylinderData.caps = kQ3EndCapMaskTop | kQ3EndCapMaskBottom;
 
            cylinderData.interiorAttributeSet   = NULL;
            cylinderData.topAttributeSet        = NULL;
            cylinderData.faceAttributeSet       = NULL;
            cylinderData.bottomAttributeSet     = NULL;
            cylinderData.cylinderAttributeSet   = NULL;
 
            attr = Q3AttributeSet_New();
            if (attr != NULL) {
                TQ3ColorRGB     rgbColor;
 
                Q3ColorRGB_Set(&rgbColor,
                                0.0,
                                (float) (kNumColumns - rx) / kNumColumns,
                                (float) cy / kNumRows);
                Q3AttributeSet_Add(attr, kQ3AttributeTypeDiffuseColor, &rgbColor);
            }
            cylinderData.cylinderAttributeSet = attr;
 
            geometry = Q3Cylinder_New(&cylinderData);
            if (geometry == NULL) {
                status = kQ3Failure;
                break;
            }
 
            Object_Dispose_NULL(&attr);
 
            Q3Group_AddObject(group, geometry);
            Object_Dispose_NULL(&geometry);
        }
    }
 
    Object_Dispose_NULL(&attr);
    Object_Dispose_NULL(&geometry);
 
    return status;
}
 
 
static
TQ3Status Scene_NewBoxSides(
    TQ3GroupObject  group,
    TQ3BoundingBox  *pBBox)
{
    #define kMaxPolygons    4
    #define kMaxPolyVerts   4
    #define kOutsetScale    0.15f
 
    TQ3Status           status   = kQ3Success;
    TQ3GeometryObject   geometry = NULL;
    TQ3PolygonData      polyData;
    TQ3Vertex3D         vertices[kMaxPolyVerts],
                        *pVertex;
    TQ3Point3D          min, max;
    unsigned long       side;
    TQ3AttributeSet     attr = NULL;
    float               boxOutset;
 
    min = pBBox->min;
    max = pBBox->max;
 
    boxOutset = (max.x - min.x) * kOutsetScale;
    min.x -= boxOutset; max.x += boxOutset;
 
    boxOutset = (max.y - min.y) * kOutsetScale;
    min.y -= boxOutset; max.y += boxOutset;
 
    boxOutset = (max.z - min.z) * kOutsetScale;
    min.z -= boxOutset; max.z += boxOutset;
 
    polyData.numVertices = kMaxPolyVerts;
    polyData.vertices    = vertices;
 
    pVertex = polyData.vertices;
 
    vertices[0].attributeSet =
    vertices[1].attributeSet =
    vertices[2].attributeSet =
    vertices[3].attributeSet = NULL;
 
    for (side = 0; side < kMaxPolygons; side++) {
 
        attr = Q3AttributeSet_New();
        if (attr != NULL) {
            TQ3ColorRGB     rgbColor;
 
            Q3ColorRGB_Set(&rgbColor, 0.0, 0.5, 0.5);
            Q3AttributeSet_Add(attr, kQ3AttributeTypeDiffuseColor, &rgbColor);
        }
        polyData.polygonAttributeSet = attr;
 
        switch (side) {
        case 0:
            Q3Point3D_Set(&pVertex[0].point, min.x, min.y, min.z);
            Q3Point3D_Set(&pVertex[1].point, min.x, min.y, max.z);
            Q3Point3D_Set(&pVertex[2].point, max.x, min.y, max.z);
            Q3Point3D_Set(&pVertex[3].point, max.x, min.y, min.z);
            break;
 
        case 1:
            Q3Point3D_Set(&pVertex[0].point, max.x, min.y, min.z);
            Q3Point3D_Set(&pVertex[1].point, max.x, min.y, max.z);
            Q3Point3D_Set(&pVertex[2].point, max.x, max.y, max.z);
            Q3Point3D_Set(&pVertex[3].point, max.x, max.y, min.z);
            break;
 
        case 2:
            Q3Point3D_Set(&pVertex[0].point, min.x, max.y, min.z);
            Q3Point3D_Set(&pVertex[1].point, max.x, max.y, min.z);
            Q3Point3D_Set(&pVertex[2].point, max.x, max.y, max.z);
            Q3Point3D_Set(&pVertex[3].point, min.x, max.y, max.z);
            break;
 
        case 3:
            Q3Point3D_Set(&pVertex[0].point, min.x, min.y, min.z);
            Q3Point3D_Set(&pVertex[1].point, min.x, max.y, min.z);
            Q3Point3D_Set(&pVertex[2].point, min.x, max.y, max.z);
            Q3Point3D_Set(&pVertex[3].point, min.x, min.y, max.z);
            break;
        }
 
        geometry = Q3Polygon_New(&polyData);
        Object_Dispose_NULL(&polyData.polygonAttributeSet);
        if (geometry == NULL) {
            return kQ3Failure;
        }
 
        Q3Group_AddObject(group, geometry);
        Object_Dispose_NULL(&geometry);
    }
 
    return kQ3Success;
}
 
 
#pragma mark -
 
/*
 *  Scene_IsRotating
 */
TQ3Boolean Scene_IsRotating(
    void)
{
    return gDoRotation;
}
 
 
/*
 *  Scene_SetIsRotating
 */
TQ3Boolean Scene_SetIsRotating(
    TQ3Boolean      newIsRotating)
{
    TQ3Boolean  oldIsRotating = gDoRotation;
 
    gDoRotation = newIsRotating;
 
    return oldIsRotating;
}
 
 
/*
 *  Scene_Rotate
 *
 *  Global:
 *      gCamera
 */
TQ3Status Scene_Rotate(
    void)
{
    TQ3Status           status = kQ3Failure;
    TQ3CameraPlacement  placement;
    float               cameraDist;
 
    if (gDoRotation) {
        gRotationAngle += kAngleDelta;
 
        status = Q3Camera_GetPlacement(gCamera, &placement);
        DEBUG_ASSERT(status == kQ3Success, Q3Camera_GetPlacement);
 
        cameraDist = Q3Point3D_Distance(&placement.cameraLocation, &placement.pointOfInterest);
 
        /* Assumes pointOfInterest is at origin any pointOfInterest.y is 0 */
        placement.cameraLocation.x = cos(gRotationAngle) * cameraDist;
        placement.cameraLocation.z = sin(gRotationAngle) * cameraDist;
 
        status = Q3Camera_SetPlacement(gCamera, &placement);
        DEBUG_ASSERT(status == kQ3Success, Q3Camera_SetPlacement);
    }
 
    return status;
}