os/graphics/m3g/m3gcore11/src/m3g_object.c
author sl
Tue, 10 Jun 2014 14:32:02 +0200 (2014-06-10)
changeset 1 260cb5ec6c19
permissions -rw-r--r--
Update contrib.
     1 /*
     2 * Copyright (c) 2003 Nokia Corporation and/or its subsidiary(-ies).
     3 * All rights reserved.
     4 * This component and the accompanying materials are made available
     5 * under the terms of the License "Eclipse Public License v1.0"
     6 * which accompanies this distribution, and is available
     7 * at the URL "http://www.eclipse.org/legal/epl-v10.html".
     8 *
     9 * Initial Contributors:
    10 * Nokia Corporation - initial contribution.
    11 *
    12 * Contributors:
    13 *
    14 * Description: Base object class implementation
    15 *
    16 */
    17 
    18 
    19 /*!
    20  * \internal
    21  * \file
    22  * \brief Base object class implementation
    23  *
    24  */
    25 
    26 #ifndef M3G_CORE_INCLUDE
    27 #   error included by m3g_core.c; do not compile separately.
    28 #endif
    29 
    30 /*----------------------------------------------------------------------
    31  * Constructor & destructor
    32  *--------------------------------------------------------------------*/
    33 
    34 /*!
    35  * \internal
    36  * \brief Constructor for all Objects
    37  *
    38  * The reference count for new objects is initialized to zero; the
    39  * object pointer must be stored using \c m3gSetRef, or m3gAddRef
    40  * called explicitly, to increase this to one.
    41  */
    42 static void m3gInitObject(Object *obj,
    43                           Interface *interface,
    44                           M3GClass classID)
    45 {
    46     M3G_ASSERT_PTR(obj);
    47     M3G_VALIDATE_INTERFACE(interface);
    48 
    49     M3G_ASSERT(m3gInRange(classID,
    50                           M3G_CLASS_ANIMATION_CONTROLLER, M3G_CLASS_WORLD));
    51     
    52     obj->classID = (M3Guint) classID;
    53     obj->interface = interface;
    54     obj->animTracks = NULL;
    55     obj->refCount = 0u;
    56     
    57     M3G_VALIDATE_OBJECT(obj);
    58 
    59     m3gAddChildObject(interface, obj);
    60     m3gMarkObject(obj);
    61     
    62     m3gIncStat(M3G_INTERFACE(obj), M3G_STAT_OBJECTS, 1);
    63     M3G_LOG2(M3G_LOG_OBJECTS, "New %s 0x%08X\n",
    64              m3gClassName((M3GClass) obj->classID),
    65              (unsigned) obj);
    66 }
    67 
    68 /*!
    69  * \internal
    70  * \brief Destructor for all Objects
    71  */
    72 static void m3gDestroyObject(Object *obj)
    73 {
    74     M3G_VALIDATE_OBJECT(obj);
    75     M3G_ASSERT(m3gIsObject(obj));
    76 
    77     if (obj->animTracks != NULL) {
    78         int n = m3gArraySize(obj->animTracks);
    79         int i;
    80 
    81         for (i = 0; i < n; ++i) {
    82             M3GObject hTrk = (M3GObject) m3gGetArrayElement(obj->animTracks, i);
    83             m3gDeleteRef(hTrk);
    84         }
    85         m3gDestroyArray(obj->animTracks, M3G_INTERFACE(obj));
    86         m3gFree(obj->interface, obj->animTracks);
    87     }
    88 
    89     m3gDelChildObject(obj->interface, obj);
    90     m3gUnmarkObject(obj);
    91     
    92     m3gIncStat(M3G_INTERFACE(obj), M3G_STAT_OBJECTS, -1);
    93     M3G_LOG2(M3G_LOG_OBJECTS, "Destroyed %s 0x%08X\n",
    94              m3gClassName((M3GClass) obj->classID),
    95              (unsigned) obj);
    96 }
    97 
    98 /*----------------------------------------------------------------------
    99  * Internal functions
   100  *--------------------------------------------------------------------*/
   101 
   102 /*!
   103  * \internal
   104  * \brief Sets an object reference to a new value and updates the
   105  * reference count accordingly
   106  *
   107  * Note that this may lead to the originally referenced object being
   108  * destroyed.
   109  *
   110  * \param ref Pointer to the reference (pointer) to set to a new value
   111  * \param obj New value of the reference
   112  */
   113 static void m3gSetRef(Object **ref, Object *obj)
   114 {
   115     M3G_ASSERT_PTR(ref);
   116 
   117     if (*ref != obj) {
   118         if (obj != NULL) {
   119             m3gAddRef((M3GObject) obj);
   120         }
   121         if (*ref != NULL) {
   122             m3gDeleteRef((M3GObject) *ref);
   123         }
   124         *ref = obj;
   125     }
   126 }
   127 
   128 #if defined(M3G_DEBUG)
   129 /*!
   130  * \internal
   131  * \brief Checks the integrity of an Object-derived object
   132  */
   133 static void m3gValidateObject(const void *pObj)
   134 {
   135     const Object *obj = (const Object *) pObj;
   136     M3G_VALIDATE_MEMBLOCK(obj);
   137     M3G_VALIDATE_MEMBLOCK(obj->interface);
   138     M3G_ASSERT(m3gInRange(obj->classID,
   139                           M3G_CLASS_ANIMATION_CONTROLLER, M3G_CLASS_WORLD));
   140     M3G_ASSERT_PTR(m3gGetVFTable(obj));
   141 }
   142 #endif /* M3G_DEBUG */
   143 
   144 
   145 /* ---------------- Internal Object3D functions ---------------- */
   146 
   147 /*!
   148  * \internal
   149  * \brief Default \c applyAnimation function implementation
   150  */
   151 static M3Gint m3gObjectApplyAnimation(Object *self, M3Gint time)
   152 {
   153     Interface *m3g = M3G_INTERFACE(self);
   154     M3Gint validity = 0x7FFFFFFF;
   155     M3Gint trackIndex, numTracks;
   156     M3Gfloat stackSampleVector[4];
   157     const PointerArray *tracks = self->animTracks;
   158 
   159     /* Quick exit if no animation tracks */
   160 
   161     if (tracks == NULL) {
   162         return validity;
   163     }
   164 
   165     /* Loop through the tracks. Note that the tracks are ordered so
   166      * that tracks targeting the same property are adjacent in the
   167      * array; this makes animation blending easier. */
   168 
   169     numTracks = m3gArraySize(tracks);
   170 
   171     for (trackIndex = 0; trackIndex < numTracks; ) {
   172         const AnimationTrack *track = (const AnimationTrack *)
   173             m3gGetArrayElement(tracks, trackIndex);
   174         const KeyframeSequence *sequence = track->sequence;
   175 
   176         M3Gint components = sequence->numComponents;
   177         M3Gint property = track->property;
   178         M3Gint nextProperty;
   179 
   180         M3Gfloat sumWeights = 0;
   181         M3Gfloat *sumValues;
   182 
   183         /* Collect the contributions from all the tracks targeting the
   184          * same property */
   185 
   186         if (components <= 4) {
   187             sumValues = stackSampleVector;
   188         }
   189         else {
   190             sumValues = (M3Gfloat *)
   191                 m3gAlloc(m3g, components * sizeof(M3Gfloat));
   192             if (sumValues == NULL) {
   193                 return 0;
   194             }
   195         }
   196 
   197         m3gZero(sumValues, components * sizeof(M3Gfloat));
   198 
   199         do {
   200             SampleInfo sampleInfo;
   201             
   202             m3gGetContribution(track, time, sumValues, &sampleInfo);
   203             if (sampleInfo.validity <= 0) {
   204                 return 0;
   205             }
   206             sumWeights += sampleInfo.weight;
   207             validity = M3G_MIN(validity, sampleInfo.validity);
   208 
   209             if (++trackIndex == numTracks) {
   210                 break;
   211             }
   212             track = (const AnimationTrack *) m3gGetArrayElement(tracks,
   213                                                                 trackIndex);
   214             nextProperty = track->property;
   215         } while (nextProperty == property);
   216 
   217         if (sumWeights > 0) {
   218             M3G_VFUNC(Object, self, updateProperty)(
   219                 self, property, components, sumValues);
   220         }
   221         if (sumValues != stackSampleVector) {
   222             m3gFree(m3g, sumValues);
   223         }
   224     }
   225 
   226     return validity;
   227 }
   228 
   229 /*!
   230  * \internal
   231  * \brief Default \c isCompatible function implementation
   232  */
   233 static M3Gbool m3gObjectIsCompatible(M3Gint property)
   234 {
   235     M3G_UNREF(property);
   236     
   237     return M3G_FALSE;
   238 }
   239 
   240 /*!
   241  * \internal
   242  * \brief Default \c updateProperty function implementation
   243  *
   244  * Silently ignoring an update request for a non-existent object
   245  * property does no harm, so this just asserts in debug builds and is
   246  * NOP otherwise.
   247  */
   248 static void m3gObjectUpdateProperty(Object *self,
   249                                     M3Gint property,
   250                                     M3Gint valueSize,
   251                                     const M3Gfloat *value)
   252 {
   253     M3G_UNREF(self);
   254     M3G_UNREF(property);
   255     M3G_UNREF(valueSize);
   256     M3G_UNREF(value);
   257     
   258     M3G_ASSERT(M3G_FALSE);
   259 }
   260 
   261 /*!
   262  * \internal
   263  * \brief Default \c getReferences function implementation
   264  */
   265 static M3Gint m3gObjectDoGetReferences(Object *self, Object **references)
   266 {
   267     M3Gint i;
   268     if (self->animTracks != NULL) {
   269         if (references != NULL) {
   270             for (i = 0; i < m3gArraySize(self->animTracks); ++i) {
   271                 references[i] = (Object *)m3gGetArrayElement(self->animTracks, i);
   272             }
   273         }
   274         return m3gArraySize(self->animTracks);
   275     }
   276     return 0;
   277 }
   278 
   279 /*!
   280  * \internal
   281  * \brief Default \c find implementation
   282  */
   283 static Object *m3gObjectFindID(Object *self, M3Gint userID)
   284 {
   285     M3Gint i;
   286 
   287     if (self->userID == userID) {
   288         return self;
   289     }
   290     
   291     if (self->animTracks) {
   292         for (i = 0; i < m3gArraySize(self->animTracks); ++i) {
   293             Object *found =
   294                 m3gFindID((Object *) m3gGetArrayElement(self->animTracks, i),
   295                           userID);
   296             if (found) {
   297                 return found;
   298             }
   299         }
   300     }
   301     
   302     return NULL;
   303 }
   304 
   305 /*!
   306  * \internal
   307  * \brief Default \c duplicate function implementation
   308  */
   309 static M3Gbool m3gObjectDuplicate(const Object *original,
   310                                   Object **clone,
   311                                   Object **pairs,
   312                                   M3Gint *numPairs)
   313 {
   314     Interface *m3g = original->interface;
   315     M3G_ASSERT_PTR(*clone); /* abstract class, must be derived */
   316 
   317     pairs[2 * (*numPairs)] = (Object *)original;
   318     pairs[2 * (*numPairs) + 1] = *clone;
   319     (*numPairs)++;
   320 
   321     /* Copy basic object properties */
   322     
   323     (*clone)->interface = m3g;
   324     (*clone)->classID   = original->classID;
   325     (*clone)->userID    = original->userID;
   326 
   327     /* Copy animation tracks.  This may fail due to out-of-memory, so
   328      * we check for that; clean-up will be handled by the derived
   329      * class method. */
   330     
   331     if (original->animTracks != NULL) {
   332         M3Gsizei numTracks = m3gArraySize(original->animTracks);
   333         M3Gint i;
   334 
   335         /* Allocate the track array and make sure it has enough room
   336          * for holding the tracks we're about to copy */
   337         
   338         PointerArray *animTracks =
   339             (PointerArray*) m3gAlloc(m3g, sizeof(PointerArray));
   340         if (animTracks == NULL) {
   341             return M3G_FALSE; /* out of memory */
   342         }        
   343         (*clone)->animTracks = animTracks;
   344 
   345         m3gInitArray(animTracks);
   346         if (!m3gEnsureArrayCapacity(animTracks, numTracks, m3g)) {
   347             return M3G_FALSE; /* out of memory */
   348         }                           
   349 
   350         /* Copy tracks one-by-one and update references.  This can no
   351          * longer fail, as the capacity request above has been
   352          * satisfied */
   353         
   354         for (i = 0; i < numTracks; ++i) {
   355             AnimationTrack *track =
   356                 (AnimationTrack *) m3gGetArrayElement(original->animTracks, i);
   357 
   358             if (m3gArrayAppend(animTracks, track, m3g) != i) {
   359                 M3G_ASSERT(M3G_FALSE);
   360             }
   361             m3gAddRef((Object *) track);
   362         }
   363     }
   364     return M3G_TRUE;
   365 }
   366 
   367 #if defined(M3G_LOGLEVEL)
   368 /*!
   369  * \internal
   370  * \brief Returns the name of an object class
   371  */
   372 static const char *m3gClassName(M3GClass classID)
   373 {
   374     switch (classID) {
   375     case M3G_CLASS_ANIMATION_CONTROLLER:
   376         return "AnimationController";
   377     case M3G_CLASS_ANIMATION_TRACK:
   378         return "AnimationTrack";
   379     case M3G_CLASS_APPEARANCE:
   380         return "Appearance";
   381     case M3G_CLASS_BACKGROUND:
   382         return "Background";
   383     case M3G_CLASS_CAMERA:
   384         return "Camera";
   385     case M3G_CLASS_COMPOSITING_MODE:
   386         return "CompositingMode";
   387     case M3G_CLASS_FOG:
   388         return "Fog";
   389     case M3G_CLASS_GROUP:
   390         return "Group";
   391     case M3G_CLASS_IMAGE:
   392         return "Image";
   393     case M3G_CLASS_INDEX_BUFFER:
   394         return "IndexBuffer";
   395     case M3G_CLASS_KEYFRAME_SEQUENCE:
   396         return "KeyframeSequence";
   397     case M3G_CLASS_LIGHT:
   398         return "Light";
   399     case M3G_CLASS_LOADER:
   400         return "Loader";
   401     case M3G_CLASS_MATERIAL:
   402         return "Material";
   403     case M3G_CLASS_MESH:
   404         return "Mesh";
   405     case M3G_CLASS_MORPHING_MESH:
   406         return "MorphingMesh";
   407     case M3G_CLASS_POLYGON_MODE:
   408         return "PolygonMode";
   409     case M3G_CLASS_RENDER_CONTEXT:
   410         return "RenderContext";
   411     case M3G_CLASS_SKINNED_MESH:
   412         return "SkinnedMesh";
   413     case M3G_CLASS_SPRITE:
   414         return "Sprite";
   415     case M3G_CLASS_TEXTURE:
   416         return "Texture";
   417     case M3G_CLASS_VERTEX_ARRAY:
   418         return "VertexArray";
   419     case M3G_CLASS_VERTEX_BUFFER:
   420         return "VertexBuffer";
   421     case M3G_CLASS_WORLD:
   422         return "World";
   423     default:
   424         return "<abstract class?>";
   425     }
   426 }
   427 #endif /* defined(M3G_LOGLEVEL) */
   428 
   429 /*----------------------------------------------------------------------
   430  * Public interface functions
   431  *--------------------------------------------------------------------*/
   432 
   433 /*!
   434  * \brief Deletes an M3G object
   435  *
   436  * Similarly to m3gDeleteRef, the object will still remain until all
   437  * references to it are deleted.  The difference from m3gDeleteRef is
   438  * mostly stylistic: m3gDeleteObject is meant to be called by the
   439  * owner of an object, while m3gDeleteRef should be used by users of
   440  * the object.  Functionally, they are equivalent in all normal use
   441  * cases.
   442  *
   443  * \note The only functional differences between m3gDeleteObject and
   444  * m3gDeleteRef are that m3gDeleteObject can be used on an object with
   445  * a reference count of zero, while m3gDeleteRef asserts against this
   446  * in debug builds; and m3gDeleteObject accepts a NULL object.
   447  */
   448 /*@access M3GObject@*/
   449 M3G_API void m3gDeleteObject(M3GObject hObject)
   450 {
   451     Interface *m3g;
   452     Object *obj = (Object *) hObject;
   453 
   454     if (obj != NULL) {
   455         M3G_VALIDATE_OBJECT(obj);
   456 
   457         if (obj->refCount > 0) {
   458             m3gDeleteRef(obj);
   459         }
   460         else {
   461             M3G_LOG2(M3G_LOG_REFCOUNT,
   462                      "Deleting %s 0x%08X\n",
   463                      m3gClassName((M3GClass) obj->classID),
   464                      (unsigned) obj);
   465             
   466             m3g = obj->interface;
   467             M3G_VALIDATE_INTERFACE(m3g);
   468             
   469             M3G_ASSERT(m3gGetVFTable(obj)->destroy != NULL);
   470             
   471             M3G_VFUNC(Object, obj, destroy)(obj);
   472             m3gFree(m3g, obj);
   473         }
   474     }
   475 }
   476 
   477 /*!
   478  * \brief Notifies that a new reference to an object has been created
   479  *
   480  * An object will not be deleted while references to it exist.
   481  */
   482 M3G_API void m3gAddRef(M3GObject hObject)
   483 {
   484     Object *obj = (Object *) hObject;
   485     M3G_VALIDATE_OBJECT(obj);
   486 
   487     M3G_LOG3(M3G_LOG_REFCOUNT,
   488              "Adding ref to 0x%08X (%s), new count %u\n",
   489              (unsigned) obj,
   490              m3gClassName((M3GClass) obj->classID),
   491              (unsigned) (obj->refCount + 1));
   492 
   493     M3G_ASSERT(obj->refCount < 0xFFFFFF);
   494     ++obj->refCount;
   495 }
   496 
   497 /*!
   498  * \brief Notifies that a reference to an object has been deleted
   499  *
   500  * If the reference count for an object reaches zero, the object is
   501  * automatically destroyed.
   502  */
   503 M3G_API void m3gDeleteRef(M3GObject hObject)
   504 {
   505     Object *obj = (Object *) hObject;
   506     M3G_VALIDATE_OBJECT(obj);
   507 
   508     M3G_ASSERT(obj->refCount > 0);
   509 
   510     M3G_LOG3(M3G_LOG_REFCOUNT,
   511              "Deleting ref to 0x%08X (%s), new count %u\n",
   512              (unsigned) obj,
   513              m3gClassName((M3GClass) obj->classID),
   514              (unsigned) (obj->refCount - 1));
   515 
   516     if (--obj->refCount == 0) {
   517         m3gDeleteObject(hObject);
   518     }
   519 }
   520 
   521 /*!
   522  * \brief Returns the run-time class of an object
   523  */
   524 M3G_API M3GClass m3gGetClass(M3GObject hObject)
   525 {
   526     Object *obj = (Object *) hObject;
   527     M3G_VALIDATE_OBJECT(obj);
   528     return M3G_CLASS(obj);
   529 }
   530 
   531 /*!
   532  * \brief Returns the interface owning this object
   533  */
   534 M3G_API M3GInterface m3gGetObjectInterface(M3GObject hObject)
   535 {
   536     Object *obj = (Object *) hObject;
   537     M3G_VALIDATE_OBJECT(obj);
   538     return obj->interface;
   539 }
   540 
   541 /* ---------------- Object3D functions ---------------- */
   542 
   543 /*!
   544  *
   545  */
   546 M3G_API M3Gint m3gAddAnimationTrack(M3GObject hObject,
   547                                     M3GAnimationTrack hAnimationTrack)
   548 {
   549     AnimationTrack *track = (AnimationTrack *)hAnimationTrack;
   550     Object *obj = (Object *) hObject;
   551     Interface *m3g = M3G_INTERFACE(obj);
   552     M3G_VALIDATE_OBJECT(obj);
   553 
   554     /* Check for errors */
   555 
   556     if (!M3G_VFUNC(Object, obj, isCompatible)(track->property)) {
   557         m3gRaiseError(m3g, M3G_INVALID_OBJECT);
   558         return -1;
   559     }
   560 
   561      /* Allocate animation track array only when adding animations for
   562       * the first time */
   563 
   564     if (obj->animTracks == NULL) {
   565         obj->animTracks = (PointerArray*) m3gAlloc(m3g, sizeof(PointerArray));
   566         if (obj->animTracks == NULL) return 0;
   567         m3gInitArray(obj->animTracks);
   568     }
   569 
   570     /*  The animation tracks are maintained in a sorted order based on
   571      *  their target property enumerations.  This keeps all tracks
   572      *  targeting the same property adjacent so that we can easily
   573      *  handle animation blending. */
   574     {
   575         PointerArray *trackArray = obj->animTracks;
   576         M3Gsizei numTracks = m3gArraySize(trackArray);
   577         M3Gint i;
   578 
   579         for (i = 0; i < numTracks; ++i) {
   580 
   581             const AnimationTrack *arrayTrack =
   582                 (const AnimationTrack *) m3gGetArrayElement(trackArray, i);
   583 
   584             if (arrayTrack->property > track->property) {
   585                 break;
   586             }
   587 
   588             if ((track == arrayTrack) ||
   589                 (   (track->property == arrayTrack->property) &&
   590                     (track->sequence->numComponents != arrayTrack->sequence->numComponents))) {
   591 
   592                     m3gRaiseError(m3g, M3G_INVALID_OBJECT);
   593                     return -1;
   594                 }
   595         }
   596 
   597         if (m3gArrayInsert(trackArray, i, track, m3g) < 0) {
   598             return -1;
   599         }
   600         m3gAddRef((M3GObject) track);
   601 
   602         return i;
   603     }
   604 }
   605 
   606 /*!
   607  *
   608  */
   609 M3G_API void m3gRemoveAnimationTrack(M3GObject hObject,
   610                                      M3GAnimationTrack hAnimationTrack)
   611 {
   612     AnimationTrack *track = (AnimationTrack *)hAnimationTrack;
   613     Object *obj = (Object *) hObject;
   614     M3G_VALIDATE_OBJECT(obj);
   615 
   616     /* Remove the track from the array, and if no tracks remain,
   617      * delete the array, too */
   618 
   619     if (track != NULL && obj->animTracks != NULL) {
   620         M3Gint i = m3gArrayFind(obj->animTracks, track);
   621 
   622         if (i != -1) {
   623             m3gArrayDelete(obj->animTracks, i);
   624             m3gDeleteRef((Object *) track);
   625 
   626             if (m3gArraySize(obj->animTracks) == 0) {
   627                 m3gDestroyArray(obj->animTracks, M3G_INTERFACE(obj));
   628                 m3gFree(M3G_INTERFACE(obj), obj->animTracks);
   629                 obj->animTracks = NULL;
   630             }
   631         }
   632     }
   633 }
   634 
   635 /*!
   636  *
   637  */
   638 M3G_API M3Gint m3gGetAnimationTrackCount(M3GObject hObject)
   639 {
   640     Object *obj = (Object *) hObject;
   641     M3G_VALIDATE_OBJECT(obj);
   642 
   643     return (obj->animTracks == NULL ? 0 : m3gArraySize(obj->animTracks));
   644 }
   645 
   646 M3G_API M3GAnimationTrack m3gGetAnimationTrack(M3GObject hObject, M3Gint idx)
   647 {
   648     Object *obj = (Object *) hObject;
   649     M3G_VALIDATE_OBJECT(obj);
   650 
   651     /* idx must be in range [0, to size of array - 1] */
   652     if (obj->animTracks == NULL
   653             || !m3gInRange(idx, 0, m3gArraySize(obj->animTracks) - 1)) {
   654         m3gRaiseError(M3G_INTERFACE(obj), M3G_INVALID_INDEX);
   655         return NULL;
   656     }
   657 
   658     return (M3GAnimationTrack) m3gGetArrayElement(obj->animTracks, idx);
   659 }
   660 
   661 M3G_API M3Gint m3gAnimate(M3GObject hObject, M3Gint time)
   662 {
   663     M3Gint validity;
   664     Object *obj = (Object *) hObject;
   665 
   666     M3G_LOG2(M3G_LOG_STAGES,
   667              "Animating %s 0x%08X\n",
   668              m3gClassName((M3GClass) obj->classID), (unsigned) obj);
   669     
   670     M3G_VALIDATE_OBJECT(obj);
   671     
   672     M3G_BEGIN_PROFILE(M3G_INTERFACE(obj), M3G_PROFILE_ANIM);
   673     validity = M3G_VFUNC(Object, obj, applyAnimation)(obj, time);
   674     M3G_END_PROFILE(M3G_INTERFACE(obj), M3G_PROFILE_ANIM);
   675     
   676     return validity;
   677 }
   678 
   679 /*!
   680  * \brief Sets userID for this object
   681 */
   682 M3G_API void m3gSetUserID(M3GObject hObject, M3Gint userID)
   683 {
   684     Object *obj = (Object *) hObject;
   685     M3G_VALIDATE_OBJECT(obj);
   686 	obj->userID = userID;
   687 }
   688 
   689 /*!
   690  * \brief Gets userID of this object
   691 */
   692 M3G_API M3Gint m3gGetUserID(M3GObject hObject)
   693 {
   694     Object *obj = (Object *) hObject;
   695     M3G_VALIDATE_OBJECT(obj);
   696 
   697 	return obj->userID;
   698 }
   699 
   700 /*!
   701  * \brief Creates a duplicate of this Object3D
   702 */
   703 M3G_API M3GObject m3gDuplicate(M3GObject hObject, M3GObject *hReferences)
   704 {
   705     Object **references = (Object **)hReferences;
   706     const Object *obj = (const Object *) hObject;
   707     Object *clone = NULL;
   708     M3Gint numRef = 0;
   709 
   710     M3G_LOG2(M3G_LOG_STAGES|M3G_LOG_OBJECTS,
   711              "Duplicating %s 0x%08X\n",
   712              m3gClassName((M3GClass) obj->classID), (unsigned) obj);
   713 
   714     M3G_VALIDATE_OBJECT(obj);
   715     
   716     /* Clone the object (or subtree) */
   717     if (!M3G_VFUNC(Object, obj, duplicate)(obj, &clone, references, &numRef)) {
   718         m3gDeleteObject(clone);
   719         return NULL; /* failed; out of memory will have been thrown */
   720     }
   721 
   722     /* NOTE This will have to change (the virtual function moved to
   723      * the Object class) if we add classes where child objects may get
   724      * duplicated */
   725     
   726     if (clone->classID == M3G_CLASS_CAMERA ||
   727         clone->classID == M3G_CLASS_GROUP ||
   728         clone->classID == M3G_CLASS_WORLD ||
   729         clone->classID == M3G_CLASS_LIGHT ||
   730         clone->classID == M3G_CLASS_MESH ||
   731         clone->classID == M3G_CLASS_MORPHING_MESH ||
   732         clone->classID == M3G_CLASS_SKINNED_MESH ||
   733         clone->classID == M3G_CLASS_SPRITE)
   734         M3G_VFUNC(Node, clone, updateDuplicateReferences)((Node *)obj, references, numRef);
   735 
   736     return clone;
   737 }
   738 
   739 /*!
   740  * \brief Checks the length of the references array and calls virtual
   741  * getReferences
   742  */
   743 M3G_API M3Gint m3gGetReferences(M3GObject hObject,
   744                                 M3GObject *references,
   745                                 M3Gint length)
   746 {
   747     Object *obj = (Object *) hObject;
   748     M3G_VALIDATE_OBJECT(obj);
   749     if (references != NULL) {
   750         int num = M3G_VFUNC(Object, obj, getReferences)(obj, NULL);
   751         if (length < num) {
   752             m3gRaiseError(obj->interface, M3G_INVALID_OBJECT);
   753             return 0;
   754         }
   755     }
   756     return M3G_VFUNC(Object, obj, getReferences)(obj, (Object **)references);
   757 }
   758 
   759 /*!
   760  * \brief Uses m3gGetReferences to find given userID
   761  */
   762 M3G_API M3GObject m3gFind(M3GObject hObject, M3Gint userID)
   763 {
   764     Object *obj = (Object *) hObject;
   765 
   766     M3G_LOG3(M3G_LOG_STAGES, "Finding ID 0x%08X (%d) in 0x%08X\n",
   767              (unsigned) userID, userID, (unsigned) obj);
   768     
   769     M3G_VALIDATE_OBJECT(obj);
   770 
   771     if (obj->userID == userID) {
   772         return obj;
   773     }
   774     
   775     return M3G_VFUNC(Object, obj, find)(obj, userID);
   776 }
   777