sl@0: /* sl@0: * Copyright (c) 2003 Nokia Corporation and/or its subsidiary(-ies). sl@0: * All rights reserved. sl@0: * This component and the accompanying materials are made available sl@0: * under the terms of the License "Eclipse Public License v1.0" sl@0: * which accompanies this distribution, and is available sl@0: * at the URL "http://www.eclipse.org/legal/epl-v10.html". sl@0: * sl@0: * Initial Contributors: sl@0: * Nokia Corporation - initial contribution. sl@0: * sl@0: * Contributors: sl@0: * sl@0: * Description: Camera implementation sl@0: * sl@0: */ sl@0: sl@0: sl@0: /*! sl@0: * \internal sl@0: * \file sl@0: * \brief Camera implementation sl@0: */ sl@0: sl@0: #ifndef M3G_CORE_INCLUDE sl@0: # error included by m3g_core.c; do not compile separately. sl@0: #endif sl@0: sl@0: #include "m3g_camera.h" sl@0: sl@0: /* Internal frustum plane enumeration (and testing order!) */ sl@0: sl@0: #define NEAR_PLANE 0 sl@0: #define FAR_PLANE 1 sl@0: #define LEFT_PLANE 2 sl@0: #define RIGHT_PLANE 3 sl@0: #define BOTTOM_PLANE 4 sl@0: #define TOP_PLANE 5 sl@0: sl@0: /*---------------------------------------------------------------------- sl@0: * Private functions sl@0: *--------------------------------------------------------------------*/ sl@0: sl@0: /*! sl@0: * \internal sl@0: * \brief Makes sure that the internal projection matrix is up-to-date sl@0: * and checks if the camera has a zero view volume. sl@0: */ sl@0: static void m3gValidateProjectionMatrix(Camera *camera) sl@0: { sl@0: M3Gint projType = camera->projType; sl@0: sl@0: /* The generic matrix is always valid, but for perspective and sl@0: * parallel we must regenerate the matrix */ sl@0: sl@0: if (projType != M3G_GENERIC) { sl@0: M3Gfloat m[16]; sl@0: sl@0: M3Gfloat clipNear = camera->clipNear; sl@0: M3Gfloat clipFar = camera->clipFar; sl@0: sl@0: if (projType == M3G_PERSPECTIVE) { sl@0: sl@0: M3Gfloat height = m3gTan(m3gMul(M3G_DEG2RAD * 0.5f, sl@0: camera->heightFov)); sl@0: sl@0: m[0] = m3gRcp(m3gMul(camera->aspect, height)); sl@0: m[1] = m[2] = m[3] = 0.f; sl@0: sl@0: m[4] = 0.f; sl@0: m[5] = m3gRcp(height); sl@0: m[6] = m[7] = 0.f; sl@0: sl@0: m[8] = m[9] = 0.f; sl@0: m[10] = m3gDiv(-m3gAdd(clipFar, clipNear), sl@0: m3gSub(clipFar, clipNear)); sl@0: m[11] = -1.f; sl@0: sl@0: m[12] = m[13] = 0.f; sl@0: m[14] = m3gDiv(m3gMul(m3gMul(-2.f, clipFar), clipNear), sl@0: m3gSub(clipFar, clipNear)); sl@0: m[15] = 0.f; sl@0: } sl@0: else if (projType == M3G_PARALLEL) { sl@0: sl@0: M3Gfloat height = camera->heightFov; sl@0: sl@0: m[0] = m3gDiv(2.f, m3gMul(camera->aspect, height)); sl@0: m[1] = m[2] = m[3] = 0.f; sl@0: sl@0: m[4] = 0.f; sl@0: m[5] = m3gDiv(2.f, height); sl@0: m[6] = m[7] = 0; sl@0: sl@0: m[8] = m[9] = 0; sl@0: m[10] = m3gDiv(-2.f, m3gSub(clipFar, clipNear)); sl@0: m[11] = 0.f; sl@0: sl@0: m[12] = m[13] = 0.f; sl@0: m[14] = m3gDiv(-m3gAdd(clipFar, clipNear), sl@0: m3gSub(clipFar, clipNear)); sl@0: m[15] = 1.f; sl@0: } sl@0: else { sl@0: M3G_ASSERT(M3G_FALSE); /* unknown projection type! */ sl@0: } sl@0: m3gSetMatrixColumns(&camera->projMatrix, m); sl@0: } sl@0: sl@0: { sl@0: M3GMatrix im; sl@0: if (!m3gMatrixInverse(&im, &camera->projMatrix)) { sl@0: camera->zeroViewVolume = M3G_TRUE; sl@0: } sl@0: else { sl@0: camera->zeroViewVolume = M3G_FALSE; sl@0: } sl@0: } sl@0: sl@0: camera->frustumPlanesValid = M3G_FALSE; sl@0: } sl@0: sl@0: /*! sl@0: * \internal sl@0: * \brief Validates the cached view frustum planes sl@0: */ sl@0: static void m3gValidateFrustumPlanes(Camera *camera) sl@0: { sl@0: if (!camera->frustumPlanesValid) { sl@0: Vec4 *plane; sl@0: Vec4 rows[4]; sl@0: sl@0: m3gGetMatrixRows(&camera->projMatrix, (M3Gfloat*) rows); sl@0: sl@0: plane = &camera->frustumPlanes[LEFT_PLANE]; sl@0: *plane = rows[3]; sl@0: m3gAddVec4(plane, &rows[0]); sl@0: sl@0: plane = &camera->frustumPlanes[RIGHT_PLANE]; sl@0: *plane = rows[3]; sl@0: m3gSubVec4(plane, &rows[0]); sl@0: sl@0: plane = &camera->frustumPlanes[BOTTOM_PLANE]; sl@0: *plane = rows[3]; sl@0: m3gAddVec4(plane, &rows[1]); sl@0: sl@0: plane = &camera->frustumPlanes[TOP_PLANE]; sl@0: *plane = rows[3]; sl@0: m3gSubVec4(plane, &rows[1]); sl@0: sl@0: plane = &camera->frustumPlanes[NEAR_PLANE]; sl@0: *plane = rows[3]; sl@0: m3gAddVec4(plane, &rows[2]); sl@0: sl@0: plane = &camera->frustumPlanes[FAR_PLANE]; sl@0: *plane = rows[3]; sl@0: m3gSubVec4(plane, &rows[2]); sl@0: sl@0: camera->frustumPlanesValid = M3G_TRUE; sl@0: } sl@0: } sl@0: sl@0: #undef NEAR_PLANE sl@0: #undef FAR_PLANE sl@0: #undef LEFT_PLANE sl@0: #undef RIGHT_PLANE sl@0: #undef BOTTOM_PLANE sl@0: #undef TOP_PLANE sl@0: sl@0: /*---------------------------------------------------------------------- sl@0: * Internal functions sl@0: *--------------------------------------------------------------------*/ sl@0: sl@0: /*! sl@0: * \internal sl@0: * \brief Overloaded Object3D method. sl@0: * sl@0: * \param property animation property sl@0: * \retval M3G_TRUE property supported sl@0: * \retval M3G_FALSE property not supported sl@0: */ sl@0: static M3Gbool m3gCameraIsCompatible(M3Gint property) sl@0: { sl@0: switch (property) { sl@0: case M3G_ANIM_FAR_DISTANCE: sl@0: case M3G_ANIM_FIELD_OF_VIEW: sl@0: case M3G_ANIM_NEAR_DISTANCE: sl@0: return M3G_TRUE; sl@0: default: sl@0: return m3gNodeIsCompatible(property); sl@0: } sl@0: } sl@0: sl@0: /*! sl@0: * \internal sl@0: * \brief Overloaded Object3D method. sl@0: * sl@0: * \param obj Camera object sl@0: * \param property animation property sl@0: * \param valueSize size of value array sl@0: * \param value value array sl@0: */ sl@0: static void m3gCameraUpdateProperty(Object *obj, sl@0: M3Gint property, sl@0: M3Gint valueSize, sl@0: const M3Gfloat *value) sl@0: { sl@0: Camera *camera = (Camera *) obj; sl@0: M3G_VALIDATE_OBJECT(camera); sl@0: M3G_ASSERT_PTR(value); sl@0: sl@0: switch (property) { sl@0: case M3G_ANIM_FAR_DISTANCE: sl@0: M3G_ASSERT(valueSize >= 1); sl@0: camera->clipFar = (camera->projType == M3G_PERSPECTIVE) sl@0: ? m3gClampFloatPositive(value[0]) sl@0: : value[0]; sl@0: break; sl@0: case M3G_ANIM_FIELD_OF_VIEW: sl@0: M3G_ASSERT(valueSize >= 1); sl@0: camera->heightFov = (camera->projType == M3G_PERSPECTIVE) sl@0: ? m3gClampFloat(value[0], 0.f, 180.f) sl@0: : m3gClampFloatPositive(value[0]); sl@0: break; sl@0: case M3G_ANIM_NEAR_DISTANCE: sl@0: M3G_ASSERT(valueSize >= 1); sl@0: camera->clipNear = (camera->projType == M3G_PERSPECTIVE) sl@0: ? m3gClampFloatPositive(value[0]) sl@0: : value[0]; sl@0: break; sl@0: default: sl@0: m3gNodeUpdateProperty(obj, property, valueSize, value); sl@0: return; /* don't invalidate the matrix */ sl@0: } sl@0: sl@0: /* Validate the projection matrix if we changed any of the sl@0: * camera parameters */ sl@0: sl@0: m3gValidateProjectionMatrix(camera); sl@0: } sl@0: sl@0: /*! sl@0: * \internal sl@0: * \brief Overloaded Node method. sl@0: * sl@0: * Start render setup scene traversal. sl@0: * sl@0: * \param node Camera object sl@0: * \param toCamera transform to camera sl@0: * \param alphaFactor total alpha factor sl@0: * \param caller caller node sl@0: * \param renderQueue RenderQueue sl@0: * sl@0: * \retval M3G_TRUE continue render setup sl@0: * \retval M3G_FALSE abort render setup sl@0: */ sl@0: static M3Gbool m3gCameraSetupRender(Node *self, sl@0: const Node *caller, sl@0: SetupRenderState *s, sl@0: RenderQueue *renderQueue) sl@0: { sl@0: Node *parent; sl@0: M3Gbool success = M3G_TRUE; sl@0: sl@0: /* Just do the parent node. Note that we won't be needing the old sl@0: * state back after going up the tree, so we can overwrite it. */ sl@0: sl@0: parent = self->parent; sl@0: sl@0: if (caller != parent && parent != NULL) { sl@0: Matrix t; sl@0: sl@0: M3G_BEGIN_PROFILE(M3G_INTERFACE(self), M3G_PROFILE_SETUP_TRANSFORMS); sl@0: if (!m3gGetInverseNodeTransform(self, &t)) { sl@0: return M3G_FALSE; sl@0: } sl@0: m3gMulMatrix(&s->toCamera, &t); sl@0: M3G_END_PROFILE(M3G_INTERFACE(self), M3G_PROFILE_SETUP_TRANSFORMS); sl@0: sl@0: /* The parent node will update the alpha factor and culling sl@0: * mask if necessary, so we need not touch those */ sl@0: sl@0: success = M3G_VFUNC(Node, parent, setupRender)(parent, sl@0: self, s, renderQueue); sl@0: } sl@0: sl@0: return success; sl@0: } sl@0: sl@0: /*! sl@0: * \internal sl@0: * \brief Overloaded Object3D method. sl@0: * sl@0: * \param originalObj original Camera object sl@0: * \param cloneObj pointer to cloned Camera object sl@0: * \param pairs array for all object-duplicate pairs sl@0: * \param numPairs number of pairs sl@0: */ sl@0: static M3Gbool m3gCameraDuplicate(const Object *originalObj, sl@0: Object **cloneObj, sl@0: Object **pairs, sl@0: M3Gint *numPairs) sl@0: { sl@0: Camera *original = (Camera *)originalObj; sl@0: Camera *clone = (Camera *)m3gCreateCamera(originalObj->interface); sl@0: *cloneObj = (Object *)clone; sl@0: if (*cloneObj == NULL) { sl@0: return M3G_FALSE; sl@0: } sl@0: sl@0: if (m3gNodeDuplicate(originalObj, cloneObj, pairs, numPairs)) { sl@0: clone->projType = original->projType; sl@0: clone->projMatrix = original->projMatrix; sl@0: clone->heightFov = original->heightFov; sl@0: clone->aspect = original->aspect; sl@0: clone->clipNear = original->clipNear; sl@0: clone->clipFar = original->clipFar; sl@0: clone->zeroViewVolume = original->zeroViewVolume; sl@0: return M3G_TRUE; sl@0: } sl@0: else { sl@0: return M3G_FALSE; sl@0: } sl@0: } sl@0: sl@0: /*! sl@0: * \internal sl@0: * \brief Initializes a Camera object. See specification sl@0: * for default values. sl@0: * sl@0: * \param m3g M3G interface sl@0: * \param camera Camera object sl@0: */ sl@0: static void m3gInitCamera(Interface *m3g, Camera *camera) sl@0: { sl@0: M3GMatrix m; sl@0: sl@0: /* Camera is derived from node */ sl@0: m3gInitNode(m3g, &camera->node, M3G_CLASS_CAMERA); sl@0: sl@0: /* GENERIC, Identity */ sl@0: m3gIdentityMatrix(&m); sl@0: m3gSetProjectionMatrix(camera, (const M3GMatrix *)&m); sl@0: } sl@0: sl@0: /*! sl@0: * \internal sl@0: * \brief Sets camera matrix to OpenGL sl@0: * projection matrix. sl@0: * sl@0: * \param camera Camera object sl@0: */ sl@0: static void m3gApplyProjection(const Camera *camera) sl@0: { sl@0: M3Gfloat t[16]; sl@0: sl@0: m3gGetMatrixColumns(&camera->projMatrix, t); sl@0: sl@0: glMatrixMode(GL_PROJECTION); sl@0: glLoadMatrixf(t); sl@0: glMatrixMode(GL_MODELVIEW); sl@0: } sl@0: sl@0: /*! sl@0: * \internal sl@0: * \brief Returns a pointer to the camera projection matrix sl@0: * sl@0: * The matrix must not be accessed directly, as only this sl@0: * function will ensure that the returned matrix has valid values in sl@0: * it. sl@0: * sl@0: * \param camera Camera object sl@0: * \return a pointer to the projection matrix sl@0: */ sl@0: static const Matrix *m3gProjectionMatrix(const Camera *camera) sl@0: { sl@0: M3G_VALIDATE_OBJECT(camera); sl@0: sl@0: return &camera->projMatrix; sl@0: } sl@0: sl@0: /*! sl@0: * \internal sl@0: * \brief Retrieves a pointer to the six camera space view frustum planes sl@0: */ sl@0: static const Vec4 *m3gFrustumPlanes(const Camera *camera) sl@0: { sl@0: M3G_VALIDATE_OBJECT(camera); sl@0: m3gValidateFrustumPlanes((Camera*) camera); sl@0: return camera->frustumPlanes; sl@0: } sl@0: sl@0: /*---------------------------------------------------------------------- sl@0: * Virtual function table sl@0: *--------------------------------------------------------------------*/ sl@0: sl@0: static const NodeVFTable m3gvf_Camera = { sl@0: { sl@0: { sl@0: m3gObjectApplyAnimation, sl@0: m3gCameraIsCompatible, sl@0: m3gCameraUpdateProperty, sl@0: m3gObjectDoGetReferences, sl@0: m3gObjectFindID, sl@0: m3gCameraDuplicate, sl@0: m3gDestroyNode /* no extra clean-up for Camera */ sl@0: } sl@0: }, sl@0: m3gNodeAlign, sl@0: NULL, /* pure virtual DoRender */ sl@0: m3gNodeGetBBox, sl@0: m3gNodeRayIntersect, sl@0: m3gCameraSetupRender, sl@0: m3gNodeUpdateDuplicateReferences, sl@0: m3gNodeValidate sl@0: }; sl@0: sl@0: sl@0: /*---------------------------------------------------------------------- sl@0: * Public API functions sl@0: *--------------------------------------------------------------------*/ sl@0: sl@0: /*! sl@0: * \brief Creates a Camera object. sl@0: * sl@0: * \param interface M3G interface sl@0: * \retval Camera new Camera object sl@0: * \retval NULL Camera creating failed sl@0: */ sl@0: M3G_API M3GCamera m3gCreateCamera(M3GInterface interface) sl@0: { sl@0: Interface *m3g = (Interface *) interface; sl@0: M3G_VALIDATE_INTERFACE(m3g); sl@0: sl@0: { sl@0: Camera *camera = m3gAllocZ(m3g, sizeof(Camera)); sl@0: sl@0: if (camera != NULL) { sl@0: m3gInitCamera(m3g, camera); sl@0: } sl@0: sl@0: return (M3GCamera) camera; sl@0: } sl@0: } sl@0: sl@0: /*! sl@0: * \brief Sets a parallel projection. sl@0: * sl@0: * \param handle Camera object sl@0: * \param height height (=fovy) sl@0: * \param aspectRatio viewport aspect ratio sl@0: * \param clipNear near clipping plane sl@0: * \param clipFar far clipping plane sl@0: */ sl@0: M3G_API void m3gSetParallel(M3GCamera handle, sl@0: M3Gfloat height, sl@0: M3Gfloat aspectRatio, sl@0: M3Gfloat clipNear, M3Gfloat clipFar) sl@0: { sl@0: Camera *camera = (Camera *) handle; sl@0: M3G_VALIDATE_OBJECT(camera); sl@0: sl@0: if (height <= 0 || aspectRatio <= 0.0f) { sl@0: m3gRaiseError(M3G_INTERFACE(camera), M3G_INVALID_VALUE); sl@0: return; sl@0: } sl@0: sl@0: camera->projType = M3G_PARALLEL; sl@0: camera->heightFov = height; sl@0: camera->aspect = aspectRatio; sl@0: camera->clipNear = clipNear; sl@0: camera->clipFar = clipFar; sl@0: sl@0: m3gValidateProjectionMatrix(camera); sl@0: } sl@0: sl@0: /*! sl@0: * \brief Sets a perspective projection. sl@0: * sl@0: * \param handle Camera object sl@0: * \param fovy fovy sl@0: * \param aspectRatio viewport aspect ratio sl@0: * \param clipNear near clipping plane sl@0: * \param clipFar far clipping plane sl@0: */ sl@0: M3G_API void m3gSetPerspective(M3GCamera handle, sl@0: M3Gfloat fovy, sl@0: M3Gfloat aspectRatio, sl@0: M3Gfloat clipNear, M3Gfloat clipFar) sl@0: { sl@0: Camera *camera = (Camera *) handle; sl@0: M3G_VALIDATE_OBJECT(camera); sl@0: sl@0: if (fovy <= 0.0f || fovy >= 180.f sl@0: || aspectRatio <= 0.0f sl@0: || clipNear <= 0.0f || clipFar <= 0.0f) { sl@0: m3gRaiseError(M3G_INTERFACE(camera), M3G_INVALID_VALUE); sl@0: return; sl@0: } sl@0: sl@0: camera->projType = M3G_PERSPECTIVE; sl@0: camera->heightFov = fovy; sl@0: camera->aspect = aspectRatio; sl@0: camera->clipNear = clipNear; sl@0: camera->clipFar = clipFar; sl@0: sl@0: m3gValidateProjectionMatrix(camera); sl@0: } sl@0: sl@0: /*! sl@0: * \brief Sets a generic projection. sl@0: * sl@0: * \param handle Camera object sl@0: * \param transform projection matrix sl@0: */ sl@0: M3G_API void m3gSetProjectionMatrix(M3GCamera handle, sl@0: const M3GMatrix *transform) sl@0: { sl@0: Camera *camera = (Camera *) handle; sl@0: M3G_VALIDATE_OBJECT(camera); sl@0: sl@0: if (transform == NULL) { sl@0: m3gRaiseError(M3G_INTERFACE(camera), M3G_NULL_POINTER); sl@0: return; sl@0: } sl@0: sl@0: camera->projType = M3G_GENERIC; sl@0: m3gCopyMatrix(&camera->projMatrix, transform); sl@0: sl@0: m3gValidateProjectionMatrix(camera); sl@0: } sl@0: sl@0: /*! sl@0: * \brief Gets camera matrix. sl@0: * sl@0: * \param handle Camera object sl@0: * \param transform projection matrix to fill in sl@0: * \return projection type sl@0: */ sl@0: M3G_API M3Gint m3gGetProjectionAsMatrix(M3GCamera handle, sl@0: M3GMatrix *transform) sl@0: { sl@0: Camera *camera = (Camera *) handle; sl@0: M3G_VALIDATE_OBJECT(camera); sl@0: sl@0: if (transform != NULL) { sl@0: /* Check for impossible projection matrix */ sl@0: if (camera->projType != M3G_GENERIC && sl@0: camera->clipFar == camera->clipNear) { sl@0: m3gRaiseError(M3G_INTERFACE(camera), M3G_ARITHMETIC_ERROR); sl@0: return 0; sl@0: } sl@0: sl@0: m3gCopyMatrix(transform, m3gProjectionMatrix(camera)); sl@0: } sl@0: sl@0: return camera->projType; sl@0: } sl@0: sl@0: /*! sl@0: * \brief Gets camera parameters. sl@0: * sl@0: * \param handle Camera object sl@0: * \param params camera parameters to fill in sl@0: * \return projection type sl@0: */ sl@0: M3G_API M3Gint m3gGetProjectionAsParams(M3GCamera handle, M3Gfloat *params) sl@0: { sl@0: Camera *camera = (Camera *) handle; sl@0: M3G_VALIDATE_OBJECT(camera); sl@0: sl@0: if (params != NULL && camera->projType != M3G_GENERIC) { sl@0: params[0] = camera->heightFov; sl@0: params[1] = camera->aspect; sl@0: params[2] = camera->clipNear; sl@0: params[3] = camera->clipFar; sl@0: } sl@0: sl@0: return camera->projType; sl@0: } sl@0: