Using Tween Components

This chapter describes how to create tween containers that the tween media handler uses. Several utility routines are also discussed.

The tween media handler uses tween components to perform tween operations (other than simple ones built into QuickTime). The components use containers to define their tween operations, and the containers are constructed from QT atoms. The tween media handler is discussed in Tween Media Handlers.

Creating a Single Tween Container

To create a single tween container, do the following:

  1. Create a QT atom container.

  2. Insert a kTweenEntry atom into the QT atom container for the tween. A kTweenEntry atom contains the atoms that define the tween.

  3. Insert a kTweenType atom that specifies the tween type into the kTweenEntry atom.

  4. Insert a kTweenData atom that contains the data for the tween into the kTweenEntry atom.

Listing 7-1 shows how to create a single kTweenTypeLong tween container that a component could use to interpolate two long integers.

Listing 7-1  Creating a single kTweenTypeLong tween container

QTAtomContainer container = nil;
long tweenDataLong[2];
QTAtomType tweenType;
QTAtom tweenAtom;
tweenDataLong[0] = EndianU32_NtoB(512);
tweenDataLong[1] = EndianU32_NtoB(0);
// create a new atom container
QTNewAtomContainer (&container);
// create the parent tween entry atom
tweenType = kTweenTypeLong;
QTInsertChild (container, kParentAtomIsContainer, kTweenEntry, 1, 0, 0,
                nil, &tweenAtom);
// add two child atoms to the tween entry atom --
// * the type atom, kTweenType
QTInsertChild (container, tweenAtom, kTweenType, 1, 0,
                sizeof(tweenType), &tweenType, nil);
// * the data atom, kTweenData
QTInsertChild (container, tweenAtom, kTweenData, 1, 0,
                sizeof(long) * 2, tweenDataLong, nil);

Using Path Tween Components

The following sections describe how to use a variety of path tween components. All path tween operations have as input a QuickTime vector data stream for a path. Path tweeners interpret their time input in one of these ways:

If the kTweenReturnDelta flag (in an optional kTweenFlags atom in the kTweenEntry atom) is set, a path tween returns the change in value from the last time it was invoked. If the flag is not set, or if the component has not previously been invoked, the component returns the normal result for the tween.

Using a List Tween Component

To use a list tween component (of type kTweenTypeAtomList), do the following:

  1. Create a QT atom container.

  2. Insert a kTweenEntry atom into the QT atom container for the tween.

  3. Insert a kTweenType atom that specifies the tween type into the kTweenEntry atom.

  4. Insert a kTweenData atom into the kTweenEntry atom. Unlike the previous example, this kTweenData atom is not a leaf atom.

  5. Insert a kListElementType atom that specifies the atom type of the list entries into the kTweenData atom. The list entries must be leaf atoms.

  6. Insert leaf atoms of the type specified by the kListElementType atom into the kTweenData atom.

The duration of the tween operation is divided by the number of leaf atoms of the specified type. For time points within the first time division (from the start of the duration up to an including the time (total time / number of atoms )), the data for the first leaf atom is returned; for the second time division, the data for the second leaf atom is returned; and so on.

Listing 7-2 shows how to create a list tween.

Listing 7-2  Creating a kTweenTypeAtomList tween container

QTAtomContainer container = nil;
long tweenDataLong[2];
QTAtomType tweenType;
QTAtom tweenAtom;
tweenDataLong[0] = EndianU32_NtoB(512);
tweenDataLong[1] = EndianU32_NtoB(0);
// create a new atom container to hold the sample
QTNewAtomContainer (&container);
// create the parent tween entry atom
tweenType = EndianU32_NtoB(kTweenTypeLong);
QTInsertChild (container, kParentAtomIsContainer, kTweenEntry, 1, 0, 0,
                nil, &tweenAtom);
// add two child atoms to the tween entry atom --
// * the type atom, kTweenType
QTInsertChild (container, tweenAtom, kTweenType, 1, 0,
                sizeof(tweenType), &tweenType, nil);
// * the data atom, kTweenData
QTInsertChild (container, tweenAtom, kTweenData, 1, 0,
                sizeof(short) * 2, tweenDataLong, nil);
 
OSErr       err = noErr;
QTTweener tween;
QTAtomContainer container = nil, listContainer = nil;
OSType      tweenerType;
TimeValue   offset, duration, tweenTime;
Handle      result;
QTAtom      tweenAtom;
tweenerType = EndianU32_NtoB(kTweenTypeAtomList);
offset  = 0;
duration = 3;
err = QTNewAtomContainer( &container );
if ( err ) goto bail;
err = AddTweenAtom( container, kParentAtomIsContainer, 1, tweenerType,
                    offset, duration, 0, 0, nil, &tweenAtom );
if ( err ) goto bail;
listContainer = CreateSampleAtomListTweenData( 1 );
if ( listContainer == nil ) { err = memFullErr; goto bail; }
err = AddDataAtom( container, tweenAtom, 1, 0, nil,
                    listContainer, 0, nil );
if ( err ) goto bail;
err = QTNewTween( &tween, container, tweenAtom, duration );
if ( err ) goto bail;
result = NewHandle( 0 );
if ( err = MemError() ) goto bail;
    // exercise the AtomListTweener
    for ( tweenTime = 1; tweenTime <= duration; tweenTime += 1 ) {
        long pictureID;
 
        err = QTDoTween( tween, tweenTime, result, nil, nil, nil );
        if ( err ) goto bail;
 
        // the pictureID from the atomDataList corresponding to tweenTime
        pictureID = *(long *)*result;
    }
 
    err = QTDisposeTween( tween );
bail:
    if ( container ) QTDisposeAtomContainer( container );
    if ( listContainer ) QTDisposeAtomContainer( listContainer );
    if ( result ) DisposeHandle( result );
    return err;
}

Utility Routines

The examples in the next sections use several utility routines to modularize their code. These routines are the following:

AddTweenAtom

Listing 7-3 shows AddTweenAtom, a routine that adds an atom of type kTweenEntry plus its standard child atoms (other than the kTweenDataAtom) to a container. Only the tweenAtomID and tweenerType parameters are required; you may pass 0 for the other parameters to avoid using them.

The minOutput and maxOutput atom parameters are necessary only if the tweener is being used as an interpolator. Passing a tweenAtomID value of 0 means that the routine can assign any unique ID.

If you use AddTweenAtom to add a nonsequenced tween entry to a container, the kTweenEntry atom it creates is the atom you pass to QTNewTween and the sequenceAtom you pass in may be kParentAtomIsContainerTween Components and Tween Media. In this case the tweenAtomID value should be 1.

If you use AddTweenAtom to add a sequenced tween entry to a container, the newSequenceAtom returned by AddSequenceTweenAtom is its sequenceAtom parameter and will also be the atom you pass to QTNewTween. Note that in most cases a sequenced tween contains only one tween entry but may contain multiple data atoms.

All tween atoms within same sequenceAtom must have same tween type. It is unlikely that you would want to change the duration or offset values within the same sequenceAtom.

Listing 7-3  Utility routine AddTweenAtom

OSErr AddTweenAtom( QTAtomContainer container, QTAtom sequenceAtom,
    QTAtomID tweenAtomID, OSType tweenerType, TimeValue offset,
    TimeValue duration, Fixed minOutput, Fixed maxOutput, StringPtr name,
    QTAtom *newTweenAtom )
{
    OSErr       err = noErr;
    QTAtom      tweenAtom = 0;
    if ( ! container )      { err = paramErr; goto bail; }
    err = QTInsertChild( container, sequenceAtom, kTweenEntry,
                            tweenAtomID, 0, 0, nil, &tweenAtom );
    if ( err ) goto bail;
    err = QTInsertChild( container, tweenAtom, kTweenType, 1, 1,
                            sizeof(tweenerType), &tweenerType, nil );
    if ( err ) goto bail;
    if ( offset ) {
        err = QTInsertChild( container, tweenAtom, kTweenStartOffset, 1,
                                1, sizeof(offset), &offset, nil );
        if ( err ) goto bail;
    }
    if ( duration ) {
        err = QTInsertChild( container, tweenAtom, kTweenDuration, 1, 1,
                                sizeof(duration), &duration, nil );
        if ( err ) goto bail;
    }
    // default minOutput is zero, so this is OK
    if ( minOutput ) {
        err = QTInsertChild( container, tweenAtom, kTweenOutputMin, 1, 1,
                                sizeof(minOutput), &minOutput, nil );
        if ( err ) goto bail;
    }
 
    if ( maxOutput ) {
        err = QTInsertChild( container, tweenAtom, kTweenOutputMax, 1, 1,
                                sizeof(maxOutput), &maxOutput, nil );
        if ( err ) goto bail;
    }
    if ( name ) {
        err = QTInsertChild( container, tweenAtom, kNameAtom, 1, 1,
                                name[0] + 1, name, nil );
        if ( err ) goto bail;
    }
bail:
    if ( newTweenAtom )
        *newTweenAtom = tweenAtom;
    return err;
}

AddDataAtom

Listing 7-4 shows AddDataAtom, a routine that adds a data atom with an ID of dataAtomID as a child atom of tweenAtom. If dataSize is nonzero, then leaf data is copied from dataPtr to dataAtom. Otherwise (if dataContainer in not nilTween Components and Tween Media), child atoms are copied from dataContainer. If you wish to add the actual data by using another routine, you may pass 0 in dataSize and nil in both dataPtr and dataContainer.

You can associate a tweener to be used as an interpolator for each dataAtom value. The interpolationTweenID parameter specifies the ID of a kTweenEntry atom that is a child of the newSequenceAtom returned by AddSequenceTweenAtom. If you specify an interpolation tweener, then the atTime parameter of the DoTween routine is first fed as an input to the interpolation tweener. The tweenResult of the interpolation tweener becomes the atTime parameter of the succeeding tweener. Note that the kTweenData atom and the kTweenInterpolationID atom have the same ID; this is how QuickTime groups them together.

For best performance, the output range of an interpolation tweener should be from 0 to the duration of the regular tweener. However, you may specify the minimum and maximum values that the interpolation tweener returns; this lets the tweener be shared. The minimum and maximum values are used to scale tweenResult, and are added as child atoms of a kTweenEntry in AddTweenAtom.

Listing 7-4  Utility routine AddDataAtom

OSErr AddDataAtom( QTAtomContainer container, QTAtom tweenAtom,
    QTAtomID dataAtomID, long dataSize, Ptr dataPtr,
    QTAtomContainer dataContainer, QTAtomID interpolationTweenID,
    QTAtom *newDataAtom )
{
    OSErr       err = noErr;
    QTAtom      dataAtom = 0;
    if ( (! container) || (dataAtomID == 0) || (dataSize &&
        (dataContainer || !dataPtr)) )  { err = paramErr; goto bail; }
    err = QTInsertChild( container, tweenAtom, kTweenData, dataAtomID, 0,
                            dataSize, dataPtr, &dataAtom );
    if ( err ) goto bail;
    if ( dataSize ) {
        err = QTSetAtomData( container, dataAtom, dataSize, dataPtr );
        if ( err ) goto bail;
    }
    else if ( dataContainer ) {
        err = QTInsertChildren( container, dataAtom, dataContainer );
        if ( err ) goto bail;
    }
    if ( interpolationTweenID ) {
        err = QTInsertChild( container, tweenAtom, kTweenInterpolationID,
                            dataAtomID, 0, sizeof(interpolationTweenID),
                            &interpolationTweenID, nil );
        if ( err ) goto bail;
    }
bail:
    if ( newDataAtom )
        *newDataAtom = dataAtom;
    return err;
}

AddSequenceTweenAtom

Listing 7-5 shows AddSequenceTweenAtom, a routine that adds a kTweenEntry atom for a sequenced tween. The newSequenceAtom returned may be passed into the AddTweenAtom and AddSequenceElement routines as their sequenceAtom parameter.

To create a nonsequenced tween, use AddTweenAtom instead.

Listing 7-5  Utility routine AddSequenceTweenAtom

OSErr AddSequenceTweenAtom( QTAtomContainer container, QTAtom parentAtom,
                    QTAtomID sequenceAtomID, QTAtom *newSequenceAtom )
{
    if ( (! container) || (sequenceAtomID == 0) )  { return paramErr; }
    return QTInsertChild( container, parentAtom, kTweenEntry,
                        sequenceAtomID, 0, 0, nil, newSequenceAtom );
}

AddSequenceElement

Listing 7-6 shows AddSequenceElement, a routine that adds a leaf atom of type kTweenSequenceElement to a container. The sequenceAtom that you pass in is the newSequenceAtom parameter returned by AddSequenceTweenAtom. Each time you call AddSequenceElement, a sequence tween element is added to the end of the list of elements. The tween toolbox organizes the list in index order, with IDs ignored.

Each element has a duration that is some percentage of the tween’s duration. The element’s duration is its endPercent value minus the previous element’s endPercent value. For example, if you wanted three elements to last 0.25, 0.25, and 0.5 of the tween’s duration, then the elements' endPercent values should be set to 0.25, 0.5, and 1. The elements tell the tween toolbox which tweenAtom and dataAtom to switch to.

The tweenAtomID is the ID of a kTweenEntry atom within the sequenceAtom. The dataAtomID is the ID of a kTweenData atom. The kTweenData atom is a child atom of the specified tweenAtom. Usually you only need to create one tweenAtom with multiple data atoms. Some tweener types (such as 3D tweeners) use child atoms of the tweenEntry atom for initialization, so in these cases you usually create a tweenAtom with one dataAtom per sequence entry.

Listing 7-6  Utility routine AddSequenceElement

OSErr AddSequenceElement( QTAtomContainer container, QTAtom sequenceAtom,
    Fixed endPercent, QTAtomID tweenAtomID, QTAtomID dataAtomID,
    QTAtom *newSequenceElementAtom )
{
    TweenSequenceEntryRecord    entry;
    if ( (! container) || (endPercent > (1L<<16)) || (tweenAtomID == 0)
                            || (dataAtomID == 0) ){ return paramErr; }
    entry.endPercent        = endPercent;
    entry.tweenAtomID       = tweenAtomID;
    entry.dataAtomID        = dataAtomID;
    // adds at end of list by index, with any unique atom id
    return QTInsertChild( container, sequenceAtom, kTweenSequenceElement,
                0, 0, sizeof(entry), &entry, newSequenceElementAtom );
}

CreateSampleAtomListTweenData

Listing 7-7 shows the CreateSampleAtomListTweenData routine.

Listing 7-7  Utility routine CreateSampleAtomListTweenData

QTAtomContainer CreateSampleAtomListTweenData( long whichOne )
{
    OSErr               err = noErr;
    QTAtomContainer     atomListContainer;
    QTAtomType          tweenAtomType;
    UInt32              elementDataType;
    UInt16              resourceID;
 
    err = QTNewAtomContainer( &atomListContainer );
    if ( err ) goto bail;
 
    // kListElementDataType atom specifies the data type of the elements
    elementDataType = kTweenTypeShort;
    err = QTInsertChild( atomListContainer, kParentAtomIsContainer,
                        kListElementDataType, 1, 1,
                        sizeof(tweenAtomType),
                        &tweenAtomType, nil );
    // kListElementType atom tells which type of atoms to look for
    tweenAtomType = 'pcid';
    err = QTInsertChild( atomListContainer, kParentAtomIsContainer,
                        kListElementType, 1, 1, sizeof(tweenAtomType),
                        &tweenAtomType, nil );
    switch ( whichOne ) {
        case 1:
            resourceID = 1000;
            err = QTInsertChild( atomListContainer,
                        kParentAtomIsContainer, 'pcid', 1, 1,
                        sizeof(resourceID), &resourceID, nil );
            if ( err ) goto bail;
 
            resourceID = 1001;
            err = QTInsertChild( atomListContainer,
                        kParentAtomIsContainer, 'pcid', 2, 2,
                        sizeof(resourceID), &resourceID, nil );
            if ( err ) goto bail;
            resourceID = 1002;
            err = QTInsertChild( atomListContainer,
                        kParentAtomIsContainer, 'pcid', 3, 3,
                        sizeof(resourceID), &resourceID, nil );
            if ( err ) goto bail;
        break;
 
        case 2:
            resourceID = 1003;
            err = QTInsertChild( atomListContainer,
                        kParentAtomIsContainer, 'pcid', 1, 1,
                        sizeof(resourceID), &resourceID, nil );
            if ( err ) goto bail;
 
            resourceID = 1004;
            err = QTInsertChild( atomListContainer,
                        kParentAtomIsContainer, 'pcid', 2, 2,
                        sizeof(resourceID), &resourceID, nil );
            if ( err ) goto bail;
            resourceID = 1005;
            err = QTInsertChild( atomListContainer,
                        kParentAtomIsContainer, 'pcid', 3, 3,
                        sizeof(resourceID), &resourceID, nil );
            if ( err ) goto bail;
        break;
    }
 
bail:
    if ( err && atomListContainer )
        { QTDisposeAtomContainer( atomListContainer );
          atomListContainer = nil; }
    return atomListContainer;
}

Using a Polygon Tween Component

A polygon tweener maps a four-sided polygon, such as the boundary of a sprite or track, into another. It can be used to create perspective effects, in which the shape of the destination polygon changes over time. The range of polygons into which the source polygon is mapped is defined by two additional four-sided polygons, which are interpolated to specify a destination polygon for any time point in the tween duration.

To use a polygon tween component (of type kTweenTypePolygonTween Components and Tween Media), do the following:

  1. Create a QT atom container.

  2. Insert a kTweenEntry atom into the QT atom container for the tween.

  3. Insert a kTweenType atom that specifies the tween type into the kTweenEntry atom.

  4. Insert a kTweenData atom into the kTweenEntry atom.

The data is an array of 27 fixed-point values (Fixed[27]Tween Components and Tween Media) that specifies the three four-sided polygons. Each polygon is specified by 9 consecutive array elements. The first element is each set of 9 contains the number of points used to specify the polygon; this value is coerced to a long integer, and it must always be 4 after coercion. The following 8 values in each set of nine are four x, y pairs that specify the corners of the polygon.

The first set of 9 elements specifies the dimensions of a sprite or track to be mapped. For example, if the object is a sprite, the four points are (0,0), (spriteWidth, 0 ), (spriteWidth, spriteHeight), (0, spriteHeight). The next set of 9 elements specifies the initial polygon into which the sprite or track is mapped. The next set of 9 elements specifies the final polygon into which the sprite or track is mapped.

The output is a MatrixRecord data structure that you use to map the sprite or track into a four-sided polygon.

Listing 7-8 shows how to create a polygon tween.

Listing 7-8  Creating a polygon tween container

OSErr CreateSamplePolygonTweenContainer( QTAtomContainer container,
    TimeValue duration, QTAtom *newTweenAtom )
{
    OSErr           err = noErr;
    TimeValue       offset;
    Handle          thePolygonData = nil;
    QTAtom          tweenAtom;
 
    err = QTRemoveChildren( container, kParentAtomIsContainer );
    if ( err ) goto bail;
    offset = 0;
    err = AddTweenAtom( container, kParentAtomIsContainer, 1,
                        kTweenTypePolygon, offset, duration, 0, 0,
                        nil, &tweenAtom );
    if ( err ) goto bail;
 
    thePolygonData = CreateSamplePolygonData();
    if ( thePolygonData == nil ) { err = memFullErr; goto bail; }
 
    HLock( thePolygonData );
 
    err = AddDataAtom( container, tweenAtom, 1,
                        GetHandleSize( thePolygonData ),
                        *thePolygonData, nil, 0, nil );
    if ( err ) goto bail;
bail:
    if ( thePolygonData ) DisposeHandle( thePolygonData );
    if ( newTweenAtom ) *newTweenAtom = tweenAtom;
    return err;
}
 
Handle CreateSamplePolygonData( void )
{
    OSErr       err = noErr;
    Handle      polygonData;
    Fixed       *poly;
    polygonData = NewHandle( 27 * sizeof(Fixed) );
    if ( polygonData == nil ) { err = memFullErr; goto bail; }
 
    poly = (Fixed *)*polygonData;
 
    poly[0] = EndianU32_NtoB(4);                // source dimensions
    poly[1] = EndianU32_NtoB(Long2Fix( 0 ));
    poly[2] = EndianU32_NtoB(Long2Fix( 0 ));
    poly[3] = EndianU32_NtoB(Long2Fix( 100 ));
    poly[4] = EndianU32_NtoB(Long2Fix( 0 ));
    poly[5] = EndianU32_NtoB(Long2Fix( 100 ));
    poly[6] = EndianU32_NtoB(Long2Fix( 100 ));
    poly[7] = EndianU32_NtoB(Long2Fix( 0 ));
    poly[8] = EndianU32_NtoB(Long2Fix( 100 ));
    poly[9] = EndianU32_NtoB(4);                // tween from polygon
    poly[10] = EndianU32_NtoB(Long2Fix( 100 ));
    poly[11] = EndianU32_NtoB(Long2Fix( 100 ));
    poly[12] = EndianU32_NtoB(Long2Fix( 200 ));
    poly[13] = EndianU32_NtoB(Long2Fix( 100 ));
    poly[14] = EndianU32_NtoB(Long2Fix( 200 ));
    poly[15] = EndianU32_NtoB(Long2Fix( 200 ));
    poly[16] = EndianU32_NtoB(Long2Fix( 100 ));
    poly[17] = EndianU32_NtoB(Long2Fix( 200 ));
    poly[18] = EndianU32_NtoB(4);               // tween to polygon
    poly[19] = EndianU32_NtoB(Long2Fix( 140 ));
    poly[20] = EndianU32_NtoB(Long2Fix( 100 ));
    poly[21] = EndianU32_NtoB(Long2Fix( 160 ));
    poly[22] = EndianU32_NtoB(Long2Fix( 100 ));
    poly[23] = EndianU32_NtoB(Long2Fix( 200 ));
    poly[24] = EndianU32_NtoB(Long2Fix( 200 ));
    poly[25] = EndianU32_NtoB(Long2Fix( 100 ));
    poly[26] = EndianU32_NtoB(Long2Fix( 200 ));
bail:
    return polygonData;
}

Specifying an Offset for a Tween Operation

You can start a tween operation after a tween media sample begins by including an optional kTweenStartOffset atom in the kTweenEntry atom for the tween. This atom specifies a time interval, beginning at the start of the tween media sample, after which the tween operation begins. If this atom is not included, the tween operation begins at the start of the tween media sample.

Specifying a Duration for a Tween

You can specify the duration of a tween operation by including an optional kTweenDuration atom in the kTweenEntry atom for the tween. When a QuickTime movie includes a tween track, the time units for the duration are those of the tween track’s time scale. If a tween component is used outside of a movie, the application using the tween data determines how the duration value and values returned by the component are interpreted.

Creating a Tween Sequence

Single Tweens and Tween Sequences discussed tween sequences, in which different tween operations of the same type may be applied sequentially. The type kTweenSequenceElement specifies an entry in a tween sequence. Its parent is the tween QT atom container (which you specify with the constant kParentAtomIsContainerTween Components and Tween Media).

The ID of a kTweenSequenceElement atom must be unique among the kTweenSequenceElement atoms in the same QT atom container. The index of a kTweenSequenceElement atom specifies its order in the sequence; the first entry in the sequence has the index 1, the second 2, and so on.

This atom is a leaf atom. The data type of its data is TweenSequenceEntryRecord, a data structure that contains the following fields:

Listing 7-9 shows how to create a tween sequence.

Listing 7-9  Creating a tween sequence

OSErr CreateSampleSequencedTweenContainer( QTAtomContainer container,
    TimeValue duration, QTAtom *newTweenAtom )
{
    OSErr               err = noErr;
    QTAtomContainer     dataContainer = nil;
    OSType              tweenerType;
    QTAtom              sequenceAtom, tweenAtom;
    TimeValue           offset;
    Handle              result;
    QTAtomID            tweenAtomID, dataAtomID;
    Fixed               endPercent;
 
    err = QTRemoveChildren( container, kParentAtomIsContainer );
    if ( err ) goto bail;
    tweenerType = kTweenTypeAtomList;
    offset  = 0;
    err = AddSequenceTweenAtom( container, kParentAtomIsContainer,
                                1, &sequenceAtom );
    if ( err ) goto bail;
 
    offset = 0;
 
    err = AddTweenAtom( container, sequenceAtom, 1, tweenerType, offset,
                        duration, 0, 0, nil, &tweenAtom );
    if ( err ) goto bail;
 
    // add first data atom (id 1) to tween atom
    dataAtomID = 1;
    dataContainer = CreateSampleAtomListTweenData( dataAtomID );
    if ( ! dataContainer ) { err = memFullErr; goto bail; }
    err = AddDataAtom( container, tweenAtom, dataAtomID, 0, nil,
                        dataContainer, 0, nil );
    if ( err ) goto bail;
 
    QTDisposeAtomContainer( dataContainer );
 
    // add second data atom (id 2) to tween atom
    dataAtomID = 2;
    dataContainer = CreateSampleAtomListTweenData( dataAtomID );
    if ( ! dataContainer ) { err = memFullErr; goto bail; }
    err = AddDataAtom( container, tweenAtom, dataAtomID, 0, nil,
                        dataContainer, 0, nil );
    if ( err ) goto bail;
 
    QTDisposeAtomContainer( dataContainer );
    // now create a sequence with four elements; the first three are data
    // atom 1, the last is data atom 2
    endPercent = FixDiv( Long2Fix(25), Long2Fix(100) );
    tweenAtomID = 1;
    dataAtomID = 1;
    err = AddSequenceElement( container, sequenceAtom, endPercent,
                                tweenAtomID, dataAtomID, nil );
    if ( err ) goto bail;
    endPercent = FixDiv( Long2Fix(50), Long2Fix(100) );
    tweenAtomID = 1;
    dataAtomID = 1;
    err = AddSequenceElement( container, sequenceAtom, endPercent,
                                tweenAtomID, dataAtomID, nil );
    if ( err ) goto bail;
    endPercent = FixDiv( Long2Fix(75), Long2Fix(100) );
    tweenAtomID = 1;
    dataAtomID = 1;
    err = AddSequenceElement( container, sequenceAtom, endPercent,
                                tweenAtomID, dataAtomID, nil );
    if ( err ) goto bail;
    endPercent = FixDiv( Long2Fix(100), Long2Fix(100) );
    tweenAtomID = 1;
    dataAtomID = 2;
    err = AddSequenceElement( container, sequenceAtom, endPercent,
                                tweenAtomID, dataAtomID, nil );
    if ( err ) goto bail;
bail:
    if ( err ) {
        if ( container )
            QTRemoveChildren( container, kParentAtomIsContainer );
        *newTweenAtom = nil;
    }
    else
        *newTweenAtom = sequenceAtom;
}

Naming Tweens

You can use the kNameAtom atom to store a string value containing a name (or any other information) in a tween container. This atom is not required and is not routinely accessed by QuickTime. It is available for use by your authoring tools or other software.

CreateSampleVectorData Utility

Listing 7-10 shows the CreateSampleVectorData routine.

Listing 7-10  Utility routine CreateSampleVectorData

Handle CreateSampleVectorData( long whichOne )
{
    OSErr               err;
    Handle              pathData = nil, vectorData = nil;
    ComponentInstance   ci = nil;
    gxPoint             aPoint;
 
    err = OpenADefaultComponent( decompressorComponentType,
                                    kVectorCodecType, &ci );
    if ( err ) goto bail;
    err = CurveNewPath( ci, &pathData );
    if ( err ) goto bail;
    if ( pathData == nil )
        { err = memFullErr; goto bail; }
    switch ( whichOne ) {
        case 1:
            aPoint.x = Long2Fix( 0 );
            aPoint.y = Long2Fix( 100 );
            err = CurveInsertPointIntoPath( ci, &aPoint, pathData,
                                            0, 0, false );
            if ( err ) goto bail;
 
            aPoint.x = Long2Fix( 100 );
            aPoint.y = Long2Fix( 0 );
            err = CurveInsertPointIntoPath( ci, &aPoint, pathData,
                                            0, 1, false );
            if ( err ) goto bail;
            aPoint.x = Long2Fix( 200 );
            aPoint.y = Long2Fix( 100 );
            err = CurveInsertPointIntoPath( ci, &aPoint, pathData,
                                            0, 2, false );
            if ( err ) goto bail;
            aPoint.x = Long2Fix( 100 );
            aPoint.y = Long2Fix( 200 );
            err = CurveInsertPointIntoPath( ci, &aPoint, pathData,
                                            0, 3, false );
            if ( err ) goto bail;
        break;
        case 2:
            aPoint.x = 0;
            aPoint.y = 100;
            err = CurveInsertPointIntoPath( ci, &aPoint, pathData,
                                            0, 0, false );
            if ( err ) goto bail;
 
            aPoint.x = 100;
            aPoint.y = 0;
            err = CurveInsertPointIntoPath( ci, &aPoint, pathData,
                                            0, 1, false );
            if ( err ) goto bail;
            aPoint.x = 200;
            aPoint.y = 100;
            err = CurveInsertPointIntoPath( ci, &aPoint, pathData,
                                            0, 2, false );
            if ( err ) goto bail;
            aPoint.x = 100;
            aPoint.y = 200;
            err = CurveInsertPointIntoPath( ci, &aPoint, pathData,
                                            0, 3, false );
            if ( err ) goto bail;
        break;
        case 3:
            aPoint.x = 0;
            aPoint.y = 0;
            err = CurveInsertPointIntoPath( ci, &aPoint, pathData,
                                            0, 0, true );
            if ( err ) goto bail;
 
            aPoint.x = 200;
            aPoint.y = 50;
            err = CurveInsertPointIntoPath( ci, &aPoint, pathData,
                                            0, 1, true );
            if ( err ) goto bail;
            aPoint.x = 400;
            aPoint.y = 400;
            err = CurveInsertPointIntoPath( ci, &aPoint, pathData,
                                            0, 2, true );
            if ( err ) goto bail;
        break;
        case 4:
            aPoint.x = Long2Fix( 0 );
            aPoint.y = Long2Fix( 0 );
            err = CurveInsertPointIntoPath( ci, &aPoint, pathData,
                                            0, 0, true );
            if ( err ) goto bail;
 
            aPoint.x = Long2Fix( 200 );
            aPoint.y = Long2Fix( 50 );
            err = CurveInsertPointIntoPath( ci, &aPoint, pathData,
                                            0, 1, true );
            if ( err ) goto bail;
            aPoint.x = Long2Fix( 400 );
            aPoint.y = Long2Fix( 400 );
            err = CurveInsertPointIntoPath( ci, &aPoint, pathData,
                                            0, 2, true );
            if ( err ) goto bail;
        break;
    }
    err = CurveCreateVectorStream( ci, &vectorData );
    if ( err ) goto bail;
    err = CurveAddPathAtomToVectorStream( ci, pathData, vectorData );
    if ( err ) goto bail;
 
    err = CurveAddZeroAtomToVectorStream( ci, vectorData );
    if ( err ) goto bail;
bail:
    if ( pathData ) DisposeHandle( pathData );
    if ( ci ) CloseComponent( ci );
    if ( err != noErr ) {
        if ( vectorData ) {
            DisposeHandle( vectorData );
            vectorData = nil;
        }
    }
    return vectorData;
}

CreateSamplePathTweenContainer Utility

Listing 7-11 shows how to use the CreateSamplePathTweenContainer utility routine.

Listing 7-11  Utility routine CreateSamplePathTweenContainer

OSErr CreateSamplePathTweenContainer( QTAtomContainer container,
    OSType tweenerType, long whichSamplePath, Boolean returnDelta,
    TimeValue duration, Fixed initialRotation, QTAtom *newTweenAtom )
{
    OSErr           err = noErr;
    TimeValue       offset;
    Handle          thePathData = nil;
    QTAtom          tweenAtom;
 
    err = QTRemoveChildren( container, kParentAtomIsContainer );
    if ( err ) goto bail;
    offset = 0;
    err = AddTweenAtom( container, kParentAtomIsContainer, 1,
                        tweenerType, offset, duration, 0, 0, nil,
                        &tweenAtom );
    if ( err ) goto bail;
 
    thePathData = CreateSampleVectorData( whichSamplePath );
    if ( thePathData == nil ) { err = memFullErr; goto bail; }
 
    HLock( thePathData );
 
    err = AddDataAtom( container, tweenAtom, 1,
                        GetHandleSize( thePathData ), *thePathData,
                        nil, 0, nil );
    if ( err ) goto bail;
    if ( returnDelta ) {
        err = AddPathTweenFlags( container, tweenAtom,
                                    kTweenReturnDelta );
    }
    if ( initialRotation )
        QTInsertChild( container, tweenAtom, kInitialRotationAtom,
                1, 1, sizeof(initialRotation), &initialRotation, nil );
bail:
    if ( thePathData ) DisposeHandle( thePathData );
    if ( newTweenAtom ) *newTweenAtom = tweenAtom;
    return err;

Using a kTweenTypePathToMatrixTranslation Tween Component

To use a kTweenTypePathToMatrixTranslation tween component, do the following:

  1. Create a QT atom container.

  2. Insert a kTweenEntry atom into the QT atom container for the tween.

  3. Insert a kTweenType atom that specifies the tween type into the kTweenEntry atom.

  4. Insert a kTweenData atom into the kTweenEntry atom.

  5. Perform the tweening operation, using QTDoTween.

Listing 7-12 shows how to create a kTweenTypePathToMatrixTranslation tween.

Listing 7-12  Creating a kTweenTypePathToMatrixTranslation tween container

OSErr               err = noErr;
TimeValue           tweenTime, duration;
Handle              result = nil;
QTAtomContainer     container = nil;
QTTweener           tween = nil;
QTAtom              tweenAtom;
duration = 8;
result = NewHandle( 0 );
if ( err = MemError() ) goto bail;
err = QTNewAtomContainer( &container );
if ( err ) goto bail;
err = CreateSamplePathTweenContainer( container,
                                    kTweenTypePathToMatrixTranslation, 1,
                                    false, duration, 0, &tweenAtom );
if ( err ) goto bail;
err = QTNewTween( &tween, container, tweenAtom, duration );
if ( err ) goto bail;
for ( tweenTime = 0; tweenTime <= duration; tweenTime++ ) {
    MatrixRecord absoluteMatrix;
 
    err = QTDoTween( tween, tweenTime, result, nil, nil, nil );
    if ( err ) goto bail;
 
    absoluteMatrix = *(MatrixRecord *)*result;
}
 
err = QTDisposeTween( tween );
bail:
    if ( container ) QTDisposeAtomContainer( container );
    if ( result ) DisposeHandle( result );

Listing 7-13 shows how to create a kTweenTypePathToMatrixTranslation tween in which the the kTweenReturnDelta flag is set.

Listing 7-13  Creating a kTweenTypePathToMatrixTranslation tween

err = CreateSamplePathTweenContainer( container,
                                    kTweenTypePathToMatrixTranslation, 1,
                                    true, duration, 0, &tweenAtom );
if ( err ) goto bail;
err = QTNewTween( &tween, container, tweenAtom, duration );
if ( err ) goto bail;
for ( tweenTime = 0; tweenTime <= duration; tweenTime++ ) {
    MatrixRecord deltaMatrix;
 
    err = QTDoTween( tween, tweenTime, result, nil, nil, nil );
    if ( err ) goto bail;
 
    deltaMatrix = *(MatrixRecord *)*result;
}
 
err = QTDisposeTween( tween );
bail:
    if ( container ) QTDisposeAtomContainer( container );
    if ( result ) DisposeHandle( result );

Using a kTweenTypePathToFixedPoint Tween Component

To use a kTweenTypePathToFixedPoint tween component, do the following:

  1. Create a QT atom container.

  2. Insert a kTweenEntry atom into the QT atom container for the tween.

  3. Insert a kTweenType atom that specifies the tween type into the kTweenEntry atom.

  4. Insert a kTweenData atom into the kTweenEntry atom.

  5. Perform the tweening operation, using QTDoTween.

Listing 7-14 shows how to create a kTweenTypePathToFixedPoint tween.

Listing 7-14  Creating a kTweenTypePathToFixedPoint tween container

err = CreateSamplePathTweenContainer( container,
                                    kTweenTypePathToFixedPoint, 2, false,
                                    duration, 0, &tweenAtom );
if ( err ) goto bail;
err = QTNewTween( &tween, container, tweenAtom, duration );
if ( err ) goto bail;
for ( tweenTime = 0; tweenTime <= duration; tweenTime++ ) {
    gxPoint absolutePoint;
 
    err = QTDoTween( tween, tweenTime, result, nil, nil, nil );
    if ( err ) goto bail;
 
    absolutePoint = *(gxPoint *)*result;
}
 
err = QTDisposeTween( tween );
bail:
    if ( container ) QTDisposeAtomContainer( container );
    if ( result ) DisposeHandle( result );

Listing 7-15 shows how to create a kTweenTypePathToFixedPoint tween in which the kTweenReturnDelta flag is set.

Listing 7-15  Creating a kTweenTypePathToFixedPoint tween container

err = CreateSamplePathTweenContainer( container,
                                    kTweenTypePathToFixedPoint, 2, true,
                                    duration, 0, &tweenAtom );
if ( err ) goto bail;
err = QTNewTween( &tween, container, tweenAtom, duration );
if ( err ) goto bail;
for ( tweenTime = 0; tweenTime <= duration; tweenTime++ ) {
    gxPoint deltaPoint;
 
    err = QTDoTween( tween, tweenTime, result, nil, nil, nil );
    if ( err ) goto bail;
 
    deltaPoint = *(gxPoint *)*result;
}
 
err = QTDisposeTween( tween );
bail:
    if ( container ) QTDisposeAtomContainer( container );
    if ( result ) DisposeHandle( result );

Using a kTweenTypePathToMatrixRotation Tween Component

To use a kTweenTypePathToMatrixRotation tween component, do the following:

  1. Create a QT atom container.

  2. Insert a kTweenEntry atom into the QT atom container for the tween.

  3. Insert a kTweenType atom that specifies the tween type into the kTweenEntry atom.

  4. Insert a kTweenData atom into the kTweenEntry atom.

  5. Perform the tweening operation, using QTDoTween.

Listing 7-16 shows how to create a kTweenTypePathToMatrixRotation tween.

Listing 7-16  Creating a kTweenTypePathToMatrixRotation tween container

// kTweenTypePathToMatrixRotation
err = CreateSamplePathTweenContainer( container,
                                kTweenTypePathToMatrixRotation, 1, false,
                                duration, X2Fix(0.5), &tweenAtom );
if ( err ) goto bail;
err = QTNewTween( &tween, container, tweenAtom, duration );
if ( err ) goto bail;
for ( tweenTime = 0; tweenTime <= duration; tweenTime++ ) {
    MatrixRecord absoluteMatrix;
 
    err = QTDoTween( tween, tweenTime, result, nil, nil, nil );
    if ( err ) goto bail;
 
    absoluteMatrix = *(MatrixRecord *)*result;
}
 
err = QTDisposeTween( tween );
bail:
    if ( container ) QTDisposeAtomContainer( container );
    if ( result ) DisposeHandle( result );

Using a kTweenTypePathToMatrixTranslationAndRotation Tween Component

To use a kTweenTypePathToMatrixTranslationAndRotation tween component, do the following:

  1. Create a QT atom container.

  2. Insert a kTweenEntry atom into the QT atom container for the tween.

  3. Insert a kTweenType atom that specifies the tween type into the kTweenEntry atom.

  4. Insert a kTweenData atom into the kTweenEntry atom.

  5. Perform the tweening operation, using QTDoTween.

Listing 7-17 shows how to create a kTweenTypePathToMatrixTranslationAndRotation tween.

Listing 7-17  Creating a kTweenTypePathToMatrixTranslationAndRotation tween container

err = CreateSamplePathTweenContainer( container,
                            kTweenTypePathToMatrixTranslationAndRotation,
                            1, false, duration, X2Fix(0.5), &tweenAtom );
if ( err ) goto bail;
err = QTNewTween( &tween, container, tweenAtom, duration );
if ( err ) goto bail;
for ( tweenTime = 0; tweenTime <= duration; tweenTime++ ) {
    MatrixRecord absoluteMatrix;
 
    err = QTDoTween( tween, tweenTime, result, nil, nil, nil );
    if ( err ) goto bail;
 
    absoluteMatrix = *(MatrixRecord *)*result;
}
 
err = QTDisposeTween( tween );
bail:
    if ( container ) QTDisposeAtomContainer( container );
    if ( result ) DisposeHandle( result );

Using a kTweenTypePathXtoY Tween Component

To use kTweenTypePathXtoY tween components, either absolute or delta, do the following:

  1. Create a QT atom container.

  2. Insert a kTweenEntry atom into the QT atom container for the tween.

  3. Insert a kTweenType atom that specifies the tween type into the kTweenEntry atom.

  4. Insert a kTweenData atom into the kTweenEntry atom.

  5. Perform the tweening operation, using QTDoTween.

Listing 7-18 shows how to create both kinds of kTweenTypePathXtoY tweens.

Listing 7-18  Creating kTweenTypePathXtoY tweens container

// kTweenTypePathXtoY - normal
err = CreateSamplePathTweenContainer( container, kTweenTypePathXtoY, 3,
                                        false, duration, 0, &tweenAtom );
if ( err ) goto bail;
err = QTNewTween( &tween, container, tweenAtom, duration );
if ( err ) goto bail;
for ( tweenTime = 0; tweenTime <= duration; tweenTime++ ) {
    Fixed absoluteYvalue;
 
    err = QTDoTween( tween, tweenTime, result, nil, nil, nil );
    if ( err ) goto bail;
 
    absoluteYvalue = *(Fixed *)*result;
}
 
err = QTDisposeTween( tween );
 
// kTweenTypePathXtoY - delta
err = CreateSamplePathTweenContainer( container, kTweenTypePathXtoY, 3,
                                        true, duration, 0, &tweenAtom );
if ( err ) goto bail;
err = QTNewTween( &tween, container, tweenAtom, duration );
if ( err ) goto bail;
for ( tweenTime = 0; tweenTime <= duration; tweenTime++ ) {
    Fixed deltaYalue;
 
    err = QTDoTween( tween, tweenTime, result, nil, nil, nil );
    if ( err ) goto bail;
 
    deltaYalue = *(Fixed *)*result;
}
 
err = QTDisposeTween( tween );
bail:
    if ( container ) QTDisposeAtomContainer( container );
    if ( result ) DisposeHandle( result );

Using a kTweenTypePathYtoX Tween Component

To use kTweenTypePathYtoX tween components, either absolute or delta, do the following:

  1. Create a QT atom container.

  2. Insert a kTweenEntry atom into the QT atom container for the tween.

  3. Insert a kTweenType atom that specifies the tween type into the kTweenEntry atom.

  4. Insert a kTweenData atom into the kTweenEntry atom.

  5. Perform the tweening operation, using QTDoTween.

Listing 7-19 shows how to create both kinds of kTweenTypePathYtoX tweens.

Listing 7-19  Creating kTweenTypePathYtoX tweens container

// kTweenTypePathYtoX - normal
err = CreateSamplePathTweenContainer( container, kTweenTypePathYtoX, 4,
                                        false, duration, 0, &tweenAtom );
if ( err ) goto bail;
err = QTNewTween( &tween, container, tweenAtom, duration );
if ( err ) goto bail;
for ( tweenTime = 0; tweenTime <= duration; tweenTime++ ) {
    Fixed absoluteXvalue;
 
    err = QTDoTween( tween, tweenTime, result, nil, nil, nil );
    if ( err ) goto bail;
 
    absoluteXvalue = *(Fixed *)*result;
}
 
err = QTDisposeTween( tween );
// kTweenTypePathYtoX - delta
err = CreateSamplePathTweenContainer( container, kTweenTypePathYtoX, 4,
                                        true, duration, 0, &tweenAtom );
if ( err ) goto bail;
err = QTNewTween( &tween, container, tweenAtom, duration );
if ( err ) goto bail;
for ( tweenTime = 0; tweenTime <= duration; tweenTime++ ) {
    Fixed deltaXvalue;
 
    err = QTDoTween( tween, tweenTime, result, nil, nil, nil );
    if ( err ) goto bail;
 
    deltaXvalue = *(Fixed *)*result;
}
 
err = QTDisposeTween( tween );
bail:
    if ( container ) QTDisposeAtomContainer( container );
    if ( result ) DisposeHandle( result );