m3g/m3gcore11/src/m3g_lightmanager.c
author jakl.martin@cell-telecom.com
Mon, 06 Dec 2010 18:07:30 +0100
branchNewGraphicsArchitecture
changeset 218 99b3451c560e
parent 0 5d03bc08d59c
permissions -rw-r--r--
Fix for Bug 3890

/*
* 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: Light manager implementation
*
*/


/*!
 * \internal
 * \file
 * \brief Light manager implementation
 */

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

#include "m3g_lightmanager.h"
#include "m3g_light.h"
#include "m3g_rendercontext.h"
#include "m3g_math.h"

/*!
 * \internal
 * \brief Light array element
 */
typedef struct
{
    /*! \internal \brief eye space spot direction */
    Vec4 spotDir;

    /*! \internal \brief eye space position */
    Vec4 position;
    
    /*! \internal \brief reference to the Light node */
    Light *light;
} LightRecord;

/*----------------------------------------------------------------------
 * Private functions
 *--------------------------------------------------------------------*/
 
/*!
 * \internal
 * \brief Update a single light record
 *
 * Light position and spot direction are transformed into world space,
 * i.e. omitting the viewing transformation which is applied by OpenGL
 * when rendering. Both transformations are special cases and
 * accomplished by just reading a part of the matrix.
 *
 * \note We need to transform the spotlight direction even if the
 * light is not a spotlight, since in immediate mode, it may change
 * into a spotlight later on!
 */
static void m3gSetLightRecord(LightRecord *lrec,
                              Light *light,
                              const Matrix *tf)
{
    Vec4 v;
    M3G_ASSIGN_REF(lrec->light, light);

    if (tf != NULL) {
        m3gGetMatrixColumn(tf, 3, &lrec->position);
        m3gGetMatrixColumn(tf, 2, &v);
        lrec->spotDir.x = -v.x;
        lrec->spotDir.y = -v.y;
        lrec->spotDir.z = -v.z;
        lrec->spotDir.w = 0.0f;
    }
    else {
        lrec->spotDir.x = lrec->spotDir.y = lrec->spotDir.w = 0.0f;
        lrec->spotDir.z = -1.0f;
        lrec->position.x = lrec->position.y = lrec->position.z = 0.0f;
        lrec->position.w = 1.0f;
    }
}

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

/*!
 * \internal
 * \brief Clears all lights in the current pool of lights
 */
static void m3gClearLights2(LightManager *mgr)
{
    LightRecord *lrec;
    PointerArray *lights;
    int i, n;
    M3G_ASSERT_PTR(mgr);

    lights = &mgr->lights;
    n = m3gArraySize(lights);
    for (i = 0; i < n; ++i) {
        lrec = m3gGetArrayElement(lights, i);
        M3G_ASSIGN_REF(lrec->light, NULL);
    }

    mgr->numActive = 0;
}

/*!
 * \internal
 * \brief Destroys the light manager, freeing allocated resources
 */
static void m3gDestroyLightManager(LightManager *mgr, Interface *m3g)
{
    int i, n;
    M3G_ASSERT_PTR(mgr);
    M3G_VALIDATE_INTERFACE(m3g);

    /* First remove all light references */
    m3gClearLights2(mgr);

    /* Free the records currently in the light array */
    n = m3gArraySize(&mgr->lights);
    for (i = 0; i < n; ++i) {
        m3gFree(m3g, m3gGetArrayElement(&mgr->lights, i));
    }
    m3gDestroyArray(&mgr->lights, m3g);
}

/*!
 * \internal
 * \brief Appends a light at the end of the light manager array
 */
static M3Gint m3gInsertLight(LightManager *mgr,
                             Light *light,
                             const Matrix *tf,
                             Interface *m3g)
{
    LightRecord *lrec;
    PointerArray *lights;
    M3Gint idx;
    M3G_ASSERT_PTR(mgr);
    M3G_VALIDATE_INTERFACE(m3g);

    lights = &mgr->lights;
    
    /* Get the first unused light record, or add a new one */
    
    if (mgr->numActive < m3gArraySize(lights)) {
        lrec = m3gGetArrayElement(lights, mgr->numActive);
    }
    else {
        M3G_ASSERT(mgr->numActive == m3gArraySize(lights));
        lrec = m3gAllocZ(m3g, sizeof(LightRecord));
        if (lrec == NULL) {
            return -1;
        }
        if (m3gArrayAppend(lights, lrec, m3g) == -1) {
            return -1;
        }
    }
    idx = mgr->numActive++;

    m3gSetLightRecord(lrec, light, tf);
    return idx;
}

/*!
 * \internal
 * \brief
 */
static M3Gsizei m3gLightArraySize(const LightManager *mgr)
{
    M3G_ASSERT_PTR(mgr);
    return mgr->numActive;
}

/*!
 * \internal
 * \brief
 */
static Light *m3gGetLightTransformInternal(const LightManager *mgr, M3Gint idx, M3GMatrix *transform)
{
    M3Gfloat matrix[16];
    LightRecord *lrec;
    M3G_ASSERT_PTR(mgr);
    M3G_ASSERT(m3gInRange(idx, 0, mgr->numActive - 1));

    lrec = m3gGetArrayElement(&mgr->lights, idx);

    if (transform != NULL) {
        m3gZero(matrix, sizeof(matrix));
    
        matrix[0 * 4 + 0] = 1.f;
        matrix[1 * 4 + 1] = 1.f;
    
        matrix[2 * 4 + 0] = -lrec->spotDir.x;
        matrix[2 * 4 + 1] = -lrec->spotDir.y;
        matrix[2 * 4 + 2] = -lrec->spotDir.z;
    
        matrix[3 * 4 + 0] = lrec->position.x;
        matrix[3 * 4 + 1] = lrec->position.y;
        matrix[3 * 4 + 2] = lrec->position.z;
        matrix[3 * 4 + 3] = lrec->position.w;
    
        m3gSetMatrixColumns(transform, matrix);
    }

    return lrec->light;
}

/*!
 * \internal
 * \brief Replaces an existing light in the light array
 */
static void m3gReplaceLight(LightManager *mgr,
                            M3Gint idx,
                            Light *light,
                            const Matrix *tf)
{
    LightRecord *lrec;
    M3G_ASSERT_PTR(mgr);
    M3G_ASSERT(m3gInRange(idx, 0, mgr->numActive - 1));

    lrec = m3gGetArrayElement(&mgr->lights, idx);
    m3gSetLightRecord(lrec, light, tf);
}

/*!
 * \internal
 * \brief Selects a set of lights from the current light array for OpenGL
 *
 * Selects a maximum on \c maxNum lights from the array matching the
 * given scope, sets those into the current OpenGL context, and
 * disables the rest of the OpenGL lights GL_LIGHT0..GL_LIGHT7. A
 * maximum of 8 lights is ever used.
 *
 */
static void m3gSelectGLLights(const LightManager *mgr,
                              M3Gsizei maxNum,
                              M3Guint scope,
                              M3Gfloat x, M3Gfloat y, M3Gfloat z)
{
    const PointerArray *lights;
    int i, required, total;
    GLenum glIndex = GL_LIGHT0;
    M3G_ASSERT_PTR(mgr);

    M3G_UNREF(x);
    M3G_UNREF(y);
    M3G_UNREF(z);

    lights = &mgr->lights;
    required = m3gClampInt(maxNum, 0, 8);
    total = mgr->numActive;

    /* Select the first n lights that match the scope */
    
    for (i = 0; required > 0 && i < total; ++i) {
        const LightRecord *lrec;
        const Light *light;
        
        lrec = (const LightRecord *) m3gGetArrayElement(lights, i);
        M3G_ASSERT(lrec != NULL);
        light = lrec->light;
        
        if (light != NULL && (light->node.scope & scope) != 0) {
            m3gApplyLight(light, glIndex++, &lrec->position, &lrec->spotDir);
            --required;
        }
    }

    /* Disable the leftover lights */
    
    while (glIndex <= GL_LIGHT7) {
        glDisable(glIndex++);
    }
}

/*!
 * \internal
 * \brief Transforms all lights with the given matrix
 */
static void m3gTransformLights(LightManager *mgr, const Matrix *mtx)
{
    const PointerArray *lights;
    M3Gint i, n;

    lights = &mgr->lights;
    n = mgr->numActive;
    
    for (i = 0; i < n; ++i) {
        LightRecord *lrec = (LightRecord*) m3gGetArrayElement(lights, i);
        m3gTransformVec4(mtx, &lrec->position);
        m3gTransformVec4(mtx, &lrec->spotDir);
    }
}