m3g/m3gcore11/src/m3g_camera.c
author William Roberts <williamr@symbian.org>
Tue, 13 Jul 2010 17:02:10 +0100
changeset 113 f3c3c510a760
parent 0 5d03bc08d59c
permissions -rw-r--r--
Re-merge fixes for bug 1362, bug 1666, bug 1863, KhronosRI and bld.inf.

/*
* Copyright (c) 2003 Nokia Corporation and/or its subsidiary(-ies).
* All rights reserved.
* This component and the accompanying materials are made available
* under the terms of the License "Eclipse Public License v1.0"
* which accompanies this distribution, and is available
* at the URL "http://www.eclipse.org/legal/epl-v10.html".
*
* Initial Contributors:
* Nokia Corporation - initial contribution.
*
* Contributors:
*
* Description: Camera implementation
*
*/


/*!
 * \internal
 * \file
 * \brief Camera implementation
 */

#ifndef M3G_CORE_INCLUDE
#   error included by m3g_core.c; do not compile separately.
#endif

#include "m3g_camera.h"

/* Internal frustum plane enumeration (and testing order!) */

#define NEAR_PLANE      0
#define FAR_PLANE       1
#define LEFT_PLANE      2
#define RIGHT_PLANE     3
#define BOTTOM_PLANE    4
#define TOP_PLANE       5

/*----------------------------------------------------------------------
 * Private functions
 *--------------------------------------------------------------------*/

/*!
 * \internal
 * \brief Makes sure that the internal projection matrix is up-to-date
 *        and checks if the camera has a zero view volume.
 */
static void m3gValidateProjectionMatrix(Camera *camera)
{
    M3Gint projType = camera->projType;

    /* The generic matrix is always valid, but for perspective and
     * parallel we must regenerate the matrix */
    
    if (projType != M3G_GENERIC) {
        M3Gfloat m[16];

        M3Gfloat clipNear = camera->clipNear;
        M3Gfloat clipFar = camera->clipFar;

        if (projType == M3G_PERSPECTIVE) {
            
            M3Gfloat height = m3gTan(m3gMul(M3G_DEG2RAD * 0.5f,
                                            camera->heightFov));
            
            m[0] = m3gRcp(m3gMul(camera->aspect, height));
            m[1] = m[2] = m[3] = 0.f;
            
            m[4] = 0.f;
            m[5] = m3gRcp(height);
            m[6] = m[7] = 0.f;
            
            m[8] = m[9] = 0.f;
            m[10] = m3gDiv(-m3gAdd(clipFar, clipNear),
                           m3gSub(clipFar, clipNear));
            m[11] = -1.f;
            
            m[12] = m[13] = 0.f;
            m[14] = m3gDiv(m3gMul(m3gMul(-2.f, clipFar), clipNear),
                           m3gSub(clipFar, clipNear));
            m[15] = 0.f;
        }
        else if (projType == M3G_PARALLEL) {

            M3Gfloat height = camera->heightFov;
            
            m[0] = m3gDiv(2.f, m3gMul(camera->aspect, height));
            m[1] = m[2] = m[3] = 0.f;

            m[4] = 0.f;
            m[5] = m3gDiv(2.f, height);
            m[6] = m[7] = 0;
            
            m[8] = m[9] = 0;
            m[10] = m3gDiv(-2.f, m3gSub(clipFar, clipNear));
            m[11] = 0.f;
            
            m[12] = m[13] = 0.f;
            m[14] = m3gDiv(-m3gAdd(clipFar, clipNear),
                           m3gSub(clipFar, clipNear));
            m[15] = 1.f;
        }
        else {
            M3G_ASSERT(M3G_FALSE); /* unknown projection type! */
        }
        m3gSetMatrixColumns(&camera->projMatrix, m); 
    }

    {
        M3GMatrix im;
        if (!m3gMatrixInverse(&im, &camera->projMatrix)) {
            camera->zeroViewVolume = M3G_TRUE;
        }
        else {
            camera->zeroViewVolume = M3G_FALSE;
        }
    }

    camera->frustumPlanesValid = M3G_FALSE;
}

/*!
 * \internal
 * \brief Validates the cached view frustum planes
 */
static void m3gValidateFrustumPlanes(Camera *camera) 
{
    if (!camera->frustumPlanesValid) {
        Vec4 *plane;
        Vec4 rows[4];
        
        m3gGetMatrixRows(&camera->projMatrix, (M3Gfloat*) rows);

        plane = &camera->frustumPlanes[LEFT_PLANE];
        *plane = rows[3];
        m3gAddVec4(plane, &rows[0]);

        plane = &camera->frustumPlanes[RIGHT_PLANE];
        *plane = rows[3];
        m3gSubVec4(plane, &rows[0]);

        plane = &camera->frustumPlanes[BOTTOM_PLANE];
        *plane = rows[3];
        m3gAddVec4(plane, &rows[1]);

        plane = &camera->frustumPlanes[TOP_PLANE];
        *plane = rows[3];
        m3gSubVec4(plane, &rows[1]);

        plane = &camera->frustumPlanes[NEAR_PLANE];
        *plane = rows[3];
        m3gAddVec4(plane, &rows[2]);

        plane = &camera->frustumPlanes[FAR_PLANE];
        *plane = rows[3];
        m3gSubVec4(plane, &rows[2]);

        camera->frustumPlanesValid = M3G_TRUE;
    }
}

#undef NEAR_PLANE
#undef FAR_PLANE
#undef LEFT_PLANE
#undef RIGHT_PLANE
#undef BOTTOM_PLANE
#undef TOP_PLANE

/*----------------------------------------------------------------------
 * Internal functions
 *--------------------------------------------------------------------*/

/*!
 * \internal
 * \brief Overloaded Object3D method.
 *
 * \param property      animation property
 * \retval M3G_TRUE     property supported
 * \retval M3G_FALSE    property not supported
 */
static M3Gbool m3gCameraIsCompatible(M3Gint property)
{
    switch (property) {
    case M3G_ANIM_FAR_DISTANCE:
    case M3G_ANIM_FIELD_OF_VIEW:
    case M3G_ANIM_NEAR_DISTANCE:
        return M3G_TRUE;
    default:
        return m3gNodeIsCompatible(property);
    }
}

/*!
 * \internal
 * \brief Overloaded Object3D method.
 *
 * \param obj          Camera object
 * \param property      animation property
 * \param valueSize     size of value array
 * \param value         value array
 */
static void m3gCameraUpdateProperty(Object *obj,
                                    M3Gint property,
                                    M3Gint valueSize,
                                    const M3Gfloat *value)
{
    Camera *camera = (Camera *) obj;
    M3G_VALIDATE_OBJECT(camera);
    M3G_ASSERT_PTR(value);

    switch (property) {
    case M3G_ANIM_FAR_DISTANCE:
        M3G_ASSERT(valueSize >= 1);
        camera->clipFar =   (camera->projType == M3G_PERSPECTIVE)
                            ? m3gClampFloatPositive(value[0])
                            : value[0];
        break;
    case M3G_ANIM_FIELD_OF_VIEW:
        M3G_ASSERT(valueSize >= 1);
        camera->heightFov = (camera->projType == M3G_PERSPECTIVE)
                            ? m3gClampFloat(value[0], 0.f, 180.f)
                            : m3gClampFloatPositive(value[0]);
        break;
    case M3G_ANIM_NEAR_DISTANCE:
        M3G_ASSERT(valueSize >= 1);
        camera->clipNear =  (camera->projType == M3G_PERSPECTIVE)
                            ? m3gClampFloatPositive(value[0])
                            : value[0];
        break;
    default:
        m3gNodeUpdateProperty(obj, property, valueSize, value);
        return; /* don't invalidate the matrix */
    }

    /* Validate the projection matrix if we changed any of the
     * camera parameters */

    m3gValidateProjectionMatrix(camera);
}

/*!
 * \internal
 * \brief Overloaded Node method.
 *
 * Start render setup scene traversal.
 *
 * \param node Camera object
 * \param toCamera transform to camera
 * \param alphaFactor total alpha factor
 * \param caller caller node
 * \param renderQueue RenderQueue
 *
 * \retval M3G_TRUE continue render setup
 * \retval M3G_FALSE abort render setup
 */
static M3Gbool m3gCameraSetupRender(Node *self,
                                    const Node *caller,
                                    SetupRenderState *s,
                                    RenderQueue *renderQueue)
{
    Node *parent;
    M3Gbool success = M3G_TRUE;

    /* Just do the parent node.  Note that we won't be needing the old
     * state back after going up the tree, so we can overwrite it. */
    
    parent = self->parent;

    if (caller != parent && parent != NULL) {
        Matrix t;
        
        M3G_BEGIN_PROFILE(M3G_INTERFACE(self), M3G_PROFILE_SETUP_TRANSFORMS);
        if (!m3gGetInverseNodeTransform(self, &t)) {
            return M3G_FALSE;
        }
        m3gMulMatrix(&s->toCamera, &t);
        M3G_END_PROFILE(M3G_INTERFACE(self), M3G_PROFILE_SETUP_TRANSFORMS);

        /* The parent node will update the alpha factor and culling
         * mask if necessary, so we need not touch those */
        
        success = M3G_VFUNC(Node, parent, setupRender)(parent,
                                                       self, s, renderQueue);
    }

    return success;
}

/*!
 * \internal
 * \brief Overloaded Object3D method.
 *
 * \param originalObj original Camera object
 * \param cloneObj pointer to cloned Camera object
 * \param pairs array for all object-duplicate pairs
 * \param numPairs number of pairs
 */
static M3Gbool m3gCameraDuplicate(const Object *originalObj,
                                  Object **cloneObj,
                                  Object **pairs,
                                  M3Gint *numPairs)
{
    Camera *original = (Camera *)originalObj;
    Camera *clone = (Camera *)m3gCreateCamera(originalObj->interface);
    *cloneObj = (Object *)clone;
    if (*cloneObj == NULL) {
        return M3G_FALSE;
    }

    if (m3gNodeDuplicate(originalObj, cloneObj, pairs, numPairs)) {
        clone->projType = original->projType;
        clone->projMatrix = original->projMatrix;
        clone->heightFov = original->heightFov;
        clone->aspect = original->aspect;
        clone->clipNear = original->clipNear;
        clone->clipFar = original->clipFar;
        clone->zeroViewVolume = original->zeroViewVolume;
        return M3G_TRUE;
    }
    else {
        return M3G_FALSE;
    }
}

/*!
 * \internal
 * \brief Initializes a Camera object. See specification
 * for default values.
 *
 * \param m3g           M3G interface
 * \param camera        Camera object
 */
static void m3gInitCamera(Interface *m3g, Camera *camera)
{
    M3GMatrix m;

    /* Camera is derived from node */
    m3gInitNode(m3g, &camera->node, M3G_CLASS_CAMERA);

    /* GENERIC, Identity */
    m3gIdentityMatrix(&m);
    m3gSetProjectionMatrix(camera, (const M3GMatrix *)&m);
}

/*!
 * \internal
 * \brief Sets camera matrix to OpenGL
 * projection matrix.
 *
 * \param camera Camera object
 */
static void m3gApplyProjection(const Camera *camera)
{
    M3Gfloat t[16];

    m3gGetMatrixColumns(&camera->projMatrix, t);
    
    glMatrixMode(GL_PROJECTION);
    glLoadMatrixf(t);
    glMatrixMode(GL_MODELVIEW);
}

/*!
 * \internal
 * \brief Returns a pointer to the camera projection matrix
 *
 * The matrix <em>must not</em> be accessed directly, as only this
 * function will ensure that the returned matrix has valid values in
 * it.
 * 
 * \param camera Camera object
 * \return a pointer to the projection matrix
 */
static const Matrix *m3gProjectionMatrix(const Camera *camera)
{
    M3G_VALIDATE_OBJECT(camera);

    return &camera->projMatrix;
}

/*!
 * \internal
 * \brief Retrieves a pointer to the six camera space view frustum planes
 */
static const Vec4 *m3gFrustumPlanes(const Camera *camera)
{
    M3G_VALIDATE_OBJECT(camera);
    m3gValidateFrustumPlanes((Camera*) camera);
    return camera->frustumPlanes;
}

/*----------------------------------------------------------------------
 * Virtual function table
 *--------------------------------------------------------------------*/

static const NodeVFTable m3gvf_Camera = {
    {
        {
            m3gObjectApplyAnimation,
            m3gCameraIsCompatible,
            m3gCameraUpdateProperty,
            m3gObjectDoGetReferences,
            m3gObjectFindID,
            m3gCameraDuplicate,
            m3gDestroyNode /* no extra clean-up for Camera */
        }
    },
    m3gNodeAlign,
    NULL, /* pure virtual DoRender */
    m3gNodeGetBBox,
    m3gNodeRayIntersect,
    m3gCameraSetupRender,
    m3gNodeUpdateDuplicateReferences,
    m3gNodeValidate
};


/*----------------------------------------------------------------------
 * Public API functions
 *--------------------------------------------------------------------*/

/*!
 * \brief Creates a Camera object.
 *
 * \param interface     M3G interface
 * \retval Camera new Camera object
 * \retval NULL Camera creating failed
 */
M3G_API M3GCamera m3gCreateCamera(M3GInterface interface)
{
    Interface *m3g = (Interface *) interface;
    M3G_VALIDATE_INTERFACE(m3g);

    {
        Camera *camera =  m3gAllocZ(m3g, sizeof(Camera));

        if (camera != NULL) {
            m3gInitCamera(m3g, camera);
        }

        return (M3GCamera) camera;
    }
}

/*!
 * \brief Sets a parallel projection.
 *
 * \param handle        Camera object
 * \param height        height (=fovy)
 * \param aspectRatio   viewport aspect ratio
 * \param clipNear      near clipping plane
 * \param clipFar       far clipping plane
 */
M3G_API void m3gSetParallel(M3GCamera handle,
                            M3Gfloat height,
                            M3Gfloat aspectRatio,
                            M3Gfloat clipNear, M3Gfloat clipFar)
{
    Camera *camera = (Camera *) handle;
    M3G_VALIDATE_OBJECT(camera);

    if (height <= 0 || aspectRatio <= 0.0f) {
        m3gRaiseError(M3G_INTERFACE(camera), M3G_INVALID_VALUE);
        return;
    }

    camera->projType   = M3G_PARALLEL;
    camera->heightFov  = height;
    camera->aspect     = aspectRatio;
    camera->clipNear   = clipNear;
    camera->clipFar    = clipFar;

    m3gValidateProjectionMatrix(camera);
}

/*!
 * \brief Sets a perspective projection.
 *
 * \param handle        Camera object
 * \param fovy          fovy
 * \param aspectRatio   viewport aspect ratio
 * \param clipNear      near clipping plane
 * \param clipFar       far clipping plane
 */
M3G_API void m3gSetPerspective(M3GCamera handle,
                               M3Gfloat fovy,
                               M3Gfloat aspectRatio,
                               M3Gfloat clipNear, M3Gfloat clipFar)
{
    Camera *camera = (Camera *) handle;
    M3G_VALIDATE_OBJECT(camera);

    if (fovy <= 0.0f || fovy >= 180.f
        || aspectRatio <= 0.0f
        || clipNear <= 0.0f || clipFar <= 0.0f) {
        m3gRaiseError(M3G_INTERFACE(camera), M3G_INVALID_VALUE);
        return;
    }

    camera->projType   = M3G_PERSPECTIVE;
    camera->heightFov  = fovy;
    camera->aspect     = aspectRatio;
    camera->clipNear   = clipNear;
    camera->clipFar    = clipFar;

    m3gValidateProjectionMatrix(camera);
}

/*!
 * \brief Sets a generic projection.
 *
 * \param handle        Camera object
 * \param transform     projection matrix
 */
M3G_API void m3gSetProjectionMatrix(M3GCamera handle,
                                    const M3GMatrix *transform)
{
    Camera *camera = (Camera *) handle;
    M3G_VALIDATE_OBJECT(camera);

    if (transform == NULL) {
        m3gRaiseError(M3G_INTERFACE(camera), M3G_NULL_POINTER);
        return;
    }
    
    camera->projType = M3G_GENERIC;    
    m3gCopyMatrix(&camera->projMatrix, transform);
    
    m3gValidateProjectionMatrix(camera);
}

/*!
 * \brief Gets camera matrix.
 *
 * \param handle        Camera object
 * \param transform     projection matrix to fill in
 * \return              projection type
 */
M3G_API M3Gint m3gGetProjectionAsMatrix(M3GCamera handle,
                                        M3GMatrix *transform)
{
    Camera *camera = (Camera *) handle;
    M3G_VALIDATE_OBJECT(camera);

    if (transform != NULL) {
        /* Check for impossible projection matrix */
        if (camera->projType != M3G_GENERIC && 
            camera->clipFar == camera->clipNear) {
            m3gRaiseError(M3G_INTERFACE(camera), M3G_ARITHMETIC_ERROR);
            return 0;
        }

        m3gCopyMatrix(transform, m3gProjectionMatrix(camera));
    }

    return camera->projType;
}

/*!
 * \brief Gets camera parameters.
 *
 * \param handle        Camera object
 * \param params        camera parameters to fill in
 * \return              projection type
 */
M3G_API M3Gint m3gGetProjectionAsParams(M3GCamera handle, M3Gfloat *params)
{
    Camera *camera = (Camera *) handle;
    M3G_VALIDATE_OBJECT(camera);

    if (params != NULL && camera->projType != M3G_GENERIC) {
        params[0] = camera->heightFov;
        params[1] = camera->aspect;
        params[2] = camera->clipNear;
        params[3] = camera->clipFar;
    }

    return camera->projType;
}