Completed Lab/QTSprites.c

//////////
//
//  File:       QTSprites.c
//
//  Contains:   QuickTime sprites support for QuickTime movies.
//
//  Written by: ???
//  Revised by: Deeje Cooley and Tim Monroe
//              Based (heavily!) on the existing MakeSpriteMovie.c code written by ???.
//
//  Copyright:  © 1997-1998 by Apple Computer, Inc., all rights reserved.
//
//  Change History (most recent first):
//
//     <5>      03/17/00    rtm     made changes to get things running under CarbonLib
//     <4>      09/30/98    rtm     tweaked call to AddMovieResource to create single-fork movies
//     <3>      06/19/98    rtm     moved to new routine names (e.g. SpriteMediaSetSpriteProperty)
//     <2>      04/09/98    rtm     added sprite hit-testing
//     <1>      04/02/98    rtm     first file; integrated existing code with shell framework
//     
//  This sample code creates a sprite movie containing one sprite track. The sprite track contains
//  a static background picture sprite (or just a colored background, depending on the value of the
//  global variable gUseBackgroundPicture) and three other sprites that change their properties over time.
//  The track's media contains only one key frame sample followed by many override samples. The key
//  frame contains all of the images used by the sprites; the override frames only contain the overrides
//  of the locations, image indices, and layers needed for the other sprites.
//
//  This sample code also shows how to hit test sprites. It uses the function SpriteMediaHitTestAllSprites
//  to find mouse clicks on the sprites in the first sprite track in a movie. If the user clicks on a
//  sprite, we toggle the visibility state of the sprite. You would probably want to do something a bit
//  more interesting when the user clicks on a sprite.
//
//////////
 
 
//////////
//
// header files
//
//////////
 
#include "QTSprites.h"
 
 
//////////
//
// global variables
//
//////////
 
Boolean                         gUseBackgroundPicture = true;       // do we display a background picture?
 
 
//////////
//
// 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).fMovieHasSprites = (myTrack != NULL);
        (**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 (void)
{
    short                   myResRefNum = 0;
    short                   myResID = movieInDataForkResID;
    Movie                   myMovie = NULL;
    Track                   myTrack;
    Media                   myMedia;
    FSSpec                  myFile;
    Boolean                 myIsSelected = false;
    Boolean                 myIsReplacing = false;  
    StringPtr               myPrompt = QTUtils_ConvertCToPascalString(kSpriteSavePrompt);
    StringPtr               myFileName = QTUtils_ConvertCToPascalString(kSpriteSaveMovieFileName);
    QTAtomContainer         mySample = NULL;
    QTAtomContainer         mySpriteData = NULL;
    RGBColor                myKeyColor;
    Point                   myLocation, myIconLocation;
    short                   isVisible, myLayer, myIndex, i, myDelta, myIconMinH, myIconMaxH;
    long                    myFlags = createMovieFileDeleteCurFile | createMovieFileDontCreateResFile;
    OSErr                   myErr = noErr;
 
    //////////
    //
    // create a new movie file
    //
    //////////
 
    // prompt user for the destination file name
    QTFrame_PutFile(myPrompt, myFileName, &myFile, &myIsSelected, &myIsReplacing);
    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
    //
    //////////
    
    myTrack = NewMovieTrack(myMovie, ((long)kSpriteTrackWidth << 16), ((long)kSpriteTrackHeight << 16), kNoVolume);
    myMedia = NewTrackMedia(myTrack, SpriteMediaType, kSpriteMediaTimeScale, NULL, 0);
 
    //////////
    //
    // create a key frame sample containing the sprites and all of their shared images
    //
    //////////
 
 
    // create a new, empty key frame sample
    myErr = QTNewAtomContainer(&mySample);
    if (myErr != noErr)
        goto bail;
 
    // specify transparency color for recompression
    myKeyColor.red = myKeyColor.green = myKeyColor.blue = 0xffff;       // white
 
    // add images to the key frame sample - AddPICTImageToKeyFrameSample
    // will add the following atom data to the key frame atom container:
    //
    // kSpriteShareDataAtomType
    // kSpriteImagesContainerAtomType
    // kSpriteImageAtomType
    // kSpriteImageDataAtomType
    //
    // and optionally:
    //
    // kSpriteImageRegistrationAtomType
    // kSpriteImageNameAtomType
    
    AddPICTImageToKeyFrameSample(mySample, kIconPictID, &myKeyColor, kIconImageIndex, NULL, NULL);
    AddPICTImageToKeyFrameSample(mySample, kWorldPictID, &myKeyColor, kWorldImageIndex, NULL, NULL);
    AddPICTImageToKeyFrameSample(mySample, kBackgroundPictID, &myKeyColor, kBackgroundImageIndex, NULL, NULL);
    for (myIndex = 1; myIndex <= kNumSpaceShipImages; myIndex++)
        AddPICTImageToKeyFrameSample(mySample, kFirstSpaceShipPictID + myIndex - 1, &myKeyColor, myIndex + 3, NULL, NULL);
 
    //////////
    //
    // add samples to the sprite track's media
    //
    //////////
    
    // the code below will add to each kSpriteAtomType 
    // sprite atom the following sprite property atoms:
    //
    // kSpritePropertyImageIndex
    // kSpritePropertyLayer
    // kSpritePropertyGraphicsMode
    // kSpritePropertyMatrix
    // kSpritePropertyVisible
    // kSpriteNameAtomType
    // kSpriteURLLinkAtomtype
    
    BeginMediaEdits(myMedia);
 
    // create 
    myErr = QTNewAtomContainer(&mySpriteData);
    if (myErr != noErr)
        goto bail;
 
    // the background image
    if (gUseBackgroundPicture) {
        myLocation.h    = 0;
        myLocation.v    = 0;
        isVisible       = true;
        myLayer         = kBackgroundSpriteLayerNum;            // this makes the sprite a background sprite
        myIndex         = kBackgroundImageIndex;
        myErr = SetSpriteData(mySpriteData, &myLocation, &isVisible, &myLayer, &myIndex, NULL, NULL, NULL);
        if (myErr != noErr)
            goto bail;
        AddSpriteToSample(mySample, mySpriteData, kBackgroundSpriteAtomID);
    }
 
    // the space ship sprite
    myLocation.h    = 0;
    myLocation.v    = 60;
    isVisible       = true;
    myLayer         = -1;
    myIndex         = kFirstSpaceShipImageIndex;
    myErr = SetSpriteData(mySpriteData, &myLocation, &isVisible, &myLayer, &myIndex, NULL, NULL, NULL);
    if (myErr != noErr)
        goto bail;
    AddSpriteToSample(mySample, mySpriteData, kSpaceShipSpriteAtomID);
 
    // the world sprite
    myLocation.h    = (kSpriteTrackWidth / 2) - 24;
    myLocation.v    = (kSpriteTrackHeight / 2) - 24;
    isVisible       = true;
    myLayer         = 1;
    myIndex         = kWorldImageIndex;
    myErr = SetSpriteData(mySpriteData, &myLocation, &isVisible, &myLayer, &myIndex, NULL, NULL, NULL);
    if (myErr != noErr)
        goto bail;
    AddSpriteToSample(mySample, mySpriteData, kWorldSpriteAtomID);
 
    // the icon sprite
    myIconMinH          = (kSpriteTrackWidth / 2) - 116;
    myIconMaxH          = myIconMinH + 200;
    myDelta             = 2;
    myIconLocation.h    = myIconMinH;
    myIconLocation.v    = (kSpriteTrackHeight / 2) - (24 + 12);
    isVisible           = true;
    myLayer             = 0;
    myIndex             = kIconImageIndex;
    myErr = SetSpriteData(mySpriteData, &myIconLocation, &isVisible, &myLayer, &myIndex, NULL, NULL, NULL);
    if (myErr != noErr)
        goto bail;
    AddSpriteToSample(mySample, mySpriteData, kIconSpriteAtomID);
 
    
    // add the key frame sample to the sprite track media
    //
    // to add the sample data in a compressed form, you would use a QuickTime DataCodec to perform the
    // compression; replace the call to the utility AddSpriteSampleToMedia with a call to the utility
    // AddCompressedSpriteSampleToMedia to do this
    
    AddSpriteSampleToMedia(myMedia, mySample, kSpriteMediaFrameDuration, true, NULL);   
    //AddCompressedSpriteSampleToMedia(myMedia, mySample, kSpriteMediaFrameDuration, true, zlibDataCompressorSubType, NULL);
 
 
    //////////
    //
    // add a few override samples to move the space ship and icon, and to change the icon's layer
    //
    //////////
 
    // original space ship location
    myIndex         = kFirstSpaceShipImageIndex;
    myLocation.h    = 0;
    myLocation.v    = 80;
    isVisible       = true;
    
    for (i = 1; i <= kNumOverrideSamples; i++) {
        
        // remove existing atoms (which we used above
        // to create our key frame sample) from our key
        // frame sample atom container so we can re-use
        // the atom containers for our override samples
        QTRemoveChildren(mySample, kParentAtomIsContainer);
        QTRemoveChildren(mySpriteData, kParentAtomIsContainer);
 
        // every third frame, bump the space ship's image index (so that it spins as it moves)
        if ((i % 3) == 0) {
            myIndex++;
            if (myIndex > kLastSpaceShipImageIndex)
                myIndex = kFirstSpaceShipImageIndex;
        }
 
        // every frame, bump the space ship's location (so that it moves as it spins)
        myLocation.h += 2;
        myLocation.v += 1;
        
        if (isVisible)
            SetSpriteData(mySpriteData, &myLocation, NULL, NULL, &myIndex, NULL, NULL, NULL);
        else {
            isVisible = true;
            SetSpriteData(mySpriteData, &myLocation, &isVisible, NULL, &myIndex, NULL, NULL, NULL);
        }
                
        AddSpriteToSample(mySample, mySpriteData, 2);
        
        // make the icon move and change layer
        
        // first remove previous children from our container
        // so we can re-add the new sprite property atoms
        QTRemoveChildren(mySpriteData, kParentAtomIsContainer);
        
        // change the icon location
        myIconLocation.h += myDelta;
        
        if (myIconLocation.h >= myIconMaxH ) {
            myIconLocation.h = myIconMaxH;
            myDelta = -myDelta;
        }
        
        if (myIconLocation.h <= myIconMinH ) {
            myIconLocation.h = myIconMinH;
            myDelta = -myDelta;
        }
        
        // change the sprite layer
        if (myDelta > 0)
            myLayer = 0;
        else
            myLayer = 3;
        
        // set the data for the sprite
        SetSpriteData(mySpriteData, &myIconLocation, NULL, &myLayer, NULL, NULL, NULL, NULL);
        
        AddSpriteToSample(mySample, mySpriteData, 4);
        
        AddSpriteSampleToMedia(myMedia, mySample, kSpriteMediaFrameDuration, false, NULL);  
    }
 
    EndMediaEdits(myMedia);
    
    // add the media to the track
    InsertMediaIntoTrack(myTrack, 0, 0, GetMediaDuration(myMedia), fixed1);
    
 
 
    //////////
    //
    // set the sprite track properties
    //
    //////////
    
    if (!gUseBackgroundPicture) {
        QTAtomContainer     myTrackProperties;
        RGBColor            myBackgroundColor;
        
        // add a background color to the sprite track
        myBackgroundColor.red = EndianU16_NtoB(0x8000);
        myBackgroundColor.green = EndianU16_NtoB(0);
        myBackgroundColor.blue = EndianU16_NtoB(0xffff);
        
        QTNewAtomContainer(&myTrackProperties);
        QTInsertChild(myTrackProperties, 0, kSpriteTrackPropertyBackgroundColor, 1, 1, sizeof(RGBColor), &myBackgroundColor, NULL);
 
        myErr = SetMediaPropertyAtom(myMedia, myTrackProperties);
        if (myErr != noErr)
            goto bail;
 
        QTDisposeAtomContainer(myTrackProperties);
    }
    
    //////////
    //
    // finish up
    //
    //////////
    
    // add the movie resource to the movie file
    myErr = AddMovieResource(myMovie, myResRefNum, &myResID, myFile.name);
    
bail:
    free(myPrompt);
    free(myFileName);
 
    if (mySample != NULL)
        QTDisposeAtomContainer(mySample);
 
    if (mySpriteData != NULL)
        QTDisposeAtomContainer(mySpriteData);   
            
    if (myResRefNum != 0)
        CloseMovieFile(myResRefNum);
 
    if (myMovie != NULL)
        DisposeMovie(myMovie);
        
    return(myErr);
}
 
 
//////////
//
// 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;
        
    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);
}