TumblerSource/Tumbler_camera.c

#include <FixMath.h>
 
#include "Tumbler_globals.h"
 
#include "QD3D.h"
#include "QD3DDrawContext.h"
#include "QD3DDrawContext.h"
#include "QD3DCamera.h"
#include "QD3DView.h"
#include "QD3DRenderer.h"
#include "QD3DGroup.h"
#include "QD3DMath.h"
 
#include "Tumbler_prototypes.h"
 
#include "QD3DGeometry.h"
#include "QD3DLight.h"
 
#include "Tumbler_camera.h"
 
void GetGroupBBox(
    TQ3ViewObject       viewObject,
    TQ3GroupObject      mainGroup,
    TQ3GroupObject      lightGroup,
    TQ3BoundingBox      *viewBBox)
{
    TQ3Point3D                  from    = { 0.0, 0.0, 1.0 };
    TQ3Point3D                  to      = { 0.0, 0.0, 0.0 };
    TQ3Vector3D                     up      = { 0.0, 1.0, 0.0 };
    
    float                       fieldOfView = .52359333333;
    float                       hither      =  0.5;
    float                       yon         =  1.5;
 
    TQ3Status                   status;
    
    Q3View_StartBoundingBox(viewObject, kQ3ComputeBoundsExact);
    do {
        status = Q3DisplayGroup_Submit(mainGroup, viewObject);
    } while (Q3View_EndBoundingBox(viewObject, viewBBox) == kQ3ViewStatusRetraverse);
                                        
    //
    //  If we have a point model, then the "viewBBox" 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 Escher.
    
    {
        float       xSize, ySize, zSize;
        
        xSize = viewBBox->max.x - viewBBox->min.x;
        ySize = viewBBox->max.y - viewBBox->min.y;
        zSize = viewBBox->max.z - viewBBox->min.z;
 
        if (xSize <= kQ3RealZero &&
            ySize <= kQ3RealZero &&
            zSize <= kQ3RealZero) {
            
            viewBBox->max.x += 0.0001;
            viewBBox->max.y += 0.0001;
            viewBBox->max.z += 0.0001;
            
            viewBBox->min.x -= 0.0001;
            viewBBox->min.y -= 0.0001;
            viewBBox->min.z -= 0.0001;
        }
    }
}
 
#define SIZESCALE   0.03
#define POSSCALE    0.5
void AdjustLightsPositions(
    DocumentPtr theDocument)
{
#if defined(ESCHER_VER_15) && ESCHER_VER_15
 
    TQ3BoundingBox      viewBBox;
    TQ3Vector3D         diagonalVector, radius;
    TQ3GroupPosition        position, lightPosition;
    TQ3Object           ellipsoid;
    TQ3Point3D          origin;
    float               maxDimension;
    TQ3GroupObject      lightGroup;
    TQ3LightObject      theLight;
    float       ratio;
    
    GetGroupBBox(theDocument->theView, theDocument->documentGroup, nil, &viewBBox);
 
    Q3Point3D_Subtract(&viewBBox.max,
                       &viewBBox.min,
                       &diagonalVector);
    maxDimension = Q3Vector3D_Length(&diagonalVector);
    maxDimension *= 8.0 / 7.0;
    
    ratio = 6.0 / maxDimension;
    
    /* Adjust the light from the viewer first */
    
    Q3View_GetLightGroup(theDocument->theView, &lightGroup);
        
    Q3Group_GetFirstPosition(lightGroup, &lightPosition);
 
    Q3Group_GetPositionObject(lightGroup, lightPosition, &theLight);
    
    origin.x = -10.0;
    origin.y = 10.0;
    origin.z = 30.0;
    
    Q3PointLight_SetLocation(theLight, &origin);
    Q3Object_Dispose(theLight);
    
    /* Set the geometry and the light parameters for the red light */
    Q3Group_GetFirstPositionOfType(theDocument->light1,
        kQ3ShapeTypeGeometry, &position);
 
    Q3Group_GetPositionObject(theDocument->light1, position, &ellipsoid);
    
    origin.x = ratio * diagonalVector.x * POSSCALE;
    origin.y = ratio *  diagonalVector.y * POSSCALE;
    origin.z =  ratio * diagonalVector.z * POSSCALE;
    Q3Ellipsoid_SetOrigin( ellipsoid, &origin);
    
    radius.x = radius.z = 0.0;
    radius.y = ratio * maxDimension * SIZESCALE;
    Q3Ellipsoid_SetOrientation( ellipsoid, &radius);
    
    radius.x = radius.y = 0.0;
    radius.z = ratio * maxDimension * SIZESCALE;
    Q3Ellipsoid_SetMajorRadius( ellipsoid, &radius);
    
    radius.z = radius.y = 0.0;
    radius.x = ratio * maxDimension * SIZESCALE;
    Q3Ellipsoid_SetMinorRadius( ellipsoid, &radius);
 
    Q3Group_GetNextPosition(lightGroup, &lightPosition);
    Q3Group_GetPositionObject(lightGroup, lightPosition, &theLight);    
    Q3PointLight_SetLocation(theLight, &origin);
    
    Q3Object_Dispose(theLight);
    
    Q3Object_Dispose(ellipsoid);
    
    
    /* Set the geometry and the light parameters for the green light */
    Q3Group_GetFirstPositionOfType(theDocument->light2,
        kQ3ShapeTypeGeometry, &position);
 
    Q3Group_GetPositionObject(theDocument->light2, position, &ellipsoid);
    
    origin.x = - ratio * diagonalVector.x * POSSCALE;
    origin.y = - ratio *  diagonalVector.y *  POSSCALE;
    origin.z = ratio * diagonalVector.z * POSSCALE;
    Q3Ellipsoid_SetOrigin( ellipsoid, &origin);
    
    radius.x = radius.z = 0.0;
    radius.y = ratio * maxDimension * SIZESCALE;
    Q3Ellipsoid_SetOrientation( ellipsoid, &radius);
    
    radius.x = radius.y = 0.0;
    radius.z = ratio * maxDimension * SIZESCALE;
    Q3Ellipsoid_SetMajorRadius( ellipsoid, &radius);
    
    radius.z = radius.y = 0.0;
    radius.x = ratio * maxDimension * SIZESCALE;
    Q3Ellipsoid_SetMinorRadius( ellipsoid, &radius);
    
    Q3Group_GetNextPosition(lightGroup, &lightPosition);
    Q3Group_GetPositionObject(lightGroup, lightPosition, &theLight);    
    Q3PointLight_SetLocation(theLight, &origin);
    
    Q3Object_Dispose(theLight);
    Q3Object_Dispose(ellipsoid);
    
    /* Set the geometry and the light parameters for the blue light */
    Q3Group_GetFirstPositionOfType(theDocument->light3,
        kQ3ShapeTypeGeometry, &position);
 
    Q3Group_GetPositionObject(theDocument->light3, position, &ellipsoid);
    
    origin.x = 0.0;
    origin.y =  ratio * diagonalVector.y * POSSCALE + 1.0;
    origin.z =  0.0;
    Q3Ellipsoid_SetOrigin( ellipsoid, &origin);
    
    radius.x = radius.z = 0.0;
    radius.y = ratio * maxDimension * SIZESCALE;
    Q3Ellipsoid_SetOrientation( ellipsoid, &radius);
    
    radius.x = radius.y = 0.0;
    radius.z = ratio * maxDimension * SIZESCALE;
    Q3Ellipsoid_SetMajorRadius( ellipsoid, &radius);
    
    radius.z = radius.y = 0.0;
    radius.x = ratio * maxDimension * SIZESCALE;
    Q3Ellipsoid_SetMinorRadius( ellipsoid, &radius);
 
    Q3Group_GetNextPosition(lightGroup, &lightPosition);
    Q3Group_GetPositionObject(lightGroup, lightPosition, &theLight);    
    Q3PointLight_SetLocation(theLight, &origin);
    
    Q3Object_Dispose(theLight);
    Q3Object_Dispose(ellipsoid);
 
    Q3Object_Dispose(lightGroup);
#endif
}
 
 
 
//===========================================================================
//
//  Routine:    AdjustCamera()
//
//  Comments:   
//
//===========================================================================
 
TQ3Point3D AdjustCamera(
    DocumentPtr theDocument,
    short               winWidth,
    short               winHeight)
{
    float                       fieldOfView;
    float                       hither;
    float                       yon;
    TQ3CameraPlacement          placement;
    TQ3CameraRange              range;
    TQ3BoundingBox              viewBBox;
    long                        fromAxis;   
    float                       maxDimension;
    float                       xSize, ySize, zSize;
    float                       weights[2] = { 0.5, 0.5 };
    TQ3Point3D                  points[2];
    TQ3Vector3D                 viewVector;
    TQ3Vector3D                 normViewVector;
    TQ3Vector3D                 eyeToFrontClip;
    TQ3Vector3D                 eyeToBackClip;
    float                       viewDistance;
    TQ3Vector3D                 diagonalVector;
    float                       ratio;
    TQ3CameraObject             camera;
    
    Q3View_GetCamera(theDocument->theView, &camera);
    
    GetGroupBBox(theDocument->theView, theDocument->documentGroup, 
        theDocument->dynamicLights, &viewBBox);
 
    /*
     *  If we have a point model, then the "viewBBox" 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 Escher.
     *
     *  Therefore, determine if the bounding box is really really small
     *  (kQ3RealZero) and increase it so it's just a little small.
     */
    xSize = viewBBox.max.x - viewBBox.min.x;
    ySize = viewBBox.max.y - viewBBox.min.y;
    zSize = viewBBox.max.z - viewBBox.min.z;
 
    if (xSize <= kQ3RealZero &&
        ySize <= kQ3RealZero &&
        zSize <= kQ3RealZero)  
    {
        viewBBox.max.x += 0.0001;
        viewBBox.max.y += 0.0001;
        viewBBox.max.z += 0.0001;
        
        viewBBox.min.x -= 0.0001;
        viewBBox.min.y -= 0.0001;
        viewBBox.min.z -= 0.0001;
    }
 
    points[0] = viewBBox.min;
    points[1] = viewBBox.max;
 
    /* 
        This sets the "documentGroupCenter" to the center of our bbox.
        
        (point[0] * weight[0] + point[1] * weight[1]) or
        
        (point[0] * 0.5 + point[1] * 0.5) or
        
        point[0] + point[1]
        -------------------, essentially, the average of the points.
                 2
    */
    Q3Point3D_AffineComb(points, weights, 2, &theDocument->documentGroupCenter);
 
    /*
     *  The "from" point is on a vector perpendicular to the plane
     *  in which the bounding box has greatest dimension.  As "up" is
     *  always in the positive y direction, look at x and z directions.
     */
    xSize = viewBBox.max.x - viewBBox.min.x;
    zSize = viewBBox.max.z - viewBBox.min.z;
    
    if (xSize > zSize) {
        fromAxis = kQ3AxisZ;
    } else {
        fromAxis = kQ3AxisX;
    }
 
    /*
     *  Compute the length of the diagonal of the bounding box.
     *
     *  The hither and yon planes are adjusted so that the
     *  diagonal of the bounding box is 7/8 the size of the
     *  minimum dimension of the view frustum. The diagonal is used instead
     *  of the maximum size (in x, y, or z) so that when you rotate
     *  the object, the corners don't get clipped out.
     */
    Q3Point3D_Subtract(
        &viewBBox.max,
        &viewBBox.min,
        &diagonalVector);
 
    maxDimension    =   Q3Vector3D_Length(&diagonalVector);
    maxDimension    *=  8.0 / 7.0;
    
    /* 
        This scales all objects in the scene so they gets scaled to fit
        in the window. We're using the length of the diagonal of the box
        to scale. We can now assume the maximum dimension of the model is
        1.0.
    */
    ratio = 1.0 / maxDimension;
            
    theDocument->documentGroupScale = ratio;
    
    /*
        Now, adjust the camera's hither and yon planes.
        
        (We don't edit the camera location, etc. as it will always be in
        the same location - we always scale the objects and leave the camera
        in the same location.)
    */
    Q3Camera_GetPlacement(camera, &placement);
 
    /*
        Compute the camera vector
    */
    Q3Point3D_Subtract(
        &placement.cameraLocation,
        &placement.pointOfInterest,
        &viewVector);
 
    viewDistance = Q3Vector3D_Length(&viewVector);
    Q3Vector3D_Normalize(&viewVector, &normViewVector);
 
    /*  
        Essentially, here we are "scaling" the normalized camera vector
        to a location in front of the object (half of maxDimension)
        and another location in back of the camera.
        
        Assume these three vectors below are co-linear.
                                    
        A = ratio * maxDimension/2.0
        
        |------hither-------||----A----|----A-----|
        |------------------yon--------------------|
        
        camera location                     
        * -----------------> * eyeToFrontClip
        * --------camera vector------> * object center
        * --------------------------------------> * eyeToBackClip
    */
    
    Q3Vector3D_Scale(&normViewVector, 
                     viewDistance - ratio * maxDimension/2.0,
                     &eyeToFrontClip);
                    
    Q3Vector3D_Scale(&normViewVector, 
                    viewDistance + ratio * maxDimension/2.0,
                    &eyeToBackClip);
 
    hither  = Q3Vector3D_Length(&eyeToFrontClip);
    yon     = Q3Vector3D_Length(&eyeToBackClip);
    
    /*
        Set the field of view. This will determine the width of our lens.
        Or, (more appropriately), as someone here mentioned,
        where the blinders begin in our image.
        
        This is the angle created by the triangle of the camera vector and the
        maximum dimension of our object.
        
        We use the hither point because it's closest and convenient.
        
        we know:
        
        atan(opposite/adjacent) = the angle we need
        
        Our angle is at the camera, so our "opposite" leg is "A" (above)
        our "adjacent" leg is the hither length.
        
        We double our result because our triangle gets us the half angle
        for the entire field of view. 
    */
    
    fieldOfView = 2 * atan((ratio * maxDimension/2.0)/hither);
 
    range.hither                = hither;
    range.yon                   = yon;
 
    Q3Camera_SetRange(camera, &range);
 
    Q3ViewAngleAspectCamera_SetFOV(
        camera, fieldOfView);
 
    /* 
        And finally, set up our aspect ratio depending on how big the 
        window is.
    */
    Q3ViewAngleAspectCamera_SetAspectRatio(
        camera, (float) winWidth / (float) winHeight);
 
    Q3Object_Dispose(camera);
    
    return(theDocument->documentGroupCenter);
}