m3g/m3gcore11/src/m3g_object.c
author Dremov Kirill (Nokia-D-MSW/Tampere) <kirill.dremov@nokia.com>
Tue, 02 Feb 2010 01:47:50 +0200
changeset 0 5d03bc08d59c
child 26 15986eb6c500
permissions -rw-r--r--
Revision: 201003 Kit: 201005

/*
* 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: Base object class implementation
*
*/


/*!
 * \internal
 * \file
 * \brief Base object class implementation
 *
 */

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

/*----------------------------------------------------------------------
 * Constructor & destructor
 *--------------------------------------------------------------------*/

/*!
 * \internal
 * \brief Constructor for all Objects
 *
 * The reference count for new objects is initialized to zero; the
 * object pointer must be stored using \c m3gSetRef, or m3gAddRef
 * called explicitly, to increase this to one.
 */
static void m3gInitObject(Object *obj,
                          Interface *interface,
                          M3GClass classID)
{
    M3G_ASSERT_PTR(obj);
    M3G_VALIDATE_INTERFACE(interface);

    M3G_ASSERT(m3gInRange(classID,
                          M3G_CLASS_ANIMATION_CONTROLLER, M3G_CLASS_WORLD));
    
    obj->classID = (M3Guint) classID;
    obj->interface = interface;
    obj->animTracks = NULL;
    obj->refCount = 0u;
    
    M3G_VALIDATE_OBJECT(obj);

    m3gAddChildObject(interface);
    m3gMarkObject(obj);
    
    m3gIncStat(M3G_INTERFACE(obj), M3G_STAT_OBJECTS, 1);
    M3G_LOG2(M3G_LOG_OBJECTS, "New %s 0x%08X\n",
             m3gClassName((M3GClass) obj->classID),
             (unsigned) obj);
}

/*!
 * \internal
 * \brief Destructor for all Objects
 */
static void m3gDestroyObject(Object *obj)
{
    M3G_VALIDATE_OBJECT(obj);
    M3G_ASSERT(m3gIsObject(obj));

    if (obj->animTracks != NULL) {
        int n = m3gArraySize(obj->animTracks);
        int i;

        for (i = 0; i < n; ++i) {
            M3GObject hTrk = (M3GObject) m3gGetArrayElement(obj->animTracks, i);
            m3gDeleteRef(hTrk);
        }
        m3gDestroyArray(obj->animTracks, M3G_INTERFACE(obj));
        m3gFree(obj->interface, obj->animTracks);
    }

    m3gDelChildObject(obj->interface);
    m3gUnmarkObject(obj);
    
    m3gIncStat(M3G_INTERFACE(obj), M3G_STAT_OBJECTS, -1);
    M3G_LOG2(M3G_LOG_OBJECTS, "Destroyed %s 0x%08X\n",
             m3gClassName((M3GClass) obj->classID),
             (unsigned) obj);
}

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

/*!
 * \internal
 * \brief Sets an object reference to a new value and updates the
 * reference count accordingly
 *
 * Note that this may lead to the originally referenced object being
 * destroyed.
 *
 * \param ref Pointer to the reference (pointer) to set to a new value
 * \param obj New value of the reference
 */
static void m3gSetRef(Object **ref, Object *obj)
{
    M3G_ASSERT_PTR(ref);

    if (*ref != obj) {
        if (obj != NULL) {
            m3gAddRef((M3GObject) obj);
        }
        if (*ref != NULL) {
            m3gDeleteRef((M3GObject) *ref);
        }
        *ref = obj;
    }
}

#if defined(M3G_DEBUG)
/*!
 * \internal
 * \brief Checks the integrity of an Object-derived object
 */
static void m3gValidateObject(const void *pObj)
{
    const Object *obj = (const Object *) pObj;
    M3G_VALIDATE_MEMBLOCK(obj);
    M3G_VALIDATE_MEMBLOCK(obj->interface);
    M3G_ASSERT(m3gInRange(obj->classID,
                          M3G_CLASS_ANIMATION_CONTROLLER, M3G_CLASS_WORLD));
    M3G_ASSERT_PTR(m3gGetVFTable(obj));
}
#endif /* M3G_DEBUG */


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

/*!
 * \internal
 * \brief Default \c applyAnimation function implementation
 */
static M3Gint m3gObjectApplyAnimation(Object *self, M3Gint time)
{
    Interface *m3g = M3G_INTERFACE(self);
    M3Gint validity = 0x7FFFFFFF;
    M3Gint trackIndex, numTracks;
    M3Gfloat stackSampleVector[4];
    const PointerArray *tracks = self->animTracks;

    /* Quick exit if no animation tracks */

    if (tracks == NULL) {
        return validity;
    }

    /* Loop through the tracks. Note that the tracks are ordered so
     * that tracks targeting the same property are adjacent in the
     * array; this makes animation blending easier. */

    numTracks = m3gArraySize(tracks);

    for (trackIndex = 0; trackIndex < numTracks; ) {
        const AnimationTrack *track = (const AnimationTrack *)
            m3gGetArrayElement(tracks, trackIndex);
        const KeyframeSequence *sequence = track->sequence;

        M3Gint components = sequence->numComponents;
        M3Gint property = track->property;
        M3Gint nextProperty;

        M3Gfloat sumWeights = 0;
        M3Gfloat *sumValues;

        /* Collect the contributions from all the tracks targeting the
         * same property */

        if (components <= 4) {
            sumValues = stackSampleVector;
        }
        else {
            sumValues = (M3Gfloat *)
                m3gAlloc(m3g, components * sizeof(M3Gfloat));
            if (sumValues == NULL) {
                return 0;
            }
        }

        m3gZero(sumValues, components * sizeof(M3Gfloat));

        do {
            SampleInfo sampleInfo;
            
            m3gGetContribution(track, time, sumValues, &sampleInfo);
            if (sampleInfo.validity <= 0) {
                return 0;
            }
            sumWeights += sampleInfo.weight;
            validity = M3G_MIN(validity, sampleInfo.validity);

            if (++trackIndex == numTracks) {
                break;
            }
            track = (const AnimationTrack *) m3gGetArrayElement(tracks,
                                                                trackIndex);
            nextProperty = track->property;
        } while (nextProperty == property);

        if (sumWeights > 0) {
            M3G_VFUNC(Object, self, updateProperty)(
                self, property, components, sumValues);
        }
        if (sumValues != stackSampleVector) {
            m3gFree(m3g, sumValues);
        }
    }

    return validity;
}

/*!
 * \internal
 * \brief Default \c isCompatible function implementation
 */
static M3Gbool m3gObjectIsCompatible(M3Gint property)
{
    M3G_UNREF(property);
    
    return M3G_FALSE;
}

/*!
 * \internal
 * \brief Default \c updateProperty function implementation
 *
 * Silently ignoring an update request for a non-existent object
 * property does no harm, so this just asserts in debug builds and is
 * NOP otherwise.
 */
static void m3gObjectUpdateProperty(Object *self,
                                    M3Gint property,
                                    M3Gint valueSize,
                                    const M3Gfloat *value)
{
    M3G_UNREF(self);
    M3G_UNREF(property);
    M3G_UNREF(valueSize);
    M3G_UNREF(value);
    
    M3G_ASSERT(M3G_FALSE);
}

/*!
 * \internal
 * \brief Default \c getReferences function implementation
 */
static M3Gint m3gObjectDoGetReferences(Object *self, Object **references)
{
    M3Gint i;
    if (self->animTracks != NULL) {
        if (references != NULL) {
            for (i = 0; i < m3gArraySize(self->animTracks); ++i) {
                references[i] = (Object *)m3gGetArrayElement(self->animTracks, i);
            }
        }
        return m3gArraySize(self->animTracks);
    }
    return 0;
}

/*!
 * \internal
 * \brief Default \c find implementation
 */
static Object *m3gObjectFindID(Object *self, M3Gint userID)
{
    M3Gint i;

    if (self->userID == userID) {
        return self;
    }
    
    if (self->animTracks) {
        for (i = 0; i < m3gArraySize(self->animTracks); ++i) {
            Object *found =
                m3gFindID((Object *) m3gGetArrayElement(self->animTracks, i),
                          userID);
            if (found) {
                return found;
            }
        }
    }
    
    return NULL;
}

/*!
 * \internal
 * \brief Default \c duplicate function implementation
 */
static M3Gbool m3gObjectDuplicate(const Object *original,
                                  Object **clone,
                                  Object **pairs,
                                  M3Gint *numPairs)
{
    Interface *m3g = original->interface;
    M3G_ASSERT_PTR(*clone); /* abstract class, must be derived */

    pairs[2 * (*numPairs)] = (Object *)original;
    pairs[2 * (*numPairs) + 1] = *clone;
    (*numPairs)++;

    /* Copy basic object properties */
    
    (*clone)->interface = m3g;
    (*clone)->classID   = original->classID;
    (*clone)->userID    = original->userID;

    /* Copy animation tracks.  This may fail due to out-of-memory, so
     * we check for that; clean-up will be handled by the derived
     * class method. */
    
    if (original->animTracks != NULL) {
        M3Gsizei numTracks = m3gArraySize(original->animTracks);
        M3Gint i;

        /* Allocate the track array and make sure it has enough room
         * for holding the tracks we're about to copy */
        
        PointerArray *animTracks =
            (PointerArray*) m3gAlloc(m3g, sizeof(PointerArray));
        if (animTracks == NULL) {
            return M3G_FALSE; /* out of memory */
        }        
        (*clone)->animTracks = animTracks;

        m3gInitArray(animTracks);
        if (!m3gEnsureArrayCapacity(animTracks, numTracks, m3g)) {
            return M3G_FALSE; /* out of memory */
        }                           

        /* Copy tracks one-by-one and update references.  This can no
         * longer fail, as the capacity request above has been
         * satisfied */
        
        for (i = 0; i < numTracks; ++i) {
            AnimationTrack *track =
                (AnimationTrack *) m3gGetArrayElement(original->animTracks, i);

            if (m3gArrayAppend(animTracks, track, m3g) != i) {
                M3G_ASSERT(M3G_FALSE);
            }
            m3gAddRef((Object *) track);
        }
    }
    return M3G_TRUE;
}

#if defined(M3G_LOGLEVEL)
/*!
 * \internal
 * \brief Returns the name of an object class
 */
static const char *m3gClassName(M3GClass classID)
{
    switch (classID) {
    case M3G_CLASS_ANIMATION_CONTROLLER:
        return "AnimationController";
    case M3G_CLASS_ANIMATION_TRACK:
        return "AnimationTrack";
    case M3G_CLASS_APPEARANCE:
        return "Appearance";
    case M3G_CLASS_BACKGROUND:
        return "Background";
    case M3G_CLASS_CAMERA:
        return "Camera";
    case M3G_CLASS_COMPOSITING_MODE:
        return "CompositingMode";
    case M3G_CLASS_FOG:
        return "Fog";
    case M3G_CLASS_GROUP:
        return "Group";
    case M3G_CLASS_IMAGE:
        return "Image";
    case M3G_CLASS_INDEX_BUFFER:
        return "IndexBuffer";
    case M3G_CLASS_KEYFRAME_SEQUENCE:
        return "KeyframeSequence";
    case M3G_CLASS_LIGHT:
        return "Light";
    case M3G_CLASS_LOADER:
        return "Loader";
    case M3G_CLASS_MATERIAL:
        return "Material";
    case M3G_CLASS_MESH:
        return "Mesh";
    case M3G_CLASS_MORPHING_MESH:
        return "MorphingMesh";
    case M3G_CLASS_POLYGON_MODE:
        return "PolygonMode";
    case M3G_CLASS_RENDER_CONTEXT:
        return "RenderContext";
    case M3G_CLASS_SKINNED_MESH:
        return "SkinnedMesh";
    case M3G_CLASS_SPRITE:
        return "Sprite";
    case M3G_CLASS_TEXTURE:
        return "Texture";
    case M3G_CLASS_VERTEX_ARRAY:
        return "VertexArray";
    case M3G_CLASS_VERTEX_BUFFER:
        return "VertexBuffer";
    case M3G_CLASS_WORLD:
        return "World";
    default:
        return "<abstract class?>";
    }
}
#endif /* defined(M3G_LOGLEVEL) */

/*----------------------------------------------------------------------
 * Public interface functions
 *--------------------------------------------------------------------*/

/*!
 * \brief Deletes an M3G object
 *
 * Similarly to m3gDeleteRef, the object will still remain until all
 * references to it are deleted.  The difference from m3gDeleteRef is
 * mostly stylistic: m3gDeleteObject is meant to be called by the
 * owner of an object, while m3gDeleteRef should be used by users of
 * the object.  Functionally, they are equivalent in all normal use
 * cases.
 *
 * \note The only functional differences between m3gDeleteObject and
 * m3gDeleteRef are that m3gDeleteObject can be used on an object with
 * a reference count of zero, while m3gDeleteRef asserts against this
 * in debug builds; and m3gDeleteObject accepts a NULL object.
 */
/*@access M3GObject@*/
M3G_API void m3gDeleteObject(M3GObject hObject)
{
    Interface *m3g;
    Object *obj = (Object *) hObject;

    if (obj != NULL) {
        M3G_VALIDATE_OBJECT(obj);

        if (obj->refCount > 0) {
            m3gDeleteRef(obj);
        }
        else {
            M3G_LOG2(M3G_LOG_REFCOUNT,
                     "Deleting %s 0x%08X\n",
                     m3gClassName((M3GClass) obj->classID),
                     (unsigned) obj);
            
            m3g = obj->interface;
            M3G_VALIDATE_INTERFACE(m3g);
            
            M3G_ASSERT(m3gGetVFTable(obj)->destroy != NULL);
            
            M3G_VFUNC(Object, obj, destroy)(obj);
            m3gFree(m3g, obj);
        }
    }
}

/*!
 * \brief Notifies that a new reference to an object has been created
 *
 * An object will not be deleted while references to it exist.
 */
M3G_API void m3gAddRef(M3GObject hObject)
{
    Object *obj = (Object *) hObject;
    M3G_VALIDATE_OBJECT(obj);

    M3G_LOG3(M3G_LOG_REFCOUNT,
             "Adding ref to 0x%08X (%s), new count %u\n",
             (unsigned) obj,
             m3gClassName((M3GClass) obj->classID),
             (unsigned) (obj->refCount + 1));

    M3G_ASSERT(obj->refCount < 0xFFFFFF);
    ++obj->refCount;
}

/*!
 * \brief Notifies that a reference to an object has been deleted
 *
 * If the reference count for an object reaches zero, the object is
 * automatically destroyed.
 */
M3G_API void m3gDeleteRef(M3GObject hObject)
{
    Object *obj = (Object *) hObject;
    M3G_VALIDATE_OBJECT(obj);

    M3G_ASSERT(obj->refCount > 0);

    M3G_LOG3(M3G_LOG_REFCOUNT,
             "Deleting ref to 0x%08X (%s), new count %u\n",
             (unsigned) obj,
             m3gClassName((M3GClass) obj->classID),
             (unsigned) (obj->refCount - 1));

    if (--obj->refCount == 0) {
        m3gDeleteObject(hObject);
    }
}

/*!
 * \brief Returns the run-time class of an object
 */
M3G_API M3GClass m3gGetClass(M3GObject hObject)
{
    Object *obj = (Object *) hObject;
    M3G_VALIDATE_OBJECT(obj);
    return M3G_CLASS(obj);
}

/*!
 * \brief Returns the interface owning this object
 */
M3G_API M3GInterface m3gGetObjectInterface(M3GObject hObject)
{
    Object *obj = (Object *) hObject;
    M3G_VALIDATE_OBJECT(obj);
    return obj->interface;
}

/* ---------------- Object3D functions ---------------- */

/*!
 *
 */
M3G_API M3Gint m3gAddAnimationTrack(M3GObject hObject,
                                    M3GAnimationTrack hAnimationTrack)
{
    AnimationTrack *track = (AnimationTrack *)hAnimationTrack;
    Object *obj = (Object *) hObject;
    Interface *m3g = M3G_INTERFACE(obj);
    M3G_VALIDATE_OBJECT(obj);

    /* Check for errors */

    if (!M3G_VFUNC(Object, obj, isCompatible)(track->property)) {
        m3gRaiseError(m3g, M3G_INVALID_OBJECT);
        return -1;
    }

     /* Allocate animation track array only when adding animations for
      * the first time */

    if (obj->animTracks == NULL) {
        obj->animTracks = (PointerArray*) m3gAlloc(m3g, sizeof(PointerArray));
        if (obj->animTracks == NULL) return 0;
        m3gInitArray(obj->animTracks);
    }

    /*  The animation tracks are maintained in a sorted order based on
     *  their target property enumerations.  This keeps all tracks
     *  targeting the same property adjacent so that we can easily
     *  handle animation blending. */
    {
        PointerArray *trackArray = obj->animTracks;
        M3Gsizei numTracks = m3gArraySize(trackArray);
        M3Gint i;

        for (i = 0; i < numTracks; ++i) {

            const AnimationTrack *arrayTrack =
                (const AnimationTrack *) m3gGetArrayElement(trackArray, i);

            if (arrayTrack->property > track->property) {
                break;
            }

            if ((track == arrayTrack) ||
                (   (track->property == arrayTrack->property) &&
                    (track->sequence->numComponents != arrayTrack->sequence->numComponents))) {

                    m3gRaiseError(m3g, M3G_INVALID_OBJECT);
                    return -1;
                }
        }

        if (m3gArrayInsert(trackArray, i, track, m3g) < 0) {
            return -1;
        }
        m3gAddRef((M3GObject) track);

        return i;
    }
}

/*!
 *
 */
M3G_API void m3gRemoveAnimationTrack(M3GObject hObject,
                                     M3GAnimationTrack hAnimationTrack)
{
    AnimationTrack *track = (AnimationTrack *)hAnimationTrack;
    Object *obj = (Object *) hObject;
    M3G_VALIDATE_OBJECT(obj);

    /* Remove the track from the array, and if no tracks remain,
     * delete the array, too */

    if (track != NULL && obj->animTracks != NULL) {
        M3Gint i = m3gArrayFind(obj->animTracks, track);

        if (i != -1) {
            m3gArrayDelete(obj->animTracks, i);
            m3gDeleteRef((Object *) track);

            if (m3gArraySize(obj->animTracks) == 0) {
                m3gDestroyArray(obj->animTracks, M3G_INTERFACE(obj));
                m3gFree(M3G_INTERFACE(obj), obj->animTracks);
                obj->animTracks = NULL;
            }
        }
    }
}

/*!
 *
 */
M3G_API M3Gint m3gGetAnimationTrackCount(M3GObject hObject)
{
    Object *obj = (Object *) hObject;
    M3G_VALIDATE_OBJECT(obj);

    return (obj->animTracks == NULL ? 0 : m3gArraySize(obj->animTracks));
}

M3G_API M3GAnimationTrack m3gGetAnimationTrack(M3GObject hObject, M3Gint idx)
{
    Object *obj = (Object *) hObject;
    M3G_VALIDATE_OBJECT(obj);

    /* idx must be in range [0, to size of array - 1] */
    if (obj->animTracks == NULL
            || !m3gInRange(idx, 0, m3gArraySize(obj->animTracks) - 1)) {
        m3gRaiseError(M3G_INTERFACE(obj), M3G_INVALID_INDEX);
        return NULL;
    }

    return (M3GAnimationTrack) m3gGetArrayElement(obj->animTracks, idx);
}

M3G_API M3Gint m3gAnimate(M3GObject hObject, M3Gint time)
{
    M3Gint validity;
    Object *obj = (Object *) hObject;

    M3G_LOG2(M3G_LOG_STAGES,
             "Animating %s 0x%08X\n",
             m3gClassName((M3GClass) obj->classID), (unsigned) obj);
    
    M3G_VALIDATE_OBJECT(obj);
    
    M3G_BEGIN_PROFILE(M3G_INTERFACE(obj), M3G_PROFILE_ANIM);
    validity = M3G_VFUNC(Object, obj, applyAnimation)(obj, time);
    M3G_END_PROFILE(M3G_INTERFACE(obj), M3G_PROFILE_ANIM);
    
    return validity;
}

/*!
 * \brief Sets userID for this object
*/
M3G_API void m3gSetUserID(M3GObject hObject, M3Gint userID)
{
    Object *obj = (Object *) hObject;
    M3G_VALIDATE_OBJECT(obj);
	obj->userID = userID;
}

/*!
 * \brief Gets userID of this object
*/
M3G_API M3Gint m3gGetUserID(M3GObject hObject)
{
    Object *obj = (Object *) hObject;
    M3G_VALIDATE_OBJECT(obj);

	return obj->userID;
}

/*!
 * \brief Creates a duplicate of this Object3D
*/
M3G_API M3GObject m3gDuplicate(M3GObject hObject, M3GObject *hReferences)
{
    Object **references = (Object **)hReferences;
    const Object *obj = (const Object *) hObject;
    Object *clone = NULL;
    M3Gint numRef = 0;

    M3G_LOG2(M3G_LOG_STAGES|M3G_LOG_OBJECTS,
             "Duplicating %s 0x%08X\n",
             m3gClassName((M3GClass) obj->classID), (unsigned) obj);

    M3G_VALIDATE_OBJECT(obj);
    
    /* Clone the object (or subtree) */
    if (!M3G_VFUNC(Object, obj, duplicate)(obj, &clone, references, &numRef)) {
        m3gDeleteObject(clone);
        return NULL; /* failed; out of memory will have been thrown */
    }

    /* NOTE This will have to change (the virtual function moved to
     * the Object class) if we add classes where child objects may get
     * duplicated */
    
    if (clone->classID == M3G_CLASS_CAMERA ||
        clone->classID == M3G_CLASS_GROUP ||
        clone->classID == M3G_CLASS_WORLD ||
        clone->classID == M3G_CLASS_LIGHT ||
        clone->classID == M3G_CLASS_MESH ||
        clone->classID == M3G_CLASS_MORPHING_MESH ||
        clone->classID == M3G_CLASS_SKINNED_MESH ||
        clone->classID == M3G_CLASS_SPRITE)
        M3G_VFUNC(Node, clone, updateDuplicateReferences)((Node *)obj, references, numRef);

    return clone;
}

/*!
 * \brief Checks the length of the references array and calls virtual
 * getReferences
 */
M3G_API M3Gint m3gGetReferences(M3GObject hObject,
                                M3GObject *references,
                                M3Gint length)
{
    Object *obj = (Object *) hObject;
    M3G_VALIDATE_OBJECT(obj);
    if (references != NULL) {
        int num = M3G_VFUNC(Object, obj, getReferences)(obj, NULL);
        if (length < num) {
            m3gRaiseError(obj->interface, M3G_INVALID_OBJECT);
            return 0;
        }
    }
    return M3G_VFUNC(Object, obj, getReferences)(obj, (Object **)references);
}

/*!
 * \brief Uses m3gGetReferences to find given userID
 */
M3G_API M3GObject m3gFind(M3GObject hObject, M3Gint userID)
{
    Object *obj = (Object *) hObject;

    M3G_LOG3(M3G_LOG_STAGES, "Finding ID 0x%08X (%d) in 0x%08X\n",
             (unsigned) userID, userID, (unsigned) obj);
    
    M3G_VALIDATE_OBJECT(obj);

    if (obj->userID == userID) {
        return obj;
    }
    
    return M3G_VFUNC(Object, obj, find)(obj, userID);
}