m3g/m3gcore11/src/m3g_keyframesequence.c
changeset 0 5d03bc08d59c
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/m3g/m3gcore11/src/m3g_keyframesequence.c	Tue Feb 02 01:47:50 2010 +0200
@@ -0,0 +1,1004 @@
+/*
+* 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: KeyframeSequence implementation
+*
+*/
+
+
+/*!
+ * \internal
+ * \file
+ * \brief KeyframeSequence implementation
+ */
+
+#ifndef M3G_CORE_INCLUDE
+#   error included by m3g_core.c; do not compile separately.
+#endif
+
+#include "m3g_keyframesequence.h"
+#include "m3g_memory.h"
+
+/*----------------------------------------------------------------------
+ * Internal functions
+ *--------------------------------------------------------------------*/
+
+/*!
+ * \internal
+ * \brief Destroys this KeyframeSequence object.
+ *
+ * \param obj KeyframeSequence object
+ */
+static void m3gDestroyKeyframeSequence(Object *obj)
+{
+    KeyframeSequence *sequence = (KeyframeSequence *) obj;
+    M3G_VALIDATE_OBJECT(sequence);
+    {
+        Interface *m3g = M3G_INTERFACE(sequence);
+        m3gFree(m3g, sequence->keyframes);
+        m3gFree(m3g, sequence->keyframeTimes);
+        m3gFree(m3g, sequence->inTangents);
+        m3gFree(m3g, sequence->outTangents);
+        m3gFree(m3g, sequence->a);
+        m3gFree(m3g, sequence->b);
+    }
+    m3gDestroyObject(&sequence->object);
+}
+
+/*!
+ * \internal
+ * \brief Overloaded Object3D method.
+ *
+ * \param originalObj original KeyframeSequence object
+ * \param cloneObj pointer to cloned KeyframeSequence object
+ * \param pairs array for all object-duplicate pairs
+ * \param numPairs number of pairs
+ */
+static M3Gbool m3gKeyframeSequenceDuplicate(const Object *originalObj,
+                                            Object **cloneObj,
+                                            Object **pairs,
+                                            M3Gint *numPairs)
+{
+    KeyframeSequence *original = (KeyframeSequence *)originalObj;
+    KeyframeSequence *clone =
+        (KeyframeSequence *)m3gCreateKeyframeSequence(originalObj->interface,
+                                                      original->numKeyframes,
+                                                      original->numComponents,
+                                                      original->interpolation);
+    *cloneObj = (Object *)clone;
+    if (*cloneObj == NULL) {
+        return M3G_FALSE;
+    }
+
+    if(m3gObjectDuplicate(originalObj, cloneObj, pairs, numPairs)) {
+        M3Gsizei n = original->numKeyframes * original->numComponents;
+        
+        m3gCopy(clone->keyframes, original->keyframes, n * sizeof(M3Gfloat));
+        m3gCopy(clone->keyframeTimes, original->keyframeTimes, original->numKeyframes * sizeof(M3Gint));
+        if (original->dirty == M3G_FALSE) {
+            if (original->inTangents) {
+                m3gCopy(clone->inTangents, original->inTangents, n * sizeof(M3Gfloat));
+                m3gCopy(clone->outTangents, original->outTangents, n * sizeof(M3Gfloat));
+            }
+            if (original->a) {
+                m3gCopy(clone->a, original->a, original->numKeyframes * sizeof(Quat));
+                m3gCopy(clone->b, original->b, original->numKeyframes * sizeof(Quat));
+            }
+        }
+        else {
+            clone->dirty = M3G_TRUE;
+        }
+
+        clone->duration = original->duration;
+        clone->closed = original->closed;
+        clone->firstValid = original->firstValid;
+        clone->lastValid = original->lastValid;
+        return M3G_TRUE;
+    }
+    else {
+        return M3G_FALSE;
+    }
+}
+
+/*!
+ * \internal
+ * \brief Initializes a KeyframeSequence object. See specification
+ * for default values.
+ *
+ * \param m3g                   M3G interface
+ * \param sequence              KeyframeSequence object
+ * \param numKeyframes          number of keyframes
+ * \param numComponents         number of components
+ * \param interpolation         interpolation type
+ * \retval                      KeyframeSequence initialized KeyframeSequence object
+ * \retval                      NULL initialization failed
+ */
+static KeyframeSequence *m3gInitKeyframeSequence(Interface *m3g,
+                                                 KeyframeSequence *sequence,
+                                                 M3Gint numKeyframes,
+                                                 M3Gint numComponents,
+                                                 M3Gint interpolation)
+{
+    m3gInitObject(&sequence->object, m3g, M3G_CLASS_KEYFRAME_SEQUENCE);
+
+    /* Set keyframe parameters */
+    
+    sequence->numKeyframes = numKeyframes;
+    sequence->numComponents = numComponents;
+    sequence->interpolation = interpolation;
+    sequence->lastValid = numKeyframes - 1; /* firstValid defaults to 0 */
+
+    /* Allocate keyframe and tangent data */
+    {    
+        M3Gsizei n = numKeyframes * numComponents;
+        
+        sequence->keyframes = (M3Gfloat *)m3gAllocZ(m3g, n * sizeof(M3Gfloat));
+        if (sequence->keyframes == NULL) {
+            goto AllocFailed;
+        }
+        sequence->keyframeTimes = (M3Gint *)m3gAllocZ(m3g, numKeyframes * sizeof(M3Gint));
+        if (sequence->keyframeTimes == NULL) {
+            goto AllocFailed;
+        }
+        
+        if (interpolation == M3G_SPLINE) {
+            sequence->inTangents  = (M3Gfloat *)m3gAllocZ(m3g, n * sizeof(M3Gfloat));
+            sequence->outTangents = (M3Gfloat *)m3gAllocZ(m3g, n * sizeof(M3Gfloat));
+            if (sequence->inTangents == NULL || sequence->outTangents == NULL) {
+                goto AllocFailed;
+            }
+        }
+        else if (interpolation == M3G_SQUAD) {
+            sequence->a = (Quat *)m3gAlloc(m3g, numKeyframes * sizeof(Quat));
+            sequence->b = (Quat *)m3gAlloc(m3g, numKeyframes * sizeof(Quat));
+            if (sequence->a == NULL || sequence->b == NULL) {
+                goto AllocFailed;
+            }
+        }
+
+        /* Success; just make a note that the data is not valid yet */
+        
+        sequence->dirty = M3G_TRUE;
+        return sequence;
+
+AllocFailed:
+        /* The destructor contains exactly the code we need for this,
+         * so just call that */
+        
+        m3gDestroyKeyframeSequence((Object*) sequence);
+        return NULL;
+    }
+}
+
+/*!
+ * \internal
+ * \brief Checks the validity of keyframe timings
+ *
+ * \param sequence   KeyframeSequence object
+ * \retval M3G_TRUE  sequence valid
+ * \retval M3G_FALSE sequence invalid
+ */
+static M3Gbool m3gValidSequence(const KeyframeSequence *sequence)
+{    
+    const M3Gint last = sequence->lastValid;
+    M3Gint current = sequence->firstValid;
+    
+    while (current != last) {
+        M3Gint next = (current < sequence->numKeyframes-1) ? current + 1 : 0;
+        if (sequence->keyframeTimes[next] < sequence->keyframeTimes[current]) {
+            return M3G_FALSE;
+        }
+        current = next;
+    }
+    return (sequence->keyframeTimes[last] <= sequence->duration);
+}
+
+
+/*!
+ * \internal
+ * \brief Get number of components
+ *
+ * \param sequence   KeyframeSequence object
+ * \return number of components
+ */
+static M3Gint m3gGetNumComponents(const KeyframeSequence *sequence)
+{
+    return sequence->numComponents;
+}
+
+/*!
+ * \internal
+ * \brief Get next keyframe index
+ *
+ * \param sequence  KeyframeSequence object
+ * \param ind       current index
+ * \return next index
+ */
+static M3Gint m3gNextKeyframeIndex(const KeyframeSequence *sequence, M3Gint ind)
+{
+    if (ind == sequence->lastValid) {
+        return sequence->firstValid;
+    }
+    else if (ind == sequence->numKeyframes - 1) {
+        return 0;
+    }
+    else {
+        return (ind + 1);
+    }
+}
+    
+/*!
+ * \internal
+ * \brief Get previous keyframe index
+ *
+ * \param sequence  KeyframeSequence object
+ * \param ind       current index
+ * \return previous index
+ */
+static M3Gint m3gPreviousKeyframeIndex(const KeyframeSequence *sequence, M3Gint ind)
+{
+    if (ind == sequence->firstValid) {
+        return sequence->lastValid;
+    }
+    else if (ind == 0) {
+        return (sequence->numKeyframes - 1);
+    }
+    else {
+        return (ind - 1);
+    }
+}
+
+/*!
+ * \internal
+ * \brief Get keyframe at index
+ *
+ * \param seq       KeyframeSequence object
+ * \param idx       keyframe index
+ * \return keyframe value
+ */
+static M3G_INLINE const M3Gfloat *m3gKeyframeAt(const KeyframeSequence *seq, M3Gint idx)
+{
+    return seq->keyframes + idx * seq->numComponents;
+}
+
+/*!
+ * \internal
+ * \brief Get keyframe at index -1
+ *
+ * \param seq       KeyframeSequence object
+ * \param idx       keyframe index
+ * \return keyframe value
+ */
+static M3G_INLINE const M3Gfloat *m3gKeyframeBefore(const KeyframeSequence *seq, M3Gint idx)
+{
+    return m3gKeyframeAt(seq, m3gPreviousKeyframeIndex(seq, idx));
+}
+
+/*!
+ * \internal
+ * \brief Get keyframe at index + 1
+ *
+ * \param seq       KeyframeSequence object
+ * \param idx       keyframe index
+ * \return keyframe value
+ */
+static M3G_INLINE const M3Gfloat *m3gKeyframeAfter(const KeyframeSequence *seq, M3Gint idx)
+{
+    return m3gKeyframeAt(seq, m3gNextKeyframeIndex(seq, idx));
+}
+
+/*!
+ * \internal
+ * \brief Get tangent to index
+ *
+ * \param seq       KeyframeSequence object
+ * \param idx       keyframe index
+ * \return tangent value
+ */
+static M3G_INLINE const M3Gfloat *m3gTangentTo(const KeyframeSequence *seq, M3Gint idx)
+{
+    M3G_ASSERT(seq->inTangents != NULL);
+    return seq->inTangents + idx * seq->numComponents;
+}
+
+/*!
+ * \internal
+ * \brief Get tangent from index
+ *
+ * \param seq       KeyframeSequence object
+ * \param idx       keyframe index
+ * \return tangent value
+ */
+static M3G_INLINE const M3Gfloat *m3gTangentFrom(const KeyframeSequence *seq, M3Gint idx)
+{
+    M3G_ASSERT(seq->outTangents != NULL);
+    return seq->outTangents + idx * seq->numComponents;
+}
+
+/*!
+ * \internal
+ * \brief Get time delta
+ *
+ * \param sequence  KeyframeSequence object
+ * \param ind       keyframe index
+ * \return time delta
+ */
+static M3Gint m3gTimeDelta(const KeyframeSequence *sequence, M3Gint ind)
+{
+    if (ind == sequence->lastValid) {
+        return
+            (sequence->duration
+             - sequence->keyframeTimes[sequence->lastValid])
+            + sequence->keyframeTimes[sequence->firstValid];
+    }
+    return sequence->keyframeTimes[m3gNextKeyframeIndex(sequence, ind)]
+        - sequence->keyframeTimes[ind];
+}
+
+/*!
+ * \internal
+ * \brief Get incoming tangent scale
+ *
+ * \param sequence  KeyframeSequence object
+ * \param ind       keyframe index
+ * \return tangent scale
+ */
+static M3Gfloat m3gIncomingTangentScale(const KeyframeSequence *sequence,
+                                        M3Gint ind)
+{
+    if (!sequence->closed
+        && (ind == sequence->firstValid || ind == sequence->lastValid)) {
+        return 0;
+    }
+    else {
+        M3Gint prevind = m3gPreviousKeyframeIndex(sequence, ind);
+        return m3gDiv(m3gMul(2.0f, (M3Gfloat) m3gTimeDelta(sequence, prevind)),
+                      (M3Gfloat)(m3gTimeDelta(sequence, ind)
+                                 + m3gTimeDelta(sequence, prevind)));
+    }
+}
+
+/*!
+ * \internal
+ * \brief Get outgoing tangent scale
+ *
+ * \param sequence  KeyframeSequence object
+ * \param ind       keyframe index
+ * \return tangent scale
+ */
+static M3Gfloat m3gOutgoingTangentScale(const KeyframeSequence *sequence,
+                                        M3Gint ind)
+{
+    if (!sequence->closed
+        && (ind == sequence->firstValid || ind == sequence->lastValid)) {
+        return 0;
+    }
+    else {
+        M3Gint prevind = m3gPreviousKeyframeIndex(sequence, ind);
+        return m3gDiv(m3gMul(2.0f, (M3Gfloat) m3gTimeDelta(sequence, ind)),
+                      (M3Gfloat)(m3gTimeDelta(sequence, ind)
+                                 + m3gTimeDelta(sequence, prevind)));
+    }
+}
+
+/*!
+ * \internal
+ * \brief Precalculate all tangents
+ *
+ * \param sequence  KeyframeSequence object
+ */
+static void m3gPrecalculateTangents(KeyframeSequence *sequence)
+{
+    M3Gint i, kf = sequence->firstValid;
+    do {
+        const M3Gfloat *prev = m3gKeyframeBefore(sequence, kf);
+        const M3Gfloat *next = m3gKeyframeAfter(sequence, kf);
+        const M3Gfloat sIn  = m3gIncomingTangentScale(sequence, kf);
+        const M3Gfloat sOut = m3gOutgoingTangentScale(sequence, kf);
+        M3Gfloat *in  = (M3Gfloat *) m3gTangentTo(sequence, kf);
+        M3Gfloat *out = (M3Gfloat *) m3gTangentFrom(sequence, kf);        
+        
+        for (i = 0; i < sequence->numComponents; ++i) {
+            in[i]  = m3gMul(m3gMul(0.5f, (m3gSub(next[i], prev[i]))), sIn);
+            out[i] = m3gMul(m3gMul(0.5f, (m3gSub(next[i], prev[i]))), sOut);
+        }
+        
+        kf = m3gNextKeyframeIndex(sequence, kf);
+    } while (kf != sequence->firstValid);
+}
+
+/*!
+ * \internal
+ * \brief Precalculate A and B
+ *
+ * \param sequence  KeyframeSequence object
+ */
+static void m3gPrecalculateAB(KeyframeSequence *sequence)
+{
+    Quat start, end, prev, next;
+    Vec3 tangent, cfd;
+    M3Gfloat temp[4]; /* used for both quats and vectors */
+    
+    M3Gint kf = sequence->firstValid;
+    do {
+
+        m3gSetQuat(&prev, m3gKeyframeBefore(sequence, kf));
+        m3gSetQuat(&start, m3gKeyframeAt(sequence, kf));
+        m3gSetQuat(&end, m3gKeyframeAfter(sequence, kf));
+        m3gSetQuat(&next, m3gKeyframeAfter(sequence, m3gNextKeyframeIndex(sequence, kf)));
+
+        /* Compute the centered finite difference at this
+           keyframe; note that this would be the tangent for basic
+           Catmull-Rom interpolation. */
+
+        m3gLogDiffQuat(&cfd, &start, &end);
+        m3gLogDiffQuat((Vec3*)temp, &prev, &start);
+        m3gAddVec3(&cfd, (Vec3*)temp);
+        m3gScaleVec3(&cfd, 0.5f);
+
+        /* Compute the outgoing tangent, scaled to compensate for
+           keyframe timing, then compute the "A" intermediate
+           quaternion. */
+
+        tangent = cfd;
+        m3gScaleVec3(&tangent, m3gOutgoingTangentScale(sequence, kf));
+
+        m3gLogDiffQuat((Vec3*)temp, &start, &end);
+        m3gSubVec3(&tangent, (Vec3*)temp);
+        m3gScaleVec3(&tangent, 0.5f);
+        m3gExpQuat((Quat*)temp, &tangent);
+        sequence->a[kf] = start;
+        m3gMulQuat(&(sequence->a[kf]), (Quat*)temp);
+
+        /* Then repeat the same steps for the incoming tangent and
+           the "B" intermediate quaternion. */
+
+        tangent = cfd;
+        m3gScaleVec3(&tangent, m3gIncomingTangentScale(sequence, kf));
+
+        m3gLogDiffQuat((Vec3*)temp, &prev, &start);
+        m3gSubVec3((Vec3*)temp, &tangent);
+        m3gScaleVec3((Vec3*)temp, 0.5f);
+        m3gExpQuat((Quat*)temp, (Vec3*)temp);
+        sequence->b[kf] = start;
+        m3gMulQuat(&(sequence->b[kf]), (Quat*)temp);
+        
+        kf = m3gNextKeyframeIndex(sequence, kf);
+    } while (kf != sequence->firstValid);
+}
+
+/*!
+ * \internal
+ * \brief Update all tangents
+ *
+ * \param sequence  KeyframeSequence object
+ */
+static void m3gUpdateTangents(KeyframeSequence *sequence)
+{
+    if (sequence->interpolation == M3G_SPLINE) {
+        m3gPrecalculateTangents(sequence);
+    }
+    else if (sequence->interpolation == M3G_SQUAD) {
+        m3gPrecalculateAB(sequence);
+    }
+}
+
+/*!
+ * \internal
+ * \brief Linear interpolate
+ *
+ * \param sequence      KeyframeSequence object
+ * \param sample        input samples
+ * \param s             speed
+ * \param startIndex    start index
+ * \param endIndex      end index
+ */
+static M3G_INLINE void m3gLerpSample(const KeyframeSequence *sequence,
+                                     M3Gfloat *sample,
+                                     M3Gfloat s,
+                                     M3Gint startIndex, M3Gint endIndex)
+{
+    const M3Gfloat *start = m3gKeyframeAt(sequence, startIndex);
+    const M3Gfloat *end   = m3gKeyframeAt(sequence, endIndex);
+
+    m3gLerp(sequence->numComponents, sample, s, start, end);
+}
+
+/*!
+ * \internal
+ * \brief Spline interpolate
+ *
+ * \param sequence      KeyframeSequence object
+ * \param sample        input samples
+ * \param s             speed
+ * \param startIndex    start index
+ * \param endIndex      end index
+ */
+static M3G_INLINE void m3gSplineSample(const KeyframeSequence *sequence,
+                                       M3Gfloat *sample,
+                                       M3Gfloat s,
+                                       M3Gint startIndex, M3Gint endIndex)
+{
+    const M3Gfloat *start, *end;
+    const M3Gfloat *tStart, *tEnd;
+    
+    /* Get the required keyframe values and the (one-sided) tangents
+     * at the ends of the segment. */
+    
+    start = m3gKeyframeAt(sequence, startIndex);
+    end   = m3gKeyframeAt(sequence, endIndex);
+
+    tStart = m3gTangentFrom(sequence, startIndex);
+    tEnd = m3gTangentTo(sequence, endIndex);
+
+    /* Interpolate the final value using a Hermite spline. */
+
+    m3gHermite(sequence->numComponents, sample, s, start, end, tStart, tEnd);
+}
+
+/*!
+ * \internal
+ * \brief Spherical linear interpolate
+ *
+ * \param sequence      KeyframeSequence object
+ * \param sample        input samples
+ * \param s             speed
+ * \param startIndex    start index
+ * \param endIndex      end index
+ */
+static M3G_INLINE void m3gSlerpSample(const KeyframeSequence *sequence,
+                                      M3Gfloat *sample,
+                                      M3Gfloat s,
+                                      M3Gint startIndex, M3Gint endIndex)
+{
+    if (sequence->numComponents != 4) {
+        m3gRaiseError(M3G_INTERFACE(sequence), M3G_INVALID_VALUE);
+        return;
+    }
+    
+    m3gSlerpQuat((Quat *) sample,
+                 s,
+                 (const Quat *) m3gKeyframeAt(sequence, startIndex),
+                 (const Quat *) m3gKeyframeAt(sequence, endIndex));
+}
+
+/*!
+ * \internal
+ * \brief Spline interpolate quats
+ *
+ * \param sequence      KeyframeSequence object
+ * \param sample        input samples
+ * \param s             speed
+ * \param startIndex    start index
+ * \param endIndex      end index
+ */
+static M3G_INLINE void m3gSquadSample(const KeyframeSequence *sequence,
+                                      M3Gfloat *sample,
+                                      M3Gfloat s,
+                                      M3Gint startIndex, M3Gint endIndex)
+{
+    if (sequence->numComponents != 4) {
+        m3gRaiseError(M3G_INTERFACE(sequence), M3G_INVALID_VALUE);
+        return;
+    }
+
+    m3gSquadQuat((Quat *) sample,
+                 s,
+                 (const Quat *) m3gKeyframeAt(sequence, startIndex),
+                 &(sequence->a[startIndex]),
+                 &(sequence->b[endIndex]),
+                 (const Quat *) m3gKeyframeAt(sequence, endIndex));
+}
+
+/*!
+ * \internal
+ * \brief Get sample
+ *
+ * \param sequence      KeyframeSequence object
+ * \param time          time
+ * \param sample        pointer to sample
+ * \return              sample validity
+ */
+static M3Gint m3gGetSample(KeyframeSequence *sequence,
+                           M3Gint time,
+                           M3Gfloat *sample)
+{
+    M3Gint start, end, i;
+    const M3Gfloat *value;
+    M3Gfloat s;
+    
+    M3G_VALIDATE_OBJECT(sequence);
+    
+    if (sequence->dirty == M3G_TRUE) {
+        if (!m3gValidSequence(sequence)) {
+            m3gRaiseError(M3G_INTERFACE(sequence), M3G_INVALID_OPERATION);
+            return 0;
+        }
+        m3gUpdateTangents(sequence);
+        sequence->dirty = M3G_FALSE;
+        sequence->probablyNext = sequence->firstValid;
+    }
+
+    /* First, map the time to the valid range of a repeating
+       sequence, or handle the special end cases of an open-ended
+       sequence. */
+    
+    if (sequence->closed) {
+        if (time < 0)
+            time = (time % sequence->duration) + sequence->duration;
+        else
+            time = time % sequence->duration;
+        if (time < sequence->keyframeTimes[sequence->firstValid]) {
+            time += sequence->duration;
+        }
+    }
+    else {
+        if (time < sequence->keyframeTimes[sequence->firstValid]) {
+            value = m3gKeyframeAt(sequence, sequence->firstValid);
+            for (i = 0; i < sequence->numComponents; i++)
+                sample[i] = value[i];
+            return (sequence->keyframeTimes[sequence->firstValid] - time);
+        }
+        else if (time >= sequence->keyframeTimes[sequence->lastValid]) {
+            value = m3gKeyframeAt(sequence, sequence->lastValid);
+            for (i = 0; i < sequence->numComponents; i++)
+                sample[i] = value[i];
+            /* \ define a meaningful constant */
+            return 0x7FFFFFFF;
+        }
+    }
+
+    /* Search for the starting keyframe of the segment to
+       interpolate. Starting the search from the previously
+       used keyframe, we are very likely to find the match
+       sooner than if we'd start from the first keyframe. */
+
+    start = sequence->probablyNext;
+    if (sequence->keyframeTimes[start] > time)
+        start = sequence->firstValid;
+    while (start != sequence->lastValid &&
+           sequence->keyframeTimes[m3gNextKeyframeIndex(sequence, start)] <= time) {
+        start = m3gNextKeyframeIndex(sequence, start);
+    }
+    sequence->probablyNext = start;
+    
+    /* Calculate the interpolation factor if necessary; the quick
+       exit also avoids a division by zero in the case that we
+       have a quirky sequence with only multiple coincident
+       keyframes. */
+
+    if (time == sequence->keyframeTimes[start] || sequence->interpolation == M3G_STEP) {
+        value = m3gKeyframeAt(sequence, start);
+        for (i = 0; i < sequence->numComponents; i++)
+            sample[i] = value[i];
+        return (sequence->interpolation == M3G_STEP)
+               ? (m3gTimeDelta(sequence, start) - (time - sequence->keyframeTimes[start]))
+               : 1;
+    }
+    s = m3gDivif(time - sequence->keyframeTimes[start], m3gTimeDelta(sequence, start));
+    
+    /* Pick the correct interpolation function and pass the
+       segment start and end keyframe indices. */
+
+    end = m3gNextKeyframeIndex(sequence, start);
+
+    switch (sequence->interpolation) {
+    case M3G_LINEAR:
+        m3gLerpSample(sequence, sample, s, start, end);
+        break;
+    case M3G_SLERP:
+        m3gSlerpSample(sequence, sample, s, start, end);
+        break;
+    case M3G_SPLINE:
+        m3gSplineSample(sequence, sample, s, start, end);
+        break;
+    case M3G_SQUAD:
+        m3gSquadSample(sequence, sample, s, start, end);
+        break;
+    default:
+        m3gRaiseError(M3G_INTERFACE(sequence), M3G_INVALID_ENUM);
+        return 0;
+    }
+
+    return 1;
+}
+
+/*----------------------------------------------------------------------
+ * Virtual function table
+ *--------------------------------------------------------------------*/
+
+static const ObjectVFTable m3gvf_KeyframeSequence = {
+    m3gObjectApplyAnimation,
+    m3gObjectIsCompatible,
+    m3gObjectUpdateProperty,
+    m3gObjectDoGetReferences,
+    m3gObjectFindID,
+    m3gKeyframeSequenceDuplicate,
+    m3gDestroyKeyframeSequence
+};
+
+
+/*----------------------------------------------------------------------
+ * Public API functions
+ *--------------------------------------------------------------------*/
+
+/*!
+ * \brief Creates a new KeyframeSequence with default values
+ * 
+ * \param hInterface            M3G interface
+ * \param numKeyframes          number of keyframes
+ * \param numComponents         number of components
+ * \param interpolation         interpolation type
+ * \retval KeyframeSequence new KeyframeSequence object
+ * \retval NULL KeyframeSequence creating failed
+ */
+/*@access M3GInterface@*/
+/*@access M3Gappearance@*/
+M3G_API M3GKeyframeSequence m3gCreateKeyframeSequence(M3GInterface hInterface,
+                                                      M3Gint numKeyframes,
+                                                      M3Gint numComponents,
+                                                      M3Gint interpolation)
+{
+    Interface *m3g = (Interface *) hInterface;
+    M3G_VALIDATE_INTERFACE(m3g);
+
+    if (numKeyframes < 1 || numComponents < 1
+        || interpolation < M3G_LINEAR || interpolation > M3G_STEP
+        || ((interpolation == M3G_SLERP || interpolation == M3G_SQUAD)
+            && numComponents != 4)) {
+        m3gRaiseError(m3g, M3G_INVALID_VALUE);
+        return NULL;
+    }
+
+    {
+        KeyframeSequence *sequence = m3gAllocZ(m3g, sizeof(KeyframeSequence));
+
+        if (sequence != NULL) {
+            if (m3gInitKeyframeSequence(m3g,
+                                        sequence,
+                                        numKeyframes, numComponents,
+                                        interpolation) == NULL) {
+                m3gFree(m3g, sequence);
+                return NULL;
+            }
+        }
+
+        return (M3GKeyframeSequence) sequence;
+    }
+}
+
+/*!
+ * \brief Assigns a time and value to a keyframe sequence entry
+ *
+ * \param handle    handle of the keyframe sequence object
+ * \param ind       index of the entry to set
+ * \param time      time to set in the entry
+ * \param valueSize number of elements in the value; this must match
+ *                  the number of elements given when constructing
+ *                  the sequence
+ * \param value     pointer to an array of \c valueSize floats
+ */
+M3G_API void m3gSetKeyframe(M3GKeyframeSequence handle,
+                            M3Gint ind,
+                            M3Gint time,
+                            M3Gint valueSize, const M3Gfloat *value)
+{
+    KeyframeSequence *sequence = (KeyframeSequence *)handle;
+    M3G_VALIDATE_OBJECT(sequence);
+
+    /* Check for invalid inputs */
+    
+    if (value == NULL) {
+        m3gRaiseError(M3G_INTERFACE(sequence), M3G_NULL_POINTER);
+        return;
+    }
+    if (valueSize < sequence->numComponents || time < 0) {
+        m3gRaiseError(M3G_INTERFACE(sequence), M3G_INVALID_VALUE);
+        return;
+    }
+    if (ind < 0 || ind >= sequence->numKeyframes) {
+        m3gRaiseError(M3G_INTERFACE(sequence), M3G_INVALID_INDEX);
+        return;
+    }
+
+    /* Assign  the  time  and  value. Quaternion  keyframes  are  also
+     * normalized, as indicated in the specification. */    
+    {
+        M3Gfloat *kf = (M3Gfloat *) m3gKeyframeAt(sequence, ind);
+        int c;
+        
+        sequence->keyframeTimes[ind] = time;
+        
+        for (c = 0; c < sequence->numComponents; ++c) {
+            kf[c] = value[c];
+        }
+
+        if (sequence->interpolation == M3G_SLERP
+            || sequence->interpolation == M3G_SQUAD) {
+            m3gNormalizeQuat((Quat*) kf);
+        }
+    }
+    
+    sequence->dirty = M3G_TRUE;        
+}
+
+/*!
+ * \brief Set valid range
+ *
+ * \param handle    handle of the keyframe sequence object
+ * \param first     first valid keyframe
+ * \param last      last valid keyframe
+ */
+M3G_API void m3gSetValidRange(M3GKeyframeSequence handle,
+                              M3Gint first, M3Gint last)
+{
+    KeyframeSequence *sequence = (KeyframeSequence *)handle;
+    M3G_VALIDATE_OBJECT(sequence);
+
+    if (first < 0 || first >= sequence->numKeyframes ||
+        last < 0 || last >= sequence->numKeyframes) {
+        m3gRaiseError(M3G_INTERFACE(sequence), M3G_INVALID_INDEX);
+        return;
+    }
+
+    sequence->firstValid = first;
+    sequence->lastValid = last;
+    sequence->dirty = M3G_TRUE;
+}
+
+/*!
+ * \brief Set duration
+ *
+ * \param handle    handle of the keyframe sequence object
+ * \param duration  duration
+ */
+M3G_API void m3gSetDuration(M3GKeyframeSequence handle, M3Gint duration)
+{
+    KeyframeSequence *sequence = (KeyframeSequence *)handle;
+    M3G_VALIDATE_OBJECT(sequence);
+
+    /* Check for errors */
+    if (duration <= 0) {
+        m3gRaiseError(M3G_INTERFACE(sequence), M3G_INVALID_VALUE);
+        return;
+    }
+
+    sequence->duration = duration;
+    sequence->dirty = M3G_TRUE;
+}
+
+/*!
+ * \brief Get duration
+ *
+ * \param handle    handle of the keyframe sequence object
+ * \return          duration
+ */
+M3G_API M3Gint m3gGetDuration(M3GKeyframeSequence handle)
+{
+    KeyframeSequence *sequence = (KeyframeSequence *)handle;
+    M3G_VALIDATE_OBJECT(sequence);
+    return sequence->duration;
+}
+
+/*!
+ * \brief Get component count
+ *
+ * \param handle    handle of the keyframe sequence object
+ * \return          component count
+ */
+M3G_API M3Gint m3gGetComponentCount(M3GKeyframeSequence handle)
+{
+    KeyframeSequence *sequence = (KeyframeSequence *)handle;
+    M3G_VALIDATE_OBJECT(sequence);
+    return sequence->numComponents;
+}
+
+/*!
+ * \brief Get interpolation type
+ *
+ * \param handle    handle of the keyframe sequence object
+ * \return          interpolation type
+ */
+M3G_API M3Gint m3gGetInterpolationType(M3GKeyframeSequence handle)
+{
+    KeyframeSequence *sequence = (KeyframeSequence *)handle;
+    M3G_VALIDATE_OBJECT(sequence);
+    return sequence->interpolation;
+}
+
+/*!
+ * \brief Get keyframe value
+ *
+ * \param handle     handle of the keyframe sequence object
+ * \param frameIndex keyframe index
+ * \param value      value array
+ 
+ * \return           time value of the keyframe
+ */
+M3G_API M3Gint m3gGetKeyframe  (M3GKeyframeSequence handle, M3Gint frameIndex, M3Gfloat *value)
+{
+    KeyframeSequence *sequence = (KeyframeSequence *)handle;
+    M3G_VALIDATE_OBJECT(sequence);
+
+    if (frameIndex < 0 || frameIndex >= sequence->numKeyframes) {
+        m3gRaiseError(M3G_INTERFACE(sequence), M3G_INVALID_INDEX);
+        return 0;
+    }
+
+    if (value != NULL) {
+        m3gCopy(    value,
+                    sequence->keyframes + frameIndex * sequence->numComponents,
+                    sequence->numComponents * sizeof(M3Gfloat));
+    }
+
+    return sequence->keyframeTimes[frameIndex];
+}
+
+/*!
+ * \brief Get keyframe count
+ *
+ * \param handle    handle of the keyframe sequence object
+ * \return          keyframe count
+ */
+M3G_API M3Gint m3gGetKeyframeCount(M3GKeyframeSequence handle)
+{
+    KeyframeSequence *sequence = (KeyframeSequence *)handle;
+    M3G_VALIDATE_OBJECT(sequence);
+    return sequence->numKeyframes;
+}
+
+/*!
+ * \brief Get valid range
+ *
+ * \param handle    handle of the keyframe sequence object
+ * \param first     pointer to valid range start
+ * \param last      pointer to valid range end
+ */
+M3G_API void m3gGetValidRange(M3GKeyframeSequence handle, M3Gint *first, M3Gint *last)
+{
+    KeyframeSequence *sequence = (KeyframeSequence *)handle;
+    M3G_VALIDATE_OBJECT(sequence);
+    *first = sequence->firstValid;
+    *last = sequence->lastValid;
+}
+
+/*!
+ * \brief Set repeat mode
+ *
+ * \param handle    handle of the keyframe sequence object
+ * \param mode      repeat mode
+ */
+M3G_API void m3gSetRepeatMode(M3GKeyframeSequence handle, M3Genum mode)
+{
+    KeyframeSequence *sequence = (KeyframeSequence *)handle;
+    M3G_VALIDATE_OBJECT(sequence);
+    if (mode != M3G_CONSTANT && mode != M3G_LOOP) {
+        m3gRaiseError(M3G_INTERFACE(sequence), M3G_INVALID_ENUM);
+        return;
+    }
+    sequence->closed = (mode == M3G_LOOP) ? M3G_TRUE : M3G_FALSE;
+}
+
+/*!
+ * \brief Get repeat mode
+ *
+ * \param handle    handle of the keyframe sequence object
+ * \return          repeat mode
+ */
+M3G_API M3Genum m3gGetRepeatMode(M3GKeyframeSequence handle)
+{
+    KeyframeSequence *sequence = (KeyframeSequence *)handle;
+    M3G_VALIDATE_OBJECT(sequence);
+    return (sequence->closed ? M3G_LOOP : M3G_CONSTANT);
+}
+