QTSpritesPlus.c

//////////
//
//  File:       QTSpritesPlus.c
//
//  Contains:   Using video overrides and tweening with sprites in QuickTime movies.
//
//  Written by: Tim Monroe
//
//  Copyright:  © 2001 by Apple Computer, Inc., all rights reserved.
//
//  Change History (most recent first):
//
//     <2>      01/19/01    rtm     added multimatrix tween code
//     <1>      01/18/01    rtm     first file; based on existing QTSprites sample code
//     
//  This sample code shows how to use video overrides and tween tracks with sprite movies.
//
//////////
 
 
//////////
//
// header files
//
//////////
 
#include "QTSpritesPlus.h"
 
 
//////////
//
// QTSprites_InitWindowData
// Do any sprite-specific initialization for the specified window.
//
//////////
 
ApplicationDataHdl QTSprites_InitWindowData (WindowObject theWindowObject)
{
    ApplicationDataHdl      myAppData = NULL;
    Track                   myTrack = NULL;
    MediaHandler            myHandler = NULL;
 
    myAppData = (ApplicationDataHdl)NewHandleClear(sizeof(ApplicationDataRecord));
    if (myAppData != NULL) {
    
        myTrack = GetMovieIndTrackType((**theWindowObject).fMovie, 1, SpriteMediaType, movieTrackMediaType | movieTrackEnabledOnly);
        if (myTrack != NULL)
            myHandler = GetMediaHandler(GetTrackMedia(myTrack));
    
        // remember the sprite media handler
        (**myAppData).fSpriteHandler = myHandler;
    }
    
    return(myAppData);
}
 
 
//////////
//
// QTSprites_DumpWindowData
// Get rid of any window-specific data for the sprite media handler.
//
//////////
 
void QTSprites_DumpWindowData (WindowObject theWindowObject)
{
    ApplicationDataHdl      myAppData = NULL;
        
    myAppData = (ApplicationDataHdl)QTFrame_GetAppDataFromWindowObject(theWindowObject);
    if (myAppData != NULL)
        DisposeHandle((Handle)myAppData);
}
 
 
//////////
//
// QTSprites_CreateSpritesMovie
// Create a QuickTime movie containing a sprite track.
//
//////////
 
OSErr QTSprites_CreateSpritesMovie (UInt16 theMenuItem)
{
    Movie                   myMovie = NULL;
    Track                   myTrack = NULL;
    Media                   myMedia = NULL;
    FSSpec                  myFile;
    Boolean                 myIsSelected = false;
    Boolean                 myIsReplacing = false;  
    Fixed                   myHeight = 0;
    Fixed                   myWidth = 0;
    StringPtr               myPrompt = QTUtils_ConvertCToPascalString(kSpriteSavePrompt);
    StringPtr               myFileName = QTUtils_ConvertCToPascalString(kSpriteSaveMovieFileName);
    long                    myFlags = createMovieFileDeleteCurFile | createMovieFileDontCreateResFile;
    short                   myResRefNum = 0;
    short                   myResID = movieInDataForkResID;
    OSErr                   myErr = noErr;
 
    //////////
    //
    // create a new movie file
    //
    //////////
 
    // prompt the user for the destination file name
    QTFrame_PutFile(myPrompt, myFileName, &myFile, &myIsSelected, &myIsReplacing);
    myErr = myIsSelected ? noErr : userCanceledErr;
    if (!myIsSelected)
        goto bail;
 
    // create a movie file for the destination movie
    myErr = CreateMovieFile(&myFile, FOUR_CHAR_CODE('TVOD'), smSystemScript, myFlags, &myResRefNum, &myMovie);
    if (myErr != noErr)
        goto bail;
    
    //////////
    //
    // create the sprite track and media
    //
    //////////
    
    QTSprites_GetMovieSize(theMenuItem, &myHeight, &myWidth);
    
    myTrack = NewMovieTrack(myMovie, myWidth, myHeight, kNoVolume);
    myMedia = NewTrackMedia(myTrack, SpriteMediaType, kSpriteMediaTimeScale, NULL, 0);
 
    myErr = BeginMediaEdits(myMedia);
    if (myErr != noErr)
        goto bail;
 
    //////////
    //
    // add the appropriate samples to the sprite media
    //
    //////////
    
    switch (theMenuItem) {
        case IDM_MAKE_VIDEO_SPRITE_MOVIE:
            QTSprites_AddPowerBookMovieSamplesToMedia(myMedia);
            break;
        case IDM_MAKE_MOVE_ICON_MOVIE:
        case IDM_MAKE_SPIN_ICON_MOVIE:
        case IDM_MAKE_ROLL_ICON_MOVIE:
            QTSprites_AddIconMovieSamplesToMedia(myMedia);
            break;
        case IDM_MAKE_PENGUIN_MOVIE:
            QTSprites_AddPenguinMovieSamplesToMedia(myMedia);
            break;
        default:
            goto bail;
    }
    
    myErr = EndMediaEdits(myMedia);
    if (myErr != noErr)
        goto bail;
    
    // add the media to the track
    InsertMediaIntoTrack(myTrack, 0, 0, GetMediaDuration(myMedia), fixed1);
        
    //////////
    //
    // set the sprite track properties
    //
    //////////
    
    QTSprites_SetTrackProperties(myMedia, theMenuItem);
    
    //////////
    //
    // add video override or tween track to movie
    //
    //////////
    
    switch (theMenuItem) {
        case IDM_MAKE_VIDEO_SPRITE_MOVIE:
            QTSprites_AddVideoOverrideTrack(myMovie, myTrack);
            break;
        case IDM_MAKE_MOVE_ICON_MOVIE:
            QTSprites_AddTweenOverrideTrack(myMovie, myTrack, kTweenTypeMatrix);
            break;
        case IDM_MAKE_SPIN_ICON_MOVIE:
            QTSprites_AddTweenOverrideTrack(myMovie, myTrack, kTweenTypeSpin);
            break;
        case IDM_MAKE_ROLL_ICON_MOVIE:
            QTSprites_AddTweenOverrideTrack(myMovie, myTrack, kTweenTypeMultiMatrix);
            break;
        case IDM_MAKE_PENGUIN_MOVIE:
            QTSprites_AddTweenOverrideTrack(myMovie, myTrack, kTweenTypeGraphicsModeWithRGBColor);
            break;
        default:
            goto bail;
    }
    
    //////////
    //
    // add the movie resource to the movie file
    //
    //////////
    
    myErr = AddMovieResource(myMovie, myResRefNum, &myResID, myFile.name);
        
bail:
    if (myResRefNum != 0)
        CloseMovieFile(myResRefNum);
 
    if (myMovie != NULL)
        DisposeMovie(myMovie);
        
    free(myPrompt);
    free(myFileName);
 
    return(myErr);
}
 
 
//////////
//
// QTSprites_GetMovieSize
// Return the desired size of the specified sprite movie.
//
//////////
 
void QTSprites_GetMovieSize (UInt16 theMenuItem, Fixed *theHeight, Fixed *theWidth)
{
    if ((theHeight == NULL) || (theWidth == NULL))
        return;
        
    switch (theMenuItem) {
        case IDM_MAKE_VIDEO_SPRITE_MOVIE:
            *theWidth = Long2Fix(kPowerBookSpriteTrackWidth);
            *theHeight = Long2Fix(kPowerBookSpriteTrackHeight);
            break;
        case IDM_MAKE_MOVE_ICON_MOVIE:
        case IDM_MAKE_SPIN_ICON_MOVIE:
        case IDM_MAKE_ROLL_ICON_MOVIE:
            *theWidth = Long2Fix(kIconSpriteTrackWidth);
            *theHeight = Long2Fix(kIconSpriteTrackHeight);
            break;
        case IDM_MAKE_PENGUIN_MOVIE:
            *theWidth = Long2Fix(kPenguinSpriteTrackWidth);
            *theHeight = Long2Fix(kPenguinSpriteTrackHeight);
            break;
    }
}
 
 
//////////
//
// QTSprites_AddIconMovieSamplesToMedia
// Build the key frame for the icon sprite movie.
//
//////////
 
void QTSprites_AddIconMovieSamplesToMedia (Media theMedia)
{
    QTAtomContainer         mySample = NULL;
    QTAtomContainer         mySpriteData = NULL;
    RGBColor                myKeyColor;
    Point                   myLocation;
    short                   isVisible, myIndex;
    FixedPoint              myPoint;
    OSErr                   myErr = noErr;
    
    //////////
    //
    // create a key frame sample containing the sprite images
    //
    //////////
 
    // create a new, empty key frame sample
    myErr = QTNewAtomContainer(&mySample);
    if (myErr != noErr)
        goto bail;
 
    myKeyColor.red = myKeyColor.green = myKeyColor.blue = 0xffff;       // white
    
    myPoint.x = Long2Fix(kIconDimension / 2);
    myPoint.y = Long2Fix(kIconDimension / 2);
 
    // add images to the key frame sample
    SpriteUtils_AddPICTImageToKeyFrameSample(mySample, kOldQTIconID, &myKeyColor, 1, &myPoint, NULL);
 
    //////////
    //
    // add the initial sprite properties to the key frame sample
    //
    //////////
    
    myErr = QTNewAtomContainer(&mySpriteData);
    if (myErr != noErr)
        goto bail;
 
    // the QT icon sprite
    myLocation.h    = kIconDimension + (kIconDimension / 2);
    myLocation.v    = kIconDimension + (kIconDimension / 2);
    isVisible       = true;
    myIndex         = kOldQTIconImageIndex;
    
    SpriteUtils_SetSpriteData(mySpriteData, &myLocation, &isVisible, NULL, &myIndex, NULL, NULL, NULL);
    SpriteUtils_AddSpriteToSample(mySample, mySpriteData, kQTIconSpriteAtomID);
    SpriteUtils_AddSpriteSampleToMedia(theMedia, mySample, kSpriteMediaFrameDurationIcon, true, NULL);  
 
    //////////
    //
    // there are no override samples here, since we'll use tweening to move the icon
    //
    //////////
    
 
bail:   
    if (mySample != NULL)
        QTDisposeAtomContainer(mySample);
 
    if (mySpriteData != NULL)
        QTDisposeAtomContainer(mySpriteData);   
}
 
 
//////////
//
// QTSprites_AddPowerBookMovieSamplesToMedia
// Build the key frame for the PowerBook sprite movie.
//
//////////
 
void QTSprites_AddPowerBookMovieSamplesToMedia (Media theMedia)
{
    QTAtomContainer         mySample = NULL;
    QTAtomContainer         mySpriteData = NULL;
    RGBColor                myKeyColor;
    Point                   myLocation;
    short                   isVisible, myIndex, myLayer;
    OSErr                   myErr = noErr;
    
    //////////
    //
    // create a key frame sample containing the sprite images
    //
    //////////
 
    // create a new, empty key frame sample
    myErr = QTNewAtomContainer(&mySample);
    if (myErr != noErr)
        goto bail;
 
    myKeyColor.red = myKeyColor.green = myKeyColor.blue = 0xffff;       // white
    
    // add images to the key frame sample
    SpriteUtils_AddPICTImageToKeyFrameSample(mySample, kOldQTIconID, &myKeyColor, 1, NULL, NULL);
    SpriteUtils_AddPICTImageToKeyFrameSample(mySample, kTitaniumPowerBookID, &myKeyColor, 2, NULL, NULL);
 
    //////////
    //
    // add the initial sprite properties to the key frame sample
    //
    //////////
    
    myErr = QTNewAtomContainer(&mySpriteData);
    if (myErr != noErr)
        goto bail;
 
    // the QT icon sprite
    myLocation.h    = 46;
    myLocation.v    = 8;
    isVisible       = true;
    myIndex         = kOldQTIconImageIndex;
    myLayer         = 1;
    
    SpriteUtils_SetSpriteData(mySpriteData, &myLocation, &isVisible, &myLayer, &myIndex, NULL, NULL, NULL);
    SpriteUtils_AddSpriteToSample(mySample, mySpriteData, kQTIconSpriteAtomID);
 
    // the PowerBook sprite
    myLocation.h    = 0;
    myLocation.v    = 0;
    isVisible       = true;
    myIndex         = kPowerBookImageIndex;
    myLayer         = 2;
 
    SpriteUtils_SetSpriteData(mySpriteData, &myLocation, &isVisible, &myLayer, &myIndex, NULL, NULL, NULL);
    SpriteUtils_AddSpriteToSample(mySample, mySpriteData, kTitaniumPowerBookID);
    SpriteUtils_AddSpriteSampleToMedia(theMedia, mySample, kSpriteMediaFrameDurationPowerBook, true, NULL); 
 
    //////////
    //
    // there are no override samples here, since we'll use a video override track to animate the icon sprite
    //
    //////////
 
bail:   
    if (mySample != NULL)
        QTDisposeAtomContainer(mySample);
 
    if (mySpriteData != NULL)
        QTDisposeAtomContainer(mySpriteData);   
}
 
 
//////////
//
// QTSprites_AddPenguinMovieSamplesToMedia
// Build the key frame for the penguin sprite movie.
//
//////////
 
void QTSprites_AddPenguinMovieSamplesToMedia (Media theMedia)
{
    QTAtomContainer         mySample = NULL;
    QTAtomContainer         mySpriteData = NULL;
    RGBColor                myKeyColor;
    Point                   myLocation;
    short                   isVisible, myIndex;
    OSErr                   myErr = noErr;
    
    //////////
    //
    // create a key frame sample containing the sprite images
    //
    //////////
 
    // create a new, empty key frame sample
    myErr = QTNewAtomContainer(&mySample);
    if (myErr != noErr)
        goto bail;
 
    myKeyColor.red = myKeyColor.green = myKeyColor.blue = 0xffff;       // white
 
    // add images to the key frame sample
    SpriteUtils_AddPICTImageToKeyFrameSample(mySample, kPenguinPictID, &myKeyColor, kPenguinImageIndex, NULL, NULL);
 
    //////////
    //
    // add the initial sprite properties to the key frame sample
    //
    //////////
    
    myErr = QTNewAtomContainer(&mySpriteData);
    if (myErr != noErr)
        goto bail;
 
    // the penguin sprite
    myLocation.h    = 0;
    myLocation.v    = 0;
    isVisible       = true;
    myIndex         = 1;
    
    SpriteUtils_SetSpriteData(mySpriteData, &myLocation, &isVisible, NULL, &myIndex, NULL, NULL, NULL);
    SpriteUtils_AddSpriteToSample(mySample, mySpriteData, kPenguinSpriteAtomID);
    SpriteUtils_AddSpriteSampleToMedia(theMedia, mySample, kSpriteMediaFrameDurationPenguin, true, NULL);   
 
    //////////
    //
    // there are no override samples here, since we'll use tweening to animate the movie
    //
    //////////
 
bail:   
    if (mySample != NULL)
        QTDisposeAtomContainer(mySample);
 
    if (mySpriteData != NULL)
        QTDisposeAtomContainer(mySpriteData);   
}
 
 
//////////
//
// QTSprites_SetTrackProperties
// Set the track properties for the specified sample sprite movie.
//
//////////
 
void QTSprites_SetTrackProperties (Media theMedia, UInt16 theMenuItem)
{
#pragma unused(theMenuItem)
    QTAtomContainer     myTrackProperties;
    RGBColor            myBackgroundColor;
    OSErr               myErr = noErr;
        
    // add a background color to the sprite track
    myBackgroundColor.red = EndianU16_NtoB(0xffff);
    myBackgroundColor.green = EndianU16_NtoB(0xffff);
    myBackgroundColor.blue = EndianU16_NtoB(0xffff);
    
    myErr = QTNewAtomContainer(&myTrackProperties);
    if (myErr == noErr) {
        QTInsertChild(myTrackProperties, 0, kSpriteTrackPropertyBackgroundColor, 1, 1, sizeof(myBackgroundColor), &myBackgroundColor, NULL);
 
        SetMediaPropertyAtom(theMedia, myTrackProperties);
        
        QTDisposeAtomContainer(myTrackProperties);
    }
}
    
 
//////////
//
// QTSprites_HitTestSprites
// Determine whether a mouse click is on a sprite; return true if it is, false otherwise.
//
// This routine is intended to be called from your movie controller action filter function,
// in response to mcActionMouseDown actions.
//
//////////
 
Boolean QTSprites_HitTestSprites (WindowObject theWindowObject, EventRecord *theEvent)
{
    ApplicationDataHdl      myAppData = NULL;
    MediaHandler            myHandler = NULL;
    Boolean                 isHandled = false;
    long                    myFlags = 0L;
    QTAtomID                myAtomID = 0;
    Point                   myPoint;
    ComponentResult         myErr = noErr;
 
    myAppData = (ApplicationDataHdl)QTFrame_GetAppDataFromWindowObject(theWindowObject);
    if (myAppData == NULL)
        goto bail;
        
    if (theEvent == NULL)
        goto bail;
        
    // make sure that the click is in the frontmost window
    if ((**theWindowObject).fWindow != QTFrame_GetFrontMovieWindow())
        goto bail;
        
    myHandler = (**myAppData).fSpriteHandler;
    myFlags = spriteHitTestImage | spriteHitTestLocInDisplayCoordinates | spriteHitTestInvisibleSprites;
    myPoint = theEvent->where;
    
    myErr = SpriteMediaHitTestAllSprites(myHandler, myFlags, myPoint, &myAtomID);
    if ((myErr == noErr) && (myAtomID != 0)) {
        Boolean             isVisible;
        
        // the user has clicked on a sprite;
        // for now, we'll just toggle the visibility state of the sprite
        SpriteMediaGetSpriteProperty(myHandler, myAtomID, kSpritePropertyVisible, (void *)&isVisible);
        SpriteMediaSetSpriteProperty(myHandler, myAtomID, kSpritePropertyVisible, (void *)!isVisible);
 
        isHandled = true;
    }
 
bail:
    return(isHandled);
}
 
 
//////////
//
// QTSprites_AddVideoOverrideTrack
// Prompt the user for a movie and use the first video track in that movie as an override track
// for the first sprite image in the specified sprite movie.
//
//////////
 
OSErr QTSprites_AddVideoOverrideTrack (Movie theSpriteMovie, Track theSpriteTrack)
{
    Movie                   myVideoMovie = NULL;
    Track                   myVideoTrack = NULL;
    short                   myRefNum = kInvalidFileRefNum;
    short                   myResID = 0;
    FSSpec                  myFSSpec;
    OSType                  myTypeList[] = {kQTFileTypeMovie};
    short                   myNumTypes = 2;
    QTFrameFileFilterUPP    myFileFilterUPP = NULL;
    TimeValue               myDuration;
    long                    myRefIndex;
    QTAtomContainer         myInputMap = NULL;
    OSErr                   myErr = noErr;
 
#if TARGET_OS_MAC
    myNumTypes = 0;
#endif
 
    //////////
    //
    // have the user select a file; make sure it has a video track in it
    //
    //////////
    
retry:
    myFileFilterUPP = QTFrame_GetFileFilterUPP((ProcPtr)QTFrame_FilterFiles);
 
    myErr = QTFrame_GetOneFileWithPreview(myNumTypes, (QTFrameTypeListPtr)myTypeList, &myFSSpec, myFileFilterUPP);
 
    if (myFileFilterUPP != NULL)
        DisposeNavObjectFilterUPP(myFileFilterUPP);
 
    if (myErr != noErr)
        goto bail;
 
    myErr = OpenMovieFile(&myFSSpec, &myRefNum, fsRdPerm);
    if (myErr != noErr)
        goto bail;
 
    // now fetch the first movie from the file
    myResID = 0;
    myErr = NewMovieFromFile(&myVideoMovie, myRefNum, &myResID, NULL, newMovieActive, NULL);
    if (myErr != noErr)
        goto bail;
        
    myVideoTrack = GetMovieIndTrackType(myVideoMovie, 1, VideoMediaType, movieTrackMediaType);
    if (myVideoTrack == NULL)
        goto retry;
        
    //////////
    //
    // copy the video track into the sprite movie
    //
    //////////
    
    myDuration = GetMovieDuration(theSpriteMovie);
    
    myErr = QTSprites_ImportVideoTrack(myVideoMovie, theSpriteMovie, &myVideoTrack);
    if (myErr != noErr)
        goto bail;
 
    // truncate the new video track to the length of the sprite movie
    DeleteMovieSegment(theSpriteMovie, myDuration, GetMovieDuration(theSpriteMovie) - myDuration);
 
    //////////
    //
    // attach the video track as a modifier to the sprite track
    //
    //////////
    
    // create a media input map
    myErr = QTNewAtomContainer(&myInputMap);
    if (myErr != noErr)
        goto bail;
        
    myErr = AddTrackReference(theSpriteTrack, myVideoTrack, kTrackModifierReference, &myRefIndex);
    if (myErr != noErr)
        goto bail;
 
    myErr = QTSprites_AddVideoEntryToInputMap(myInputMap, myRefIndex, kOldQTIconImageIndex, kTrackModifierTypeImage, NULL);
    if (myErr != noErr)
        goto bail;
    
    // attach the input map to the sprite track
    myErr = SetMediaInputMap(GetTrackMedia(theSpriteTrack), myInputMap);
 
bail:
    if (myVideoMovie != NULL)
        DisposeMovie(myVideoMovie);
        
    if (myRefNum != kInvalidFileRefNum)
        CloseMovieFile(myRefNum);
        
    if (myInputMap != NULL)
        QTDisposeAtomContainer(myInputMap);
 
    return(myErr);
}
 
 
//////////
//
// QTSprites_AddVideoEntryToInputMap
// Add a video entry to the specified input map.
//
//////////
 
OSErr QTSprites_AddVideoEntryToInputMap (QTAtomContainer theInputMap, long theRefIndex, long theID, OSType theType, char *theName)
{
#pragma unused(theName)
    QTAtom              myInputAtom;
    OSErr               myErr = noErr;
    
    // add an entry to the input map
    myErr = QTInsertChild(theInputMap, kParentAtomIsContainer, kTrackModifierInput, theRefIndex, 0, 0, NULL, &myInputAtom);
    if (myErr != noErr)
        goto bail;
    
    // add two child atoms to the parent atom;
    // these atoms define the type of the modifier input and the image index to override
    theType = EndianU32_NtoB(theType);
    myErr = QTInsertChild(theInputMap, myInputAtom, kTrackModifierType, 1, 0, sizeof(OSType), &theType, NULL);
    if (myErr != noErr)
        goto bail;
    
    theID = EndianS32_NtoB(theID);
    myErr = QTInsertChild(theInputMap, myInputAtom, kSpritePropertyImageIndex, 1, 0, sizeof(long), &theID, NULL);
            
bail:
    return(myErr);
}
 
 
//////////
//
// QTSprites_AddTweenOverrideTrack
// Add a tween track to the specified movie, tweening the specified track.
//
//////////
 
void QTSprites_AddTweenOverrideTrack (Movie theMovie, Track theTargetTrack, UInt32 theTweenType)
{
    Handle                      myHandle = NULL;
    SampleDescriptionHandle     mySampleDesc = NULL;
    short                       myResRefNum = 0;
    short                       myResID = movieInDataForkResID;
    Track                       myTweenTrack;
    Media                       myTweenMedia;
    QTAtomContainer             mySample = NULL;
    QTAtomContainer             myInputMap = NULL;
    QTAtom                      myTweenEntryAtom = 0;
    QTAtomType                  myType;
    long                        myRefIndex;
    OSErr                       myErr = noErr;
 
    //////////
    //
    // create the tween track and media, and a sample to contain the tween data
    //
    //////////
 
    // create the tween track and media
    myTweenTrack = NewMovieTrack(theMovie, 0, 0, kNoVolume);
    myTweenMedia = NewTrackMedia(myTweenTrack, TweenMediaType, GetMovieTimeScale(theMovie), NULL, 0);
 
    // create a new sample; this sample will hold the tween data
    myErr = QTNewAtomContainer(&mySample);
    if (myErr != noErr)
        goto bail;
        
    //////////
    //
    // add a tween entry atom to the atom container
    //
    //////////
 
    myErr = QTInsertChild(mySample, kParentAtomIsContainer, kTweenEntry, 1, 0, 0, NULL, &myTweenEntryAtom);
    if (myErr != noErr)
        goto bail;
    
    // set the type of this tween entry
    // a kTweenEntry atom can contain only one kTweenType atom, whose ID and index must be 1
    myType = EndianU32_NtoB(theTweenType);
    myErr = QTInsertChild(mySample, myTweenEntryAtom, kTweenType, 1, 0, sizeof(myType), &myType, NULL);
    if (myErr != noErr)
        goto bail;
 
    //////////
    //
    // add a tween data atom to the tween entry atom
    //
    //////////
 
    if (theTweenType == kTweenTypeMatrix) {
        MatrixRecord            myMatrix[2];
    
        // set the initial data for this tween entry
        SetIdentityMatrix(&myMatrix[0]);
        TranslateMatrix(&myMatrix[0], Long2Fix(kIconDimension + (kIconDimension / 2)), Long2Fix(kIconDimension + (kIconDimension / 2)));
        EndianUtils_MatrixRecord_NtoB(&myMatrix[0]);
        
        // set the final data for this tween entry
        SetIdentityMatrix(&myMatrix[1]);
        TranslateMatrix(&myMatrix[1], Long2Fix(230 + (kIconDimension / 2)), Long2Fix(kIconDimension + (kIconDimension / 2)));
        EndianUtils_MatrixRecord_NtoB(&myMatrix[1]);
        
        myErr = QTInsertChild(mySample, myTweenEntryAtom, kTweenData, 1, 0, 2 * sizeof(MatrixRecord), myMatrix, NULL);
        if (myErr != noErr)
            goto bail;
        
    } else if (theTweenType == kTweenTypeGraphicsModeWithRGBColor) {
        ModifierTrackGraphicsModeRecord     myGraphicsMode[2];
        
        // set the initial blend amount (0 = fully transparent)
        myGraphicsMode[0].graphicsMode = EndianU32_NtoB(blend);
        myGraphicsMode[0].opColor.red = 0;
        myGraphicsMode[0].opColor.green = 0;
        myGraphicsMode[0].opColor.blue = 0;
 
        // set the final blend amount (0xffff = fully opaque)
        myGraphicsMode[1].graphicsMode = EndianU32_NtoB(blend);
        myGraphicsMode[1].opColor.red = EndianU16_NtoB(0xffff);
        myGraphicsMode[1].opColor.green = EndianU16_NtoB(0xffff);
        myGraphicsMode[1].opColor.blue = EndianU16_NtoB(0xffff);
        
        myErr = QTInsertChild(mySample, myTweenEntryAtom, kTweenData, 1, 0, 2 * sizeof(ModifierTrackGraphicsModeRecord), myGraphicsMode, NULL);
        if (myErr != noErr)
            goto bail;
            
    } else if (theTweenType == kTweenTypeSpin) {
        Fixed   mySpinData[2];
        
        // set the initial rotation value
        mySpinData[0] = EndianU32_NtoB(0);
 
        // set the number of rotations
        mySpinData[1] = EndianU32_NtoB(Long2Fix(kNumRotations));
        
        myErr = QTInsertChild(mySample, myTweenEntryAtom, kTweenData, 1, 0, 2 * sizeof(Fixed), mySpinData, NULL);
        if (myErr != noErr)
            goto bail;
 
    } else if (theTweenType == kTweenTypeMultiMatrix) {
        
        QTAtom                      myMultiTweenDataAtom = 0;
        QTAtom                      myAtom = 0;
        MatrixRecord                myMatrix[2];
        Fixed                       mySpinData[2];
        
        //////////
        //
        // add a multimatrix tween data atom to the tween entry atom
        //
        //////////
 
        myErr = QTInsertChild(mySample, myTweenEntryAtom, kTweenData, 1, 0, 0, NULL, &myMultiTweenDataAtom);
        if (myErr != noErr)
            goto bail;
            
        //////////
        //
        // add a spin tween to the multimatrix tween data atom
        //
        //////////
 
        myErr = QTInsertChild(mySample, myMultiTweenDataAtom, kTweenEntry, 1, 0, 0, NULL, &myAtom);
        if (myErr != noErr)
            goto bail;
        
        // set the type of this tween entry
        myType = EndianU32_NtoB(kTweenTypeSpin);
        myErr = QTInsertChild(mySample, myAtom, kTweenType, 1, 0, sizeof(myType), &myType, NULL);
        if (myErr != noErr)
            goto bail;
 
        // set the initial rotation value
        mySpinData[0] = EndianU32_NtoB(0);
 
        // set the number of rotations
        mySpinData[1] = EndianU32_NtoB(Long2Fix(kNumRotations));
        
        myErr = QTInsertChild(mySample, myAtom, kTweenData, 1, 0, 2 * sizeof(Fixed), mySpinData, NULL);
        if (myErr != noErr)
            goto bail;
        
        //////////
        //
        // add a translation matrix tween to the multimatrix tween data atom
        //
        //////////
 
        myErr = QTInsertChild(mySample, myMultiTweenDataAtom, kTweenEntry, 2, 0, 0, NULL, &myAtom);
        if (myErr != noErr)
            goto bail;
        
        // set the type of this tween entry
        myType = EndianU32_NtoB(kTweenTypeMatrix);
        myErr = QTInsertChild(mySample, myAtom, kTweenType, 1, 0, sizeof(myType), &myType, NULL);
        if (myErr != noErr)
            goto bail;
 
        // set the initial data for this tween entry
        SetIdentityMatrix(&myMatrix[0]);
        TranslateMatrix(&myMatrix[0], Long2Fix(kIconDimension + (kIconDimension / 2)), Long2Fix(kIconDimension + (kIconDimension / 2)));
        EndianUtils_MatrixRecord_NtoB(&myMatrix[0]);
        
        // set the final data for this tween entry
        SetIdentityMatrix(&myMatrix[1]);
        TranslateMatrix(&myMatrix[1], Long2Fix(230 + (kIconDimension / 2)), Long2Fix(kIconDimension + (kIconDimension / 2)));
        EndianUtils_MatrixRecord_NtoB(&myMatrix[1]);
        
        myErr = QTInsertChild(mySample, myAtom, kTweenData, 1, 0, 2 * sizeof(MatrixRecord), myMatrix, NULL);
        if (myErr != noErr)
            goto bail;
    }
    
    // create the sample description
    mySampleDesc = (SampleDescriptionHandle)NewHandleClear(sizeof(SampleDescription));
    if (mySampleDesc == NULL)
        goto bail;
    
    (**mySampleDesc).descSize = sizeof(SampleDescription);
        
    // add the tween sample to the media
    myErr = BeginMediaEdits(myTweenMedia);
    if (myErr != noErr)
        goto bail;
        
    myErr = AddMediaSample(myTweenMedia, mySample, 0, GetHandleSize(mySample), GetMediaDuration(GetTrackMedia(theTargetTrack)), (SampleDescriptionHandle)mySampleDesc, 1, 0, NULL);
    if (myErr != noErr)
        goto bail;
        
    myErr = EndMediaEdits(myTweenMedia);
    if (myErr != noErr)
        goto bail;
    
    // add the media to the track
    myErr = InsertMediaIntoTrack(myTweenTrack, 0, 0, GetMediaDuration(myTweenMedia), fixed1);
    if (myErr != noErr)
        goto bail;
    
    //////////
    //
    // create a link between the sprite track and the tween track, and update the sprite track's input map
    //
    //////////
    
    // create a media input map
    myErr = QTNewAtomContainer(&myInputMap);
    if (myErr != noErr)
        goto bail;
        
    // for *each* tween entry in the tween media sample,
    // create a link between the sprite track and the tween track, and add an entry to the input map
    myErr = AddTrackReference(theTargetTrack, myTweenTrack, kTrackModifierReference, &myRefIndex);
    if (myErr != noErr)
        goto bail;
        
    if (theTweenType == kTweenTypeMatrix)
        myErr = QTSprites_AddTweenEntryToInputMap(myInputMap, myRefIndex, kQTIconSpriteAtomID, kTrackModifierObjectMatrix, NULL);
        
    if (theTweenType == kTweenTypeGraphicsModeWithRGBColor)
        myErr = QTSprites_AddTweenEntryToInputMap(myInputMap, myRefIndex, kPenguinSpriteAtomID, kTrackModifierObjectGraphicsMode, NULL);
 
    if (theTweenType == kTweenTypeSpin)
        myErr = QTSprites_AddTweenEntryToInputMap(myInputMap, myRefIndex, 1, kTrackModifierObjectMatrix, NULL);
                
    if (theTweenType == kTweenTypeMultiMatrix)
        myErr = QTSprites_AddTweenEntryToInputMap(myInputMap, myRefIndex, 1, kTrackModifierObjectMatrix, NULL);
                
    if (myErr != noErr)
        goto bail;
    
    // attach the input map to the sprite track
    myErr = SetMediaInputMap(GetTrackMedia(theTargetTrack), myInputMap);
    
bail:
    if (mySample != NULL)
        QTDisposeAtomContainer(mySample);
 
    if (myInputMap != NULL)
        QTDisposeAtomContainer(myInputMap);
 
    if (mySampleDesc != NULL)
        DisposeHandle((Handle)mySampleDesc);
    
    if (myHandle != NULL)
        DisposeHandle(myHandle);
 
    return;
}
 
 
//////////
//
// QTSprites_AddTweenEntryToInputMap
// Add a tween entry to the specified input map.
//
//////////
 
OSErr QTSprites_AddTweenEntryToInputMap (QTAtomContainer theInputMap, long theRefIndex, long theID, OSType theType, char *theName)
{
#pragma unused(theName)
    QTAtom              myInputAtom;
    OSErr               myErr = noErr;
    
    // add an entry to the input map
    myErr = QTInsertChild(theInputMap, kParentAtomIsContainer, kTrackModifierInput, theRefIndex, 0, 0, NULL, &myInputAtom);
    if (myErr != noErr)
        goto bail;
    
    // add two child atoms to the parent atom;
    // these atoms define the type of the modifier input and the ID of the sprite to receive the tween data
    theType = EndianU32_NtoB(theType);
    myErr = QTInsertChild(theInputMap, myInputAtom, kTrackModifierType, 1, 0, sizeof(OSType), &theType, NULL);
    if (myErr != noErr)
        goto bail;
            
    theID = EndianU32_NtoB(theID);
    myErr = QTInsertChild(theInputMap, myInputAtom, kTrackModifierObjectID, 1, 0, sizeof(long), &theID, NULL);
        
bail:
    return(myErr);
}
 
 
//////////
//
// QTSprites_SetTweenEntryDuration
// Set the duration of the specified tween entry.
//
//////////
 
OSErr QTSprites_SetTweenEntryDuration (QTAtomContainer theSample, QTAtomID theID, TimeValue theDuration)
{
    QTAtom              myAtom;
    OSErr               myErr = noErr;
    
    // find the tween entry with the specified ID
    myAtom = QTFindChildByID(theSample, kParentAtomIsContainer, kTweenEntry, theID, NULL);
    if (myAtom == 0)
        return(paramErr);
    
    // add in the duration
    theDuration = EndianS32_NtoB(theDuration);
    // a kTweenEntry atom can contain only one kTweenDuration atom, whose ID and index must be 1
    myErr = QTInsertChild(theSample, myAtom, kTweenDuration, 1, 1, sizeof(TimeValue), &theDuration, NULL);
        
    return(myErr);
}
 
 
//////////
//
// QTSprites_SetTweenEntryStartOffset
// Set the starting offset of the specified tween entry.
//
//////////
 
OSErr QTSprites_SetTweenEntryStartOffset (QTAtomContainer theSample, QTAtomID theID, TimeValue theOffset)
{
    QTAtom              myAtom;
    OSErr               myErr = noErr;
    
    // find the tween entry with the specified ID
    myAtom = QTFindChildByID(theSample, kParentAtomIsContainer, kTweenEntry, theID, NULL);
    if (myAtom == 0)
        return(paramErr);
    
    // add in the starting offset
    // a kTweenEntry atom can contain only one kTweenStartOffset atom, whose ID and index must be 1
    theOffset = EndianS32_NtoB(theOffset);
    myErr = QTInsertChild(theSample, myAtom, kTweenStartOffset, 1, 1, sizeof(TimeValue), &theOffset, NULL);
        
    return(myErr);
}
 
 
//////////
//
// QTSprites_ImportVideoTrack
// Copy a video track from one movie (the source) to another (the destination); return the new track to the caller.
//
// We assume that we want the first video track in theSrcMovie.
//
//////////
 
OSErr QTSprites_ImportVideoTrack (Movie theSrcMovie, Movie theDstMovie, Track *theTrack)
{
    Track                       mySrcTrack = NULL;
    Media                       mySrcMedia = NULL;
    Track                       myDstTrack = NULL;
    Media                       myDstMedia = NULL;
    Fixed                       myWidth, myHeight;
    OSErr                       myErr = paramErr;
 
    // get the first video track in the source movie
    mySrcTrack = GetMovieIndTrackType(theSrcMovie, 1, VideoMediaType, movieTrackMediaType);
    if (mySrcTrack == NULL)
        goto bail;
        
    // get the track's media and dimensions
    mySrcMedia = GetTrackMedia(mySrcTrack);
    GetTrackDimensions(mySrcTrack, &myWidth, &myHeight);
    
    // create a destination track
    myDstTrack = NewMovieTrack(theDstMovie, myWidth, myHeight, kNoVolume);
    if (myDstTrack == NULL)
        goto bail;
 
    // create a destination media
    myDstMedia = NewTrackMedia(myDstTrack, VideoMediaType, GetMediaTimeScale(mySrcMedia), 0, 0);
    if (myDstMedia == NULL)
        goto bail;
 
    myErr = BeginMediaEdits(myDstMedia);
    if (myErr != noErr)
        goto bail;
        
    myErr = CopyTrackSettings(mySrcTrack, myDstTrack);
    if (myErr != noErr)
        goto bail;
        
    myErr = InsertTrackSegment(mySrcTrack, myDstTrack, 0, GetTrackDuration(mySrcTrack), 0);
    if (myErr != noErr)
        goto bail;
 
    myErr = EndMediaEdits(myDstMedia);
    if (myErr != noErr)
        goto bail;
    
bail:
    if (theTrack != NULL)
        *theTrack = myDstTrack;
        
    return(myErr);
}