VRMakePano.c

/*
    File:       VRMakePano.c
 
    Contains:   Code for creating a QuickTime VR panoramic movie from a panoramic image.
 
    Written by: Ken Turkowski
                Major overhaul in the API by Ken Turkowski.
                Directly derived from VRMakePano.c by Tim Monroe.
                Based largely on MakeQTVRPanorama code by Ed Harp (and others?).
                Cubic projection code based on SampleMakeCubic.cp code by Ken Doyle.
 
    Warranty Information:
                Even though Apple has reviewed this software, Apple makes no warranty
                or representation, either express or implied, with respect to this
                software, its quality, accuracy, merchantability, or fitness for a
                particular purpose.  As a result, this software is provided "as is,"
                and you, its user, are assuming the entire risk as to its quality
                and accuracy.
 
    Copyright:  © 1996-2001 by Apple Computer, Inc., all rights reserved.
                This code may be used and freely distributed as long as it includes
                this copyright notice and the above warranty information.
 
    Change History (most recent first):
 
        $Log: VRMakePano.c,v $
        Revision 1.15  2001/10/18 18:21:47  turk
        We needed to do endian flipping in the fast start reference atom.
        
        Revision 1.14  2001/06/25 20:52:45  turk
        roundtol() for non-mac
        
        Revision 1.13  2001/05/15 02:41:25  turk
        update tile windows
        
        Revision 1.12  2001/05/10 09:12:40  turk
        fix tile display for Carbon
        
        Revision 1.11  2001/04/23 19:00:11  turk
        Enable setting of preview compression parameters.
        
        Revision 1.10  2001/04/02 22:55:14  turk
        Added warranty and copyright information.
        
        Revision 1.9  2001/03/08 00:54:06  turk
        Close the QTVR Flattener component after using it.
        
        Revision 1.8  2001/03/05 01:57:58  turk
        Make sure the time interval of all tracks is identical.
        
        Revision 1.7  2001/02/08 03:32:14  turk
        Make it more robust in looking FSSpecHandles; this fixes the case where the handle is not
        NULL, but has zero length.
        
        Revision 1.6  2001/02/03 01:39:58  turk
        Write the hot spot atoms; feed extra information to the flattener.
        
        Revision 1.5  2000/12/22 00:23:22  turk
        Call an FSSpecHandle an FSSpecHandle, not a FSSpec**, for they differ in their access:
        sh[0][n] vs. sh[n][0].
        
        Revision 1.4  2000/11/23 02:46:10  turk
        Reverse coordinate system for tile Y coordinate. Merge Tim Monroe's changes for Windows
        and Carbon.
        
        Revision 1.3  2000/11/20 21:35:08  turk
        document changes in 1.2
        
        Revision 1.2  2000/11/20 21:33:35  turk
        Get QTVR flattener working by setting flags.
        
        Revision 1.1  2000/11/15 23:11:39  turk
        Major rework of the API. Brought all parameters directly up to the API, rather than being
        assigned default values. Factorized procedures to avoid duplication of code. Provide
        parameter checking, and defaults are assigned in many cases if the parameter is zero.
        Added API's for creating both cylindrical and cubic panoramas from GWorlds. Tile picts
        into a single pre-flattened movie, rather than first into tile movies. Use QTVR flattener
        if it exists (although I haven't been able to access it yet).
        
 
       <15>     06/14/00    rtm     modified ImportVideoTrack as per Ken Doyle's changes in MakeCubicPPC
       <14>     03/23/00    rtm     added code to handle new cubic file format
       <13>     03/21/00    rtm     made changes to get things running under CarbonLib
       <12>     11/24/99    rtm     reworked file opening and saving to avoid using Standard File Package
                                    on Macintosh (for Carbonization effort)
       <11>     02/01/99    rtm     reworked prompt and filename handling to remove "\p" sequences
       <10>     10/19/98    rtm     added check in CreatePanoTrack to make sure we were passed a
                                    valid hot spot file spec (otherwise no panorama is created!); added
                                    the default movie progress procedure
       <9>      09/30/98    rtm     tweaked call to AddMovieResource to create single-fork movies;
                                    tweaked call to FlattenMovieData to enable FastStart option
       <8>      08/06/98    rtm     added VRPano_MakeHotSpotVers2x0 (based loosely on code from John Mott)
                                    to support version 2.0 hot spot atoms; sketched out preliminary
                                    support for 1.0 hot spots, but that's left as an exercise for the reader
       <7>      08/05/98    rtm     added support for hot spots image tracks (both version 1.0 and 2.0)
       <6>      08/26/98    rtm     added support for pan/tilt/field-of-view constraint atoms
       <5>      08/18/98    rtm     added support for version 1.0 panoramic movies
       <4>      08/16/98    rtm     final clean-up before initial release
       <3>      08/15/98    rtm     reworked the image file handling using QuickTime graphics importers;
                                    added tile-display window for debugging purposes;
                                    now we run fine under both MacOS and Windows. Ta da!
       <2>      01/14/98    rtm     further work; added Endian* macros; got working on MacOS
       <1>      01/12/98    rtm     first file from CMovieMaker.cp in MakeQTVRPanorama 2.0b
*/
 
 
 
/********************************************************************************
 *  This file contains functions that convert a panoramic image into a QuickTime VR movie. The image
 *  can be a picture (of type 'PICT') or any other kind of image for which QuickTime has a graphics
 *  importer component. Here, we can create both version 1.0 and version 2.0 QTVR panoramic movies.
 *  We can also create hot spot image tracks and (for version 2.0 only) assemble the various hot spot
 *  atoms.
 *
 *  This file also contains a function that constructs a QuickTime VR movie that uses the new cubic
 *  projection. In the following notes, we'll call such movies "cubic movies" or "cubic panoramas".
 *
 ********************************************************************************
 *
 *  VERSION 2.0 FILE FORMAT
 *
 *  The definitive source of information about creating QTVR 2.0 panoramic movies is Chapter 3 of the
 *  book "Virtual Reality Programming With QuickTime VR 2.0". (This information is also available
 *  online, at <http://dev.info.apple.com/dev/techsupport/insidemac/qtvr/qtvrapi-2.html>.) Here is
 *  a condensed version of the info in that chapter, as pertains to panoramas:
 *
 *  A panoramic movie is a QuickTime movie that contains at least three tracks: a QTVR track, a panorama
 *  track, and a panorama image track. In addition, a QuickTime VR movie must contain some special user data
 *  that specifies the QuickTime VR movie controller. A QuickTime VR movie can also contain other kinds of
 *  tracks, such as hot spot image tracks and even sound tracks.
 *
 *  A QuickTime VR movie contains a single "QTVR track", which maintains a list of the nodes in the movie.
 *  Each individual sample in the QTVR track's media contains information about a single node, such as the
 *  node's type, ID, and name. Since we are creating a single-node movie here, our QTVR track will contain
 *  a single media sample. 
 *
 *  Every media sample in a QTVR track has the same sample description, whose type is QTVRSampleDescription.
 *  The data field of that sample description is a "VR world", an atom container whose child atoms specify
 *  information about the nodes in the movie, such as the default node ID and the default imaging properties.
 *  We'll spend a good bit of time putting things into the VR world.
 *
 *  A panoramic movie also contains a single "panorama track", which contains information specific to the
 *  panorama. A panorama track has a media sample for each media sample in the QTVR track. As a result,
 *  our panorama track will have one sample. The QTVRPanoSampleAtom structure defines the media sample data. 
 *
 *  The actual image data for a panoramic node is contained in a "panorama image track". The individual
 *  frames in that track are the diced (and also perhaps compressed) tiles of the original panoramic image.
 *  There may also be a "hot spot image track" that contains the diced (and also perhaps compressed) tiles
 *  of the hot spot panoramic image.
 *
 *  So, our general strategy, given a panoramic image, is as follows (though perhaps not in the order listed):
 *
 *      (1) Create a movie containing a video track whose frames are the compressed tiles of the panoramic
 *          image. Call this movie the "tile movie". Create a similar movie for the hot spot image. Call
 *          this movie the "hot spot tile movie".
 *      (2) Create a new, empty movie. Call this movie the "QTVR movie".
 *      (3) Create a QTVR track and its associated media.
 *      (4) Create a VR world atom container; this is stored in the sample description for the QTVR track.
 *      (5) Create a node information atom container for each node; this is stored as a media sample
 *          in the QTVR track.
 *      (6) Create a panorama track and add it to the movie.
 *      (7) Create a panorama image track by copying the video track from the tile movie to the QTVR movie.
 *      (8) Create a hot spot image track by copying the video track from the hot spot tile movie to the QTVR movie.
 *      (9) Set up track references from the QTVR track to the panorama track, and from the panorama track
 *          to the panorama image track and the hot spot image track.
 *      (A) Add a user data item that identifies the QTVR movie controller.
 *      (B) Flatten the QTVR movie into the final panoramic movie.
 *
 ********************************************************************************
 *
 *  VERSION 1.0 FILE FORMAT
 *
 *  The definitive source of information about creating QTVR 1.0 panoramic movies is Technote 1035,
 *  "QuickTime VR 1.0 Panorama Movie File Format" released in March 1996, available online at the address
 *  <http://devworld.apple.com/dev/technotes/tn/tn1035.html>. Here is a condensed version of the info
 *  in that technote:
 *
 *  For version 1.0 panoramic movies, the file format is somewhat simpler. A single-node panoramic movie
 *  contains a "scene track" and a "panorama track". The scene track is an inactive video track that contains
 *  the diced (and also perhaps compressed) tiles of the original panoramic image. A version 1.0 scene track
 *  is essentially a version 2.0 panorama image track.
 *
 *  A panoramic movie also contains a single "panorama track", which contains information specific to the
 *  panorama. The panorama track in version 1.0 differs significantly from the panorama track in version 2.0,
 *  so don't let the names confuse you. 
 *
 *  A "panorama track" in version 1.0 contains one media sample for each node in the movie. Every media sample
 *  in a panorama track has the same sample description, whose type is PanoramaDescription. This structure
 *  contains information about how the panoramic image was diced, along with information about any hot spot
 *  and low-resolution scene tracks that might be contained in the movie file.
 *
 *  Each individual sample in the panorama track's media contains information about a single node,
 *  such as the node's default view angles, pan and zoom constraints, and hot spot information.
 *  Since we are creating a single-node movie here, our panorama track will contain a single media sample. 
 *  The data in a panorama track sample is organized as a sequence of "atom data structures"; each such
 *  structure begins with size and type fields, so you can easily read thru the atom data structures by
 *  starting at the beginning of the sample data and hopping over each structure. The atoms can appear in
 *  any order in the panorama track sample data.
 *
 *  -> IMPORTANT: The "atom data structures" that comprise a panorama track sample are *not* QTAtoms, and the
 *  -> panorama track sample itself is *not* a QTAtomContainer. The version 1.0 panorama file format predates
 *  -> QuickTime 2.1, which introduced the atom data routines. As a result, you cannot use the atom data routines
 *  -> to read or write version 1.0 panorama track samples.
 *
 *  A panorama track sample must contain a "panorama header atom", whose structure is PanoSampleHeaderAtom.
 *  The sample may contain other atoms as well, such as a string table atom (which contains strings, such as
 *  node names) and a hot spot table atom (which lists all of the hot spots in a node). In this sample code,
 *  we add a hot spot image track to the VR movie, but we do not (yet) build a hot spot atom table; as a result,
 *  our panorama track sample will need to contain only a panorama header atom.
 *
 *  So, our general strategy, given a panoramic image, is as follows (though perhaps not in the order listed):
 *
 *      (1) Create a movie containing a video track whose frames are the compressed tiles of the panoramic
 *          image. Call this movie the "tile movie". Create a similar movie for the hot spot image. Call
 *          this movie the "hot spot tile movie".
 *      (2) Create a new, empty movie. Call this movie the "QTVR movie".
 *      (3) Create a scene track by copying the video track from the tile movie to the QTVR movie.
 *      (4) Create a hot spot image track by copying the video track from the hot spot tile movie to the QTVR movie.
 *      (5) Create a panorama track and add it to the movie.
 *      (6) Add a user data item that identifies the QTVR movie controller.
 *      (7) Flatten the QTVR movie into the final panoramic movie.
 *
 ********************************************************************************
 *
 *  CUBIC PANORAMA FILE FORMAT
 *
 *  QuickTime VR version 5.0 introduces "cubic panoramas", which store the panoramic image as 6 (or more) separate images that
 *  are (during playback) projected onto the sides of a cube (polyhedron). This allows the user to look straight up and straight
 *  down. The file format for cubic panoramas is identical with the Version 2.0 cylindrical file format, with these
 *  exceptions:
 *
 *  The image track (and any associated hot spot and preview tracks) must contain 6 samples/frames/faces
 *  (these can be subtiled, though tile based memory management may not be available initially). The first
 *  sample is the front cube face, the second is the right-hand cube face, and so on; sample 5 is the top of the cube
 *  and sample 6 is the bottom of the cube. You can change this default orientation with the 'cufa' descriptor.
 *  The frames are oriented horizontally (except for the top and bottom), not rotated as with cylindrical panoramas.
 *
 *  For cubic panoramas, some of the fields in the panorama sample atom should be assigned special values that allow
 *  the file to be displayed with the cylindrical engine if the cubic engine is not available. The cubic engine ignores
 *  those values, instead using values stored in the new "cubic view atom". See the function CreatePanoTrack for
 *  the special values you should use.
 *
 *
 *  NOTES:
 *
 *  *** (1) ***
 *  This code is based largely on the existing MakeQTVRPanorama sample code written by Ed Harp (and others?).
 *  MakeQTVRPanorama is a full-featured application written in C++ using Metrowerks' PowerPlant, a Mac-only
 *  application framework. Here I've uncoupled the central portion of that code, contained in the file CMovieMaker.cp,
 *  and converted it into straight C. I have taken the liberty of reworking that code as necessary to make it run
 *  also on Windows platforms and to bring it into line with the other QuickTime code samples. So far the biggest
 *  changes involve using graphics importers instead of the original PICT-reading code and inserting all those
 *  Endian macros.
 *
 *  *** (2) ***
 *  All data in QTAtom structures must be in big-endian format. We use macros like EndianU32_NtoB to convert
 *  values into the proper format before inserting them into atoms. See CreateVRWorld for some examples.
 *
 ********************************************************************************/
 
 
 
/********************************************************************************
 ********************************************************************************
 ********************************************************************************
 ***        compiler flags
 ********************************************************************************
 ********************************************************************************
 ********************************************************************************/
 
/* The following flag determines whether we show the uncompressed tiles in a window as they get processed;
 * I did this mainly for debugging purposes, but it's also a useful progress indicator....
 */
#define USE_TILE_DISPLAY_WINDOW         1
#define USE_TEMP_MEM_FIRST
 
 
/********************************************************************************
 ********************************************************************************
 ********************************************************************************
 ***        header files
 ********************************************************************************
 ********************************************************************************
 ********************************************************************************/
 
#include <Endian.h>
#include <QuickTimeVR.h>
#include <QuickTimeVRFormat.h>
#include <QuickTimeComponents.h>
#include <FixMath.h>
#include <Script.h>
#include <Sound.h>
#include <stdio.h>
#include <string.h>
#if TARGET_OS_MAC
# include <fp.h>
#else /* !TARGET_OS_MAC */
# include <math.h>
#endif /* !TARGET_OS_MAC */
#include "VRMakePano.h"
 
 
#pragma mark #Constants
/********************************************************************************
 ********************************************************************************
 ********************************************************************************
 ***        Constants
 ********************************************************************************
 ********************************************************************************
 ********************************************************************************/
 
enum {
    QTVRFlattenerType = FOUR_CHAR_CODE('vrwe')  /* An oversight prevented this from going into QuickTimeVRFormat.h */
};
 
#define D_PI                            3.1415926535898
#define D_2PI                           6.2831853071796
#define D_PI_2                          1.5707963267949
#define D_DEGREES_PER_RADIAN            57.295779513082
#define D_RADIANS_PER_DEGREE            0.017453292519943
 
#define kQTVRStandardTimeScale          3600                        /* time scale for QTVR panoramas */
#define kDefaultNodeID                  1                           /* default node ID */
 
#define kQTVRCylindricalVersion1        (long)1
#define kQTVRCylindricalVersion2v       (long)2
#define kQTVRCylindricalVersion2h       (long)3
#define kQTVRCubicVersion1              (long)4
 
#define kPanDescType                    FOUR_CHAR_CODE('pano')
#define kPanHeaderType                  FOUR_CHAR_CODE('pHdr')
#define kStringTableType                FOUR_CHAR_CODE('strT')
#define kHotSpotTableType               FOUR_CHAR_CODE('pHot')
 
#define kQTVRPanoTypeCube               FOUR_CHAR_CODE('cube')
#define kQTVRCubicViewAtomType          FOUR_CHAR_CODE('cuvw')
#define kQTVRCubicFaceDataAtomType      FOUR_CHAR_CODE('cufa')
 
#define kCreateMovieFlags               (createMovieFileDeleteCurFile | createMovieFileDontCreateResFile)
 
 
 
#pragma mark #Data Structures
/********************************************************************************
 ********************************************************************************
 ********************************************************************************
 ***        Data Structures
 ********************************************************************************
 ********************************************************************************
 ********************************************************************************/
 
/* version 1.0 data types */
 
#pragma options align=mac68k
 
/* panorama description:
 * this defines the structure of a sample description for a media sample in a panorama track
 */
typedef struct PanoramaDescription {
    long            size;                       /* total size of PanoramaDescription */
    long            type;                       /* must be 'pano' */
    long            reserved1;                  /* must be zero */
    long            reserved2;                  /* must be zero */
    short           majorVersion;               /* must be zero */
    short           minorVersion;               /* must be zero */
    long            sceneTrackID;               /* ID of video track that contains panoramic scene */
    long            loResSceneTrackID;          /* ID of video track that contains lo res panoramic scene */
    long            reserved3[6];               /* must be zero */
    long            hotSpotTrackID;             /* ID of video track that contains hotspot image */
    long            loResHotSpotTrackID;        /* ID of video track that contains lo res hotspot image */
    long            reserved4[8];               /* must be zero */
    Fixed           hPanStart;                  /* horizontal pan range start angle (eg. 0) */
    Fixed           hPanEnd;                    /* horizontal pan range end angle (eg. 360) */
    Fixed           vPanTop;                    /* vertical pan range top angle(eg. 42.5) */
    Fixed           vPanBottom;                 /* vertical pan range bottom angle (eg. -42.5) */
    Fixed           minimumZoom;                /* minimum zoom angle (eg. 5; use 0 for default)) */
    Fixed           maximumZoom;                /* maximum zoom angle (eg. 65; use 0 for default) */
    /* info for highest res version of scene track */
    long            sceneSizeX;                 /* pixel width of the panorama (eg. 768) */
    long            sceneSizeY;                 /* pixel height of the panorama (eg. 2496) */
    long            numFrames;                  /* number of diced frames (eg. 24) */
    short           reserved5;                  /* must be zero */
    short           sceneNumFramesX;            /* diced frames wide (eg. 1) */
    short           sceneNumFramesY;            /* diced frames high (eg. 24) */
    short           sceneColorDepth;            /* bit depth of the scene track (eg. 32) */
    /* info for highest res version of hotSpot track */
    long            hotSpotSizeX;               /* pixel width of the hot spot panorama (eg. 768) */
    long            hotSpotSizeY;               /* pixel height of the hot spot panorama (eg. 2496) */
    short           reserved6;                  /* must be zero */
    short           hotSpotNumFramesX;          /* diced frames wide (eg. 1) */
    short           hotSpotNumFramesY;          /* diced frames high (eg. 24) */
    short           hotSpotColorDepth;          /* must be 8 */
    
} PanoramaDescription, *PanoramaDescriptionPtr, **PanoramaDescriptionHandle;
 
/* panorama sample header atom:
 * this defines the structure of (part of) a media sample in a panorama track
 */
typedef struct PanoSampleHeaderAtom {
    long            size;
    OSType          type;                       /* must be 'pHdr' */
    unsigned long   nodeID;                     /* corresponds to a node ID in the idToTime table  */
    Fixed           defHPan;                    /* default horizontal pan angle when displaying this node */
    Fixed           defVPan;                    /* default vertical pan angle when displaying this node */
    Fixed           defZoom;                    /* default zoom angle when displaying this node */
    Fixed           minHPan;                    /* constraints for this node; use zero for default */
    Fixed           minVPan;
    Fixed           minZoom;
    Fixed           maxHPan;
    Fixed           maxVPan;
    Fixed           maxZoom;
    long            reserved1;                  /* must be zero */
    long            reserved2;                  /* must be zero */
    long            nameStrOffset;              /* offset into string table atom */
    long            commentStrOffset;           /* offset into string table atom */
} PanoSampleHeaderAtom, *PanoSampleHeaderAtomPtr, **PanoSampleHeaderAtomHandle;
 
/* hot spot atom */
typedef struct HotSpot {
    unsigned short  hotSpotID;                  /* the ID of this hot spot */
    short           reserved1;                  /* must be zero */
    OSType          type;                       /* the hot spot type (e.g. 'link',  'navg', etc) */
    unsigned long   typeData;                   /* for link and navg, the ID in the link  and navg table */
    Fixed           viewHPan;                   /* canonical view for this hot spot */
    Fixed           viewVPan;
    Fixed           viewZoom;
    Rect            hotSpotRect;                /* bounding rectangle of the hot spot in  the panorama */
    long            mouseOverCursorID;
    long            mouseDownCursorID;
    long            mouseUpCursorID;
    long            reserved2;                  /* must be 0 */
    long            nameStrOffset;              /* offset into string table atom */
    long            commentStrOffset;           /* offset into string table atom */
} HotSpot, *HotSpotPtr, **HotSpotHandle;
 
/* hot spot table atom */
typedef struct HotSpotTableAtom {
    long            size;
    OSType          type;                       /* must be 'pHot' */
    short           pad;                        /* must be 0 */
    short           numHotSpots;
    HotSpot         hotSpots[1];                /* list of hot spots */
} HotSpotTableAtom, *HotSpotTableAtomPtr, **HotSpotTableAtomHandle;
 
 
typedef struct StringTableAtom {
    long            size;
    OSType          type;                       /* must be 'strT' */
    char            bunchOstrings[1];           /* concatenated Pascal strings */
} StringTableAtom, *StringTableAtomPtr, **StringTableAtomHandle;
 
 
#pragma options align=reset
 
 
typedef struct MyPanoTrackDescriptor {
    long    versionToCreate;
 
    long    imageTileWidth;
    long    imageTileHeight;
    long    imageRefTrackIndex;
    
    long    hotSpotTileWidth;
    long    hotSpotTileHeight;
    long    hotSpotRefTrackIndex;
    
    long    fastStartTileWidth;
    long    fastStartTileHeight;
    long    fastStartRefTrackIndex;
} MyPanoTrackDescriptor;
 
 
typedef struct FaceOrientation {    float x, y, z, theta;   } FaceOrientation;
 
 
#pragma mark #Global Variables
/********************************************************************************
 ********************************************************************************
 ********************************************************************************
 ***        Global Variables
 ********************************************************************************
 ********************************************************************************
 ********************************************************************************/
 
 
static FaceOrientation faceOrientation[6] = {
/*  X       Y       Z       theta   */
    0.0f,   1.0f,   0.0f,      0.0f,    /* Front (no rotation) */
    0.0f,   1.0f,   0.0f,    -90.0f,    /* Right (rotate 90 degrees rightward about positive Y axis) */
    0.0f,   1.0f,   0.0f,   -180.0f,    /* Back (rotate 180 degrees rightward about positive Y axis) */
    0.0f,   1.0f,   0.0f,   -270.0f,    /* Left (rotate 90 degrees rightward about negative Y axis) */
    1.0f,   0.0f,   0.0f,     90.0f,    /* Top (rotate 90 degrees upward about positive X axis) */
    1.0f,   0.0f,   0.0f,    -90.0f     /* Bottom (rotate 90 degrees downward about positive X axis) */
};
 
 
/********************************************************************************
 ********************************************************************************
 ********************************************************************************
 ***        Support Functions
 ********************************************************************************
 ********************************************************************************
 ********************************************************************************/
 
 
 
/********************************************************************************
 * MyNewHandle
 ********************************************************************************/
 
static Handle
MyNewHandle(Size size)
{
    OSErr err;
    Handle h;
    
    #ifdef USE_TEMP_MEM_FIRST
        h = TempNewHandle(size, &err);
        if (err != noErr)
            h = NewHandle(size);
    #else /* USE_HEAP_MEM_FIRST */
        h = NewHandle(size);
        if (h == NULL)
            h = TempNewHandle(size, &err);
    #endif /* USE_HEAP_MEM_FIRST */
 
    return(h);
}
 
 
/********************************************************************************
 * MyNewHandleClear
 ********************************************************************************/
 
#if !TARGET_OS_MAC
 #include <string.h>
 #define BlockZero(p, z)    memset(p, 0, z)
#endif /* !TARGET_OS_MAC */
 
static Handle
MyNewHandleClear(Size size)
{
    OSErr err;
    Handle h;
    
    #ifdef USE_TEMP_MEM_FIRST
        h = TempNewHandle(size, &err);
        if (err == noErr)
            BlockZero(*h, size);
        else
            h = NewHandleClear(size);
    #else /* USE_HEAP_MEM_FIRST */
        h = NewHandleClear(size);
        if (h == NULL) {
            h = TempNewHandle(size, &err);
            if (err == noErr)
                BlockZero(*h, size);
        }
    #endif /* USE_HEAP_MEM_FIRST */
 
    return(h);
}
 
 
/*******************************************************************************
 * MakeTempFSSpec
 *
 *  Tack the given tmpSuffix onto the origSpec, resulting in the tmpSpec.
 *******************************************************************************/
 
static void
MakeTempFSSpec(const FSSpec *origSpec, const char *tmpSuffix, FSSpec *tmpSpec)
{
    long    sufSize, namSize;
    char    *t;
    
    *tmpSpec = *origSpec;
    namSize  = tmpSpec->name[0];
    sufSize  = strlen(tmpSuffix);
 
#if TARGET_OS_MAC
    if (namSize > (32 - 1 - sufSize))
        namSize = (32 - 1 - sufSize);
#else /* !TARGET_OS_MAC */
    if (namSize > (sizeof(StrFileName) - 1 - sufSize))
        namSize = (sizeof(StrFileName) - 1 - sufSize);
#endif /* !TARGET_OS_MAC */
    tmpSpec->name[0] = namSize + sufSize;
    t = (char*)(&tmpSpec->name[namSize+1]);
    while (sufSize--)
        *t++ = *tmpSuffix++;
}
 
 
/*******************************************************************************
 * ClearFSSpec
 *******************************************************************************/
 
static void
ClearFSSpec(FSSpec *fsSpec)
{
    fsSpec->vRefNum = 0;
    fsSpec->parID   = 0;
    fsSpec->name[0] = 0;
}
 
 
/********************************************************************************
 * HasOneFSSpec
 ********************************************************************************/
 
static Boolean
HasOneFSSpec(FSSpecHandle sh)
{
    return((
            (sh != NULL) &&
            (GetHandleSize((Handle)sh) >= sizeof(FSSpec)) &&
            (MemError() == noErr) &&
            (sh[0][0].name[0] != 0)
        ) ?
        true : false
    );
}
 
 
/********************************************************************************
 * ConvertFloatToBigEndian
 * EndianF32_NtoB
 *
 *  Convert the specified floating-point number to big-endian format.
 ********************************************************************************/
 
#if TARGET_RT_BIG_ENDIAN
# define EndianF32_NtoB(f)          ((float)(f))
#else /* TARGET_RT_LITTLE_ENDIAN */
 
static float
EndianF32_NtoB(float f)
{
    unsigned long fl;
    
    *((float*)(&fl)) = f;
    fl = EndianU32_NtoB(fl);
    f = *((float*)(&fl));
    return(f);
}
 
#endif /* TARGET_RT_LITTLE_ENDIAN */
 
 
#ifndef macintosh
/*******************************************************************************
 * roundtol
 *******************************************************************************/
 
static long
roundtol(double_t f)
{
    f += (f < 0) ? -0.5 : 0.5;
    return((long)f);
}
#endif /* macintosh */
 
 
/*******************************************************************************
 * EndianF32NtoFixedB
 *******************************************************************************/
 
static Fixed
EndianF32NtoFixedB(float f)
{
    Fixed x;
    
    x = roundtol(f * 65536.0);
    x = EndianU32_NtoB(x);
    return(x);
}
 
 
/********************************************************************************
 * FocalLengthToFOV
 *      t = 2 atan((h-1)/(2f))
 ********************************************************************************/
 
static float
FocalLengthToFOV(float focalLength, long height)
{
    return(2.0F * atan((height - 1) * 0.5F / focalLength));
}
 
 
/********************************************************************************
 * PixelZoomToFOV
 ********************************************************************************/
 
static float
PixelZoomToFOV(float srcFocalLength, long windowHeight, float pixelZoom)
{
    return(FocalLengthToFOV(srcFocalLength * pixelZoom, windowHeight));
}
 
 
#pragma mark -
/*******************************************************************************
 * AddCStringToAtomContainer
 *      Add a C string to the specified atom container;
 *      return (through theID) the ID of the new string atom.
 *******************************************************************************/
 
static OSErr
AddCStringToAtomContainer(QTAtomContainer theContainer, QTAtom theParent, const char *theString, QTAtomID *theID)
{
    OSErr               err         = noErr;
    QTAtom              myStringAtom;
    UInt16              mySize;
    QTVRStringAtomPtr   myStringAtomPtr;
    unsigned short      stringLength;
 
    *theID = 0;             /* initialize the returned atom ID */
    
    if ((theContainer == NULL) || (theParent == 0))
        return(paramErr);
        
    
    stringLength = strlen(theString);
    mySize = sizeof(QTVRStringAtom) - 4 + stringLength;
    myStringAtomPtr = (QTVRStringAtomPtr)NewPtr(mySize);
    
    if (myStringAtomPtr != NULL) {
        myStringAtomPtr->stringUsage    = EndianU16_NtoB(1);
        myStringAtomPtr->stringLength   = EndianU16_NtoB(stringLength);
        BlockMoveData(theString, myStringAtomPtr->theString, stringLength);
        err = QTInsertChild(theContainer, theParent, kQTVRStringAtomType, 0, 0, mySize, (Ptr)myStringAtomPtr, &myStringAtom);
        DisposePtr((Ptr)myStringAtomPtr);
        
        if (err == noErr)
            QTGetAtomTypeAndID(theContainer, myStringAtom, NULL, theID);
    }
    
    return(err);
}
 
 
/*******************************************************************************
 * AddStringHandleToAtomContainer
 *      Add a string, contained in a Handle, to the specified atom container;
 *      return (through theID) the ID of the new string atom.
 *******************************************************************************/
 
static OSErr
AddStringHandleToAtomContainer(QTAtomContainer theContainer, QTAtom theParent, Handle theString, QTAtomID *theID)
{
    OSErr               err         = noErr;
    QTAtom              myStringAtom;
    UInt16              mySize;
    QTVRStringAtomPtr   myStringAtomPtr;
    unsigned short      stringLength;
 
    *theID = 0;             /* initialize the returned atom ID */
    
    if ((theContainer == NULL) || (theParent == 0))
        return(paramErr);
        
    
    stringLength = GetHandleSize(theString);
    mySize = sizeof(QTVRStringAtom) - 4 + stringLength;
    myStringAtomPtr = (QTVRStringAtomPtr)NewPtr(mySize);
    
    if (myStringAtomPtr != NULL) {
        myStringAtomPtr->stringUsage    = EndianU16_NtoB(1);
        myStringAtomPtr->stringLength   = EndianU16_NtoB(stringLength);
        BlockMoveData(*theString, myStringAtomPtr->theString, stringLength);
        err = QTInsertChild(theContainer, theParent, kQTVRStringAtomType, 0, 0, mySize, (Ptr)myStringAtomPtr, &myStringAtom);
        DisposePtr((Ptr)myStringAtomPtr);
        
        if (err == noErr)
            QTGetAtomTypeAndID(theContainer, myStringAtom, NULL, theID);
    }
    
    return(err);
}
 
 
/*******************************************************************************
 * SetQTControllerType
 *
 *  Set the controller type of the specified movie.
 *
 * This function adds an item to the movie's user data;
 * the updated user data is written to the movie file when the movie is next updated
 * (by calling AddMovieResource or UpdateMovieResource).
 *
 *******************************************************************************/
 
static OSErr
SetQTControllerType(Movie theMovie, OSType theType)
{
    UserData    myUserData;
    OSErr       err     = noErr;
 
    /* make sure we've got a movie */
    if (theMovie == NULL)
        return(paramErr);
        
    /* get the movie's user data list */
    myUserData = GetMovieUserData(theMovie);
    if (myUserData == NULL)
        return(paramErr);
    
    theType = EndianU32_NtoB(theType);
    err = SetUserDataItem(myUserData, &theType, sizeof(theType), kQTControllerType, 0);
 
    return(err);
}
 
 
/*******************************************************************************
 * MakeQTVRHotSpot1x0
 *
 *  Create a hot spot table atom with atoms for a single hot spot; also, if necessary,
 *  resize the string table handle theStringTable to contain a name and comment for the
 *  hot spot.
 *
 *  NOTE: This function builds hot spots that conform to version 1.0 of the QuickTime VR file format.
 *******************************************************************************/
 
static HotSpotTableAtomHandle
MakeQTVRHotSpot1x0(StringTableAtomHandle theStringTable)
{
#pragma unused(theStringTable)
 
    HotSpotTableAtomHandle  myHandle = NULL;
    
    /* [left as an exercise for the reader] */
    
    return(myHandle);
}
 
 
/*******************************************************************************
 * AddQTVRHotSpot2x0
 *
 * Create and add a version 2.0 hot spot atom to the specified node info atom.
 *
 * This atom in turn, has at least one child: a hot spot information atom,
 * which contains general info about the hot spot.
 *
 * A second child is contained when the hot spot is a URL link.
 *
 * Only two types of hot spots are accommodated by this API:
 *      kQTVRHotSpotURLType
 * or
 *      kQTVRHotSpotUndefinedType.
 * They are distionguished by whether the URL Handle is NULL or not.
 *
 * Note that this API does not accommodate kQTVRHotSpotLinkType hot spots.
*******************************************************************************/
 
static OSErr
AddQTVRHotSpot2x0(
    QTAtomContainer theNodeInfo,
    QTAtom          theHSParent,
    UInt32          theIndex,
    Handle          theHSName,
    Handle          theURL
)
{
    QTAtom                  myHSAtom;
    QTVRHotSpotInfoAtom     myHSInfoAtom;
    QTAtomID                nameID;
    OSErr                   err     = noErr;
 
    
    /* the atom ID should be the same as the hot spot ID, which is an index in a 8-bit color table   */
    err = QTInsertChild(theNodeInfo, theHSParent, kQTVRHotSpotAtomType, theIndex, 0, 0, NULL, &myHSAtom);
    if (err != noErr)
        goto bail;
    
    /*
     * Set the value of the hot spot information atom
     */
    
    myHSInfoAtom.majorVersion       = EndianU16_NtoB(kQTVRMajorVersion);
    myHSInfoAtom.minorVersion       = EndianU16_NtoB(kQTVRMinorVersion);
    myHSInfoAtom.hotSpotType        = theURL ? EndianU32_NtoB(kQTVRHotSpotURLType)
                                             : EndianU32_NtoB(kQTVRHotSpotUndefinedType);
 
 
    if ((err = AddStringHandleToAtomContainer(theNodeInfo, myHSAtom, theHSName, &nameID)) != noErr)
        goto bail;
    myHSInfoAtom.nameAtomID         = EndianU32_NtoB(nameID);   /* Add the hot spot name */
 
    myHSInfoAtom.commentAtomID      = EndianU32_NtoB(0L);       /* Add the hot spot comment (none) */
 
    myHSInfoAtom.cursorID[0]        = EndianU32_NtoB(0L);       /* Set the custom cursor IDs; 0 means that no custom cursors exist */
    myHSInfoAtom.cursorID[1]        = EndianU32_NtoB(0L);
    myHSInfoAtom.cursorID[2]        = EndianU32_NtoB(0L);
    
    myHSInfoAtom.bestPan            = EndianF32_NtoB(0.0);      /* Set the viewing parameters to best see this hot spot */
    myHSInfoAtom.bestTilt           = EndianF32_NtoB(0.0);
    myHSInfoAtom.bestFOV            = EndianF32_NtoB(0.0);
    myHSInfoAtom.bestViewCenter.x   = EndianF32_NtoB(0.0);
    myHSInfoAtom.bestViewCenter.y   = EndianF32_NtoB(0.0);
 
    myHSInfoAtom.hotSpotRect.top    = EndianU16_NtoB(0);        /* Set hot spot bounding rectangle; apparently unused */
    myHSInfoAtom.hotSpotRect.left   = EndianU16_NtoB(0);
    myHSInfoAtom.hotSpotRect.bottom = EndianU16_NtoB(0);
    myHSInfoAtom.hotSpotRect.right  = EndianU16_NtoB(0);
 
    myHSInfoAtom.flags              = EndianU32_NtoB(0L);
    myHSInfoAtom.reserved1          = EndianU32_NtoB(0L);
    myHSInfoAtom.reserved2          = EndianU32_NtoB(0L);
    
 
    if ((err = QTInsertChild(theNodeInfo, myHSAtom,             /* Insert the hot spot information atom into the hot spot atom */
        kQTVRHotSpotInfoAtomType, 1, 0, sizeof(myHSInfoAtom), &myHSInfoAtom, NULL)) != noErr
    )
        goto bail;
        
    if (theURL) {                                               /* Add a URL hot spot atom as a child of the hot spot atom */
        HLock(theURL);
        err = QTInsertChild(theNodeInfo, myHSAtom, kQTVRHotSpotURLType, 1, 0, GetHandleSize(theURL), *theURL, NULL);
        HUnlock(theURL);
    }
    
bail:
    return(err);
}
 
 
/********************************************************************************
 * AddAllHotSpots2x0
 ********************************************************************************/
 
static OSErr
AddAllHotSpots2x0(
    QTAtomContainer             nodeInfoAtom,
    VRHotSpotDefinitionHandle   hotSpots
)
{
    QTAtom  hsParentAtom;
    long    i, n;
    OSErr   err             = noErr;
 
    if (hotSpots != NULL) {
        /* Insert the hot spot parent atom into the node info atom container */
        err = QTInsertChild(nodeInfoAtom, kParentAtomIsContainer, kQTVRHotSpotParentAtomType, 1, 1, 0, NULL, &hsParentAtom);
        if (err != noErr)
            goto bail;
 
        n = GetHandleSize((Handle)(hotSpots)) / sizeof(VRHotSpotDefinition);
        for (i = 0; i < n; i++) {
            VRHotSpotDefinition *hs = *hotSpots + i;
            if ((err = AddQTVRHotSpot2x0(nodeInfoAtom, hsParentAtom, hs->index, hs->name, hs->url)) != noErr)
                goto bail;
        }
    }
 
bail:
    return(err);
}
 
 
/*******************************************************************************
 * CountTrackSamples
 *******************************************************************************/
 
static long
CountTrackSamples(Track track)
{
    long        numSamples;
    TimeValue   currentTime, interestingTime;
    OSErr       err;
 
    err = GetMoviesStickyError();
    interestingTime = 1;
    currentTime = 0;
    numSamples = -1; /* will iterate one more time than there is slices */
 
    while (interestingTime != currentTime) {
        currentTime = interestingTime;
        ++numSamples;
        GetTrackNextInterestingTime(track, nextTimeMediaSample, currentTime, 1, &interestingTime, NULL);
    }
    
    if (err == noErr)
        ClearMoviesStickyError();   /* Because the last GetTrackNextInterestingTime() goes beyond the end */
 
    return(numSamples);
}
 
 
/*******************************************************************************
 * ImportFirstVideoTrackFromMovie
 *
 *  Copy a video track from one movie (the source) to another (the destination).
 *
 *  We assume that we want the first video track in theSrcMovie.
 *******************************************************************************/
 
static OSErr
ImportFirstVideoTrackFromMovie(
    Movie       srcMovie,               /* Transfer the first video track of this movie... */
    Movie       dstMovie,               /* to this movie. */
    TimeValue   dstSequenceDuration,    /* duration of the whole sequence */
    long        numSamples,             /* the number of samples, or zero if the whole track is desired */
    long        *pDstTrackWidth,        /* returned track width,  in integral units */
    long        *pDstTrackHeight,       /* returned track height, in integral units */
    Track       *pDstTrack              /* the track added to the dstMovie */
)
{
    Track                       srcTrack        = NULL;
    Media                       srcMedia        = NULL;
    Track                       dstTrack        = NULL;
    Media                       dstMedia        = NULL;
    Fixed                       trackWidth, trackHeight;
    OSType                      mediaType;
    SampleDescriptionHandle     sampleDesc      = (SampleDescriptionHandle)MyNewHandle(0);
    Handle                      sampleData      = MyNewHandle(0);
    long                        sampleDataSize;
    TimeValue                   srcMediaTime    = 0;
    TimeValue                   srcSampleDuration, dstSampleDuration;
    
    ClearMoviesStickyError();
    
    /* get the first video track in the source movie */
    srcTrack = GetMovieIndTrackType(srcMovie, 1, VideoMediaType, movieTrackMediaType);
    if (srcTrack == NULL)
        return(paramErr);
    
    /* get the track's media and dimensions */
    srcMedia = GetTrackMedia(srcTrack);
    GetTrackDimensions(srcTrack, &trackWidth, &trackHeight);
    
    /* create a destination track */
    dstTrack = NewMovieTrack(dstMovie, trackWidth, trackHeight, GetTrackVolume(srcTrack));
    if (pDstTrackWidth  != NULL)        *pDstTrackWidth  = trackWidth  >> 16;
    if (pDstTrackHeight != NULL)        *pDstTrackHeight = trackHeight >> 16;
    if (pDstTrack       != NULL)        *pDstTrack       = dstTrack;
 
    /* create a destination media */
    GetMediaHandlerDescription(srcMedia, &mediaType, NULL, NULL);
    dstMedia = NewTrackMedia(dstTrack, mediaType, kQTVRStandardTimeScale, 0, 0);
    
    /* determine the number of samples */
    if (numSamples == 0)
        numSamples = CountTrackSamples(srcTrack);
    dstSampleDuration = dstSequenceDuration / numSamples;
 
    
    /* extract samples one at a time */
    BeginMediaEdits(dstMedia);
    while (numSamples--) {
        GetMediaSample(srcMedia, sampleData, 0, &sampleDataSize, srcMediaTime, NULL, &srcSampleDuration, sampleDesc, NULL, 1, NULL, NULL);
        srcMediaTime += srcSampleDuration;
        AddMediaSample(dstMedia, sampleData, 0, sampleDataSize, dstSampleDuration, sampleDesc, 1, 0, NULL);
    }
    EndMediaEdits(dstMedia);
    
    InsertMediaIntoTrack(dstTrack, 0, 0, GetMediaDuration(dstMedia), 1L << 16);
 
    DisposeHandle((Handle)sampleDesc);
    DisposeHandle(sampleData);
 
    /* a panorama image track should always be disabled */
    SetTrackEnabled(dstTrack, false);
 
    return(GetMoviesStickyError());
}
 
 
/*******************************************************************************
 * ImportFirstVideoTrackFromFile
 *******************************************************************************/
 
static OSErr
ImportFirstVideoTrackFromFile(
    const FSSpec    *srcMovieSpec,          /* Transfer the first video track from this movie... */
    Movie           dstMovie,               /* ... to this movie */
    TimeValue       dstSequenceDuration,    /* Desired time duration of the sequence */
    long            numSamples,             /* The number of samples, or zero if the whole track is desired */
    long            *pDstFrameWidth,        /* Return the width  of the track frame */
    long            *pDstFrameHeight,       /* Return the height of the track frame */
    Track           *pDstTrack              /* Return the new track */
)
{
    Movie   srcMovie    = NULL;
    short   srcRefNum   = 0;
    short   srcResID    = 0;
    OSErr   err;
 
    err = OpenMovieFile(srcMovieSpec, &srcRefNum, fsRdPerm);
    if (err != noErr)
        goto bail;
    
    err = NewMovieFromFile(&srcMovie, srcRefNum, &srcResID, 0, 0, 0);
    if (err != noErr)
        goto bail;
 
    SetMoviePlayHints(srcMovie, hintsHighQuality, hintsHighQuality);
    
    err = ImportFirstVideoTrackFromMovie(srcMovie, dstMovie, dstSequenceDuration, numSamples,
                                        pDstFrameWidth, pDstFrameHeight, pDstTrack
                                    );
 
bail:
    if (srcMovie  != NULL)  DisposeMovie(srcMovie);
    if (srcRefNum != 0)     CloseMovieFile(srcRefNum);
    
    return(err);
}
 
 
/*******************************************************************************
 * VerifyPanoParameters
 *******************************************************************************/
 
static void
VerifyPanoParameters(register VRMakeQTVRParams *qtvrParams, const MyPanoTrackDescriptor *panoDesc)
{
    float circumference, axialHeight, focalLengthCircumferential, focalLengthAxial, maxFOV;
 
 
    if (qtvrParams->trackDuration <= 0)
        qtvrParams->trackDuration           = 7200;
 
    /* Set default pan range, if not set appropriately */
    if (qtvrParams->maxPan == qtvrParams->minPan) {
        qtvrParams->minPan                  =   0.0f;   /* Default: 360 degree wrapping pano */
        qtvrParams->maxPan                  = 360.0f;
        qtvrParams->wraps                   = 1;
    }
 
 
    if ((panoDesc->versionToCreate == kQTVRCylindricalVersion2v) || (panoDesc->versionToCreate == kQTVRCylindricalVersion2h)) { /* we're building a version 2.0 cylindrical panorama */
 
        /* Check for valid parameters, and assign defaults. */
        if (panoDesc->versionToCreate == kQTVRCylindricalVersion2h) {   /* non-rotated pano */
            circumference                   = panoDesc->imageTileWidth  * qtvrParams->tilesH; if (!qtvrParams->wraps) circumference--;
            axialHeight                     = panoDesc->imageTileHeight * qtvrParams->tilesV;
        }
        else {                                              /* rotated pano */
            circumference                   = panoDesc->imageTileHeight * qtvrParams->tilesV; if (!qtvrParams->wraps) circumference--;
            axialHeight                     = panoDesc->imageTileWidth  * qtvrParams->tilesH;
        }
        focalLengthCircumferential          = circumference / (qtvrParams->maxPan - qtvrParams->minPan) * D_DEGREES_PER_RADIAN;
        if (qtvrParams->maxTilt == qtvrParams->minTilt) {
            focalLengthAxial                = focalLengthCircumferential;               /* Default: square pixels */
            qtvrParams->maxTilt             = atan((axialHeight - 1) / (2.0f * focalLengthAxial)) * D_DEGREES_PER_RADIAN;
            qtvrParams->minTilt             = -qtvrParams->maxTilt;
        }
        else {
            focalLengthAxial                = (axialHeight - 1) / (tan(qtvrParams->maxTilt * D_RADIANS_PER_DEGREE) - tan(qtvrParams->minTilt * D_RADIANS_PER_DEGREE));
        }
        maxFOV = qtvrParams->maxTilt - qtvrParams->minTilt;
        if ((qtvrParams->maxFieldOfView     <= 0.0f) || (qtvrParams->maxFieldOfView > maxFOV))
            qtvrParams->maxFieldOfView      = maxFOV;
        if (qtvrParams->minFieldOfView      <= 0.0f)                                            /* Set minimum to 2:1 pixel zoom */
            qtvrParams->minFieldOfView      = PixelZoomToFOV(focalLengthAxial, qtvrParams->windowHeight, 2.0f) * D_DEGREES_PER_RADIAN;
        if (qtvrParams->minFieldOfView      > qtvrParams->maxFieldOfView)
            qtvrParams->minFieldOfView      = qtvrParams->maxFieldOfView;
        if (qtvrParams->defaultFieldOfView  == 0.0f)                                        /* Set default to 1:1 pixel zoom */
            qtvrParams->defaultFieldOfView  = PixelZoomToFOV(focalLengthAxial, qtvrParams->windowHeight, 1.0f) * D_DEGREES_PER_RADIAN;
        if (qtvrParams->defaultFieldOfView  > qtvrParams->maxFieldOfView)
            qtvrParams->defaultFieldOfView  = qtvrParams->maxFieldOfView;
        if (qtvrParams->defaultFieldOfView  < qtvrParams->minFieldOfView)
            qtvrParams->defaultFieldOfView  = qtvrParams->minFieldOfView;
    }
 
    else {  /* We're building a cubic panorama */
    
        /* Check for valid parameters, and set to default if not */
        if (qtvrParams->maxTilt == qtvrParams->minTilt) {       /* Default: peg at the poles */
            qtvrParams->minTilt             = -90.0f;
            qtvrParams->maxTilt             =  90.0f;
        }
        focalLengthAxial                    = (panoDesc->imageTileHeight * qtvrParams->tilesV - 1) * 0.5f;  /* These are questionable for partial cubic panos */
        maxFOV = qtvrParams->maxTilt - qtvrParams->minTilt;
        if (maxFOV > 170.1f)                            /* 170.1 degrees (= 3 radians) limited by numerical precision in the computations */
            maxFOV = 170.1f;
        if (qtvrParams->maxFieldOfView <= 0.0f) {
            qtvrParams->maxFieldOfView          = maxFOV;
            if (qtvrParams->maxFieldOfView > 120.0)
                qtvrParams->maxFieldOfView      = 120.0f;   /* 120 degrees is the default maximum FOV */
        }
        else if (qtvrParams->maxFieldOfView > maxFOV)
            qtvrParams->maxFieldOfView          = maxFOV;
        if (qtvrParams->minFieldOfView <= 0.0f)                                     /* Set minimum to 2:1 pixel zoom */
            qtvrParams->minFieldOfView          = PixelZoomToFOV(focalLengthAxial, qtvrParams->windowHeight, 2.0f) * D_DEGREES_PER_RADIAN;
        if (qtvrParams->minFieldOfView > qtvrParams->maxFieldOfView)
            qtvrParams->minFieldOfView          = qtvrParams->maxFieldOfView;
        if (qtvrParams->defaultFieldOfView <= 0.0f)                                 /* Set default to 1:1 pixel zoom */
            qtvrParams->defaultFieldOfView      = PixelZoomToFOV(focalLengthAxial, qtvrParams->windowHeight, 1.0f) * D_DEGREES_PER_RADIAN;
        if (qtvrParams->defaultFieldOfView > qtvrParams->maxFieldOfView)
            qtvrParams->defaultFieldOfView      = qtvrParams->maxFieldOfView;
        if (qtvrParams->defaultFieldOfView < qtvrParams->minFieldOfView)
            qtvrParams->defaultFieldOfView      = qtvrParams->minFieldOfView;
    }
}
 
 
/*******************************************************************************
 * SetCubicViewData
 *******************************************************************************/
 
static void
SetCubicViewData(const VRMakeQTVRParams *qtvrParams, QTVRCubicViewAtom *cubicViewData)
{
    cubicViewData->minPan               = EndianF32_NtoB(qtvrParams->minPan);
    cubicViewData->maxPan               = EndianF32_NtoB(qtvrParams->maxPan);
    cubicViewData->minTilt              = EndianF32_NtoB(qtvrParams->minTilt);
    cubicViewData->maxTilt              = EndianF32_NtoB(qtvrParams->maxTilt);
    cubicViewData->minFieldOfView       = EndianF32_NtoB(qtvrParams->minFieldOfView);
    cubicViewData->maxFieldOfView       = EndianF32_NtoB(qtvrParams->maxFieldOfView);
    cubicViewData->defaultPan           = EndianF32_NtoB(qtvrParams->defaultPan);
    cubicViewData->defaultTilt          = EndianF32_NtoB(qtvrParams->defaultTilt);
    cubicViewData->defaultFieldOfView   = EndianF32_NtoB(qtvrParams->defaultFieldOfView);
}
 
 
/********************************************************************************
 * SetOneCubicFaceData
 *
 *  This assumes that the faces are equal subtiles of 6 cube faces.
 ********************************************************************************/
 
static void
SetOneCubicFaceData(
    QTVRCubicFaceData   *face,
    float               x,          /* axis of rotation */
    float               y,
    float               z,  
    float               degrees,    /* rotation about axis */
    long                tilesH,     /* the number of subdivisions to each face */
    long                tilesV,
    long                h,          /* The horizontal index of this sub-face */
    long                v           /* The vertical   index of this sub-face */
)
{
    double halfAngle, norm, sqrtCotVFOV, s, c;
    
    sqrtCotVFOV = sqrt((double)tilesV);
    halfAngle = degrees * D_PI / 360.0;
    norm = x*x + y*y + z*z;
    if (norm != 0.0)
        norm = 1.0 / sqrt(norm);
    if (fabs(s = sin(halfAngle)) < 1.0e-8)  s = 0.0;    /* Toss out numerically insignificant values ... */
    if (fabs(c = cos(halfAngle)) < 1.0e-8)  c = 0.0;    /* ... so the file format looks nice */
    norm *= s * sqrtCotVFOV;
 
    face->orientation[0]    = EndianF32_NtoB(c * sqrtCotVFOV);
    face->orientation[1]    = EndianF32_NtoB(x * norm);
    face->orientation[2]    = EndianF32_NtoB(y * norm);
    face->orientation[3]    = EndianF32_NtoB(z * norm);
    
    /* Center, normalized by the vertical dimension */
    face->center[0]         = EndianF32_NtoB( (2 * h - tilesH + 1) * (float)tilesV / (float)tilesH);
    face->center[1]         = EndianF32_NtoB(-(2 * v - tilesV + 1));    /* reverse from scan direction to world up direction */
    face->aspect            = EndianF32_NtoB(1.0f);
    face->skew              = EndianF32_NtoB(0.0f);
}
 
 
/*******************************************************************************
 * SetCubicFaceData
 *******************************************************************************/
 
static void
SetCubicFaceData(const VRMakeQTVRParams *qtvrParams, QTVRCubicFaceData* faceData)
{
    long            i, j, k;
    FaceOrientation *fo;
    
    for (k = 0, fo = faceOrientation; k < 6; k++, fo++)
        for (j = 0; j < qtvrParams->tilesV; j++)
            for (i = 0; i < qtvrParams->tilesH; i++, faceData++)
                SetOneCubicFaceData(faceData, fo->x, fo->y, fo->z, fo->theta, qtvrParams->tilesH, qtvrParams->tilesV, i, j);
}
 
 
/*******************************************************************************
 * SetPanoSampleData
 *******************************************************************************/
 
static void
SetPanoSampleData(
    const VRMakeQTVRParams      *qtvrParams,
    const MyPanoTrackDescriptor *panoDescriptor,
    QTVRPanoSampleAtom          *panoSampleData
)
{
    panoSampleData->majorVersion            = EndianU16_NtoB(kQTVRMajorVersion);
    panoSampleData->minorVersion            = EndianU16_NtoB(kQTVRMinorVersion);
    panoSampleData->imageRefTrackIndex      = EndianU32_NtoB(panoDescriptor->imageRefTrackIndex);
    panoSampleData->hotSpotRefTrackIndex    = EndianU32_NtoB(panoDescriptor->hotSpotRefTrackIndex);
 
 
    if ((panoDescriptor->versionToCreate == kQTVRCylindricalVersion2v) || (panoDescriptor->versionToCreate == kQTVRCylindricalVersion2h)) { /* we're building a version 2.0 cylindrical panorama */
        /* Set cylindrical parameters in Pano Sample Atom */
        panoSampleData->minPan              = EndianF32_NtoB(qtvrParams->minPan);
        panoSampleData->maxPan              = EndianF32_NtoB(qtvrParams->maxPan);
        panoSampleData->minTilt             = EndianF32_NtoB(qtvrParams->minTilt);
        panoSampleData->maxTilt             = EndianF32_NtoB(qtvrParams->maxTilt);
        panoSampleData->minFieldOfView      = EndianF32_NtoB(qtvrParams->minFieldOfView);
        panoSampleData->maxFieldOfView      = EndianF32_NtoB(qtvrParams->maxFieldOfView);
        panoSampleData->defaultPan          = EndianF32_NtoB(qtvrParams->defaultPan);
        panoSampleData->defaultTilt         = EndianF32_NtoB(qtvrParams->defaultTilt);
        panoSampleData->defaultFieldOfView  = EndianF32_NtoB(qtvrParams->defaultFieldOfView);
 
        panoSampleData->imageSizeX          = EndianU32_NtoB(panoDescriptor->imageTileWidth  * qtvrParams->tilesH);
        panoSampleData->imageSizeY          = EndianU32_NtoB(panoDescriptor->imageTileHeight * qtvrParams->tilesV);
        panoSampleData->imageNumFramesX     = EndianU16_NtoB(qtvrParams->tilesH);
        panoSampleData->imageNumFramesY     = EndianU16_NtoB(qtvrParams->tilesV);
        
        panoSampleData->hotSpotSizeX        = EndianU32_NtoB(panoDescriptor->hotSpotTileWidth  * qtvrParams->tilesH);
        panoSampleData->hotSpotSizeY        = EndianU32_NtoB(panoDescriptor->hotSpotTileHeight * qtvrParams->tilesV); 
        panoSampleData->hotSpotNumFramesX   = EndianU16_NtoB(qtvrParams->tilesH);
        panoSampleData->hotSpotNumFramesY   = EndianU16_NtoB(qtvrParams->tilesV);
            
        if (panoDescriptor->versionToCreate == kQTVRCylindricalVersion2h) {
            panoSampleData->panoType        = EndianU32_NtoB(kQTVRHorizontalCylinder);  /* non-rotated pano */
            //panoSampleData->flags         = EndianU32_NtoB((qtvrParams->wraps ? kQTVRPanoFlagAlwaysWrap : 0L) | kQTVRPanoFlagHorizontal); // include for compatibility with QT 4.0
            panoSampleData->flags           = EndianU32_NtoB(kQTVRPanoFlagHorizontal); // include for compatibility with QT 4.0
        }
        else {
            panoSampleData->panoType        = EndianU32_NtoB(kQTVRVerticalCylinder);    /* rotated pano */
            //panoSampleData->flags         = EndianU32_NtoB(qtvrParams->wraps ? kQTVRPanoFlagAlwaysWrap : 0L);
            panoSampleData->flags           = EndianU32_NtoB(0L);
        }
        panoSampleData->reserved2           = EndianU32_NtoB(0L);
    }
 
    else {  /* We're building a cubic panorama: build backward-compatible pano sample data */
        panoSampleData->minPan              = EndianF32_NtoB(qtvrParams->minPan);
        panoSampleData->maxPan              = EndianF32_NtoB(qtvrParams->maxPan);
        panoSampleData->minTilt             = EndianF32_NtoB(qtvrParams->minTilt        < -45.0f ? -45.0f : qtvrParams->minTilt);
        panoSampleData->maxTilt             = EndianF32_NtoB(qtvrParams->maxTilt        >  45.0f ?  45.0f : qtvrParams->maxTilt);
        panoSampleData->minFieldOfView      = EndianF32_NtoB(qtvrParams->minFieldOfView);
        panoSampleData->maxFieldOfView      = EndianF32_NtoB(qtvrParams->maxFieldOfView >  90.0f ?  90.0f : qtvrParams->maxFieldOfView);
        panoSampleData->defaultPan          = EndianF32_NtoB(qtvrParams->defaultPan);
        panoSampleData->defaultTilt         = EndianF32_NtoB(qtvrParams->defaultTilt);
        panoSampleData->defaultFieldOfView  = EndianF32_NtoB(qtvrParams->defaultFieldOfView);
 
        panoSampleData->imageSizeX          = EndianU32_NtoB(panoDescriptor->imageTileWidth  * qtvrParams->tilesH * 4);
        panoSampleData->imageSizeY          = EndianU32_NtoB(panoDescriptor->imageTileHeight * qtvrParams->tilesV);
        panoSampleData->imageNumFramesX     = EndianU16_NtoB(qtvrParams->tilesH * 4);
        panoSampleData->imageNumFramesY     = EndianU16_NtoB(qtvrParams->tilesV);
        
        panoSampleData->hotSpotSizeX        = EndianU32_NtoB(panoDescriptor->hotSpotTileWidth  * qtvrParams->tilesH * 4);
        panoSampleData->hotSpotSizeY        = EndianU32_NtoB(panoDescriptor->hotSpotTileHeight * qtvrParams->tilesV); 
        panoSampleData->hotSpotNumFramesX   = EndianU16_NtoB(qtvrParams->tilesH * 4);
        panoSampleData->hotSpotNumFramesY   = EndianU16_NtoB(qtvrParams->tilesV);
            
        /* Set the horizontal flag so that pre-cubic QTVR engine can at least display the four sides of the cube */
        panoSampleData->flags               = EndianU32_NtoB(kQTVRPanoFlagHorizontal);
        panoSampleData->panoType            = EndianU32_NtoB(kQTVRCube);
        panoSampleData->reserved2           = EndianU32_NtoB(0L);
    }
}
 
 
/********************************************************************************
 * TileGWorldsToNewTrack
 ********************************************************************************/
 
static OSErr
TileGWorldsToNewTrack(
    long        numGWs,                 /* the number of GWorlds */
    GWorldPtr   *srcGWs,                /* Source GWorlds - in standard order */
    long        overlapTiles,           /* Whether the tiles should overlap or not */
    CodecType   tileCodec,
    CodecQ      tileQuality,
    long        tilesH,
    long        tilesV,
    long        tileDepth,
    Movie       dstMovie,               /* to be tiled and added to this movie. */
    TimeValue   dstSequenceDuration,    /* duration of the whole sequence */
    long        *pDstTrackWidth,        /* returned track width,  in integral units */
    long        *pDstTrackHeight,       /* returned track height, in integral units */
    Track       *pDstTrack              /* the track added to the dstMovie */
)
{
    PixMapHandle                pm;
    Rect                        tileRect;
    Track                       tileTrack       = NULL;
    Media                       tileMedia       = NULL;
    ImageDescriptionHandle      tileImageDesc   = NULL;
    Handle                      tileData        = NULL;
    Ptr                         tileDataPtr     = NULL;
    long                        tileFrameSize;
    TimeValue                   dstTileDuration = dstSequenceDuration / (numGWs * tilesH * tilesV);
    OSErr                       err             = noErr;
    long                        i, j, k;
    #if USE_TILE_DISPLAY_WINDOW
        WindowRef               tileWindow;
    #endif /* USE_TILE_DISPLAY_WINDOW */
    
    /* Determine tile size */
    #if ACCESSOR_CALLS_ARE_FUNCTIONS
        GetPortBounds(srcGWs[0], &tileRect);
    #else /* ACCESSOR_CALLS_ARE_FUNCTIONS */
        tileRect = srcGWs[0]->portRect;
    #endif /* ACCESSOR_CALLS_ARE_FUNCTIONS */
    MacOffsetRect(&tileRect, -tileRect.left, -tileRect.top);
    if (overlapTiles) {
        tileRect.right  += tilesH - 1;  /* Adjust for overlap at seams */
        tileRect.bottom += tilesV - 1;  /* Adjust for overlap at seams */
    }
    if ((tileRect.right % tilesH != 0) || (tileRect.bottom % tilesV != 0)) {    /* Check for evenly divisible tiles */
        err = paramErr;     /* Oops: tiles are not evenly divisible */
        goto bail;
    }
    tileRect.right  /= tilesH;
    tileRect.bottom /= tilesV;
    if (pDstTrackWidth  != NULL)    *pDstTrackWidth  = tileRect.right;
    if (pDstTrackHeight != NULL)    *pDstTrackHeight = tileRect.bottom;
    
    /* Create tile view, if desired */
    #if USE_TILE_DISPLAY_WINDOW
        MacOffsetRect(&tileRect, 20, 100);
        tileWindow = NewCWindow(NULL, &tileRect, "\pUncompressed tiles", true, noGrowDocProc, (WindowPtr)-1L, true, 0);
        MacOffsetRect(&tileRect, -20, -100);
    #endif /* USE_TILE_DISPLAY_WINDOW */
    
    /* Create the tile movie track and media */
    tileTrack = NewMovieTrack(dstMovie, (long)tileRect.right << 16, (long)tileRect.bottom << 16, kNoVolume);    
    tileMedia = NewTrackMedia(tileTrack, VideoMediaType, kQTVRStandardTimeScale, NULL, 0);
    if ((tileTrack == NULL) || (tileMedia == NULL))
        goto bail;
    if (pDstTrack != NULL)  *pDstTrack = tileTrack;
    
    /*
     * dice the picture into pieces, compress them, and add the compressed tiles as video samples to the movie
     */
 
    /* create an image description handle; this is resized and filled in below by FCompressImage */
    tileImageDesc = (ImageDescriptionHandle)MyNewHandleClear(4);
    if (tileImageDesc == NULL)
        goto bail;
 
    pm = GetGWorldPixMap(srcGWs[0]);
    LockPixels(pm);
    err = GetMaxCompressionSize(pm, &tileRect, tileDepth, tileQuality, tileCodec, (CompressorComponent)anyCodec, &tileFrameSize);
    UnlockPixels(pm);
    if (err != noErr)
        goto bail;
    tileData = MyNewHandle(tileFrameSize);
    if (tileData == NULL)
        goto bail;
    HLock(tileData);
    tileDataPtr = *tileData;
    BeginMediaEdits(tileMedia);
            
    /* tile, compress, and write tiles */
    for (k = 0; k < numGWs; k++) {
        pm = GetGWorldPixMap(srcGWs[k]);
        for (j = 0; j < tilesV; j++) {
            for (i = 0; i < tilesH; i++) {
                Rect r;
                if (overlapTiles) {
                    r.left = i * (tileRect.right  - 1);
                    r.top  = j * (tileRect.bottom - 1);
                }
                else {
                    r.left = i * tileRect.right;
                    r.top  = j * tileRect.bottom;
                }
                r.right  = r.left + tileRect.right;
                r.bottom = r.top  + tileRect.bottom;
                
                LockPixels(pm);
 
                /* Draw the picture into the window we've created */
                #if USE_TILE_DISPLAY_WINDOW
                    if (tileWindow != NULL) {
                        GrafPtr savePort;
                        GetPort(&savePort);
                    #if TARGET_API_MAC_CARBON
                    {   Rect pr;
                        CGrafPtr tilePort = GetWindowPort(tileWindow);
                        MacSetPort(tilePort);
                        CopyBits((BitMap*)(*(pm)), (BitMap*)*GetPortPixMap(tilePort),
                            &r, GetWindowPortBounds(tileWindow, &pr), ditherCopy, NULL
                        );
                        if (QDIsPortBuffered(tilePort))
                            QDFlushPortBuffer(tilePort, NULL);
                    }
                    #else /* !TARGET_API_MAC_CARBON */
                        MacSetPort(tileWindow);
                        CopyBits((BitMap*)(*(pm)), &(tileWindow->portBits), &r, &tileWindow->portRect, ditherCopy, NULL);
                    #endif /* !TARGET_API_MAC_CARBON */
                        MacSetPort(savePort);
                    }
                #endif /* USE_TILE_DISPLAY_WINDOW */
 
                err = FCompressImage(pm, &r, tileDepth, tileQuality, tileCodec, (CompressorComponent)anyCodec, NULL, 0, 0, NULL, NULL, tileImageDesc, tileDataPtr);
                UnlockPixels(pm);
                if (err != noErr)
                    goto bail;
                err = AddMediaSample(tileMedia, tileData, 0, (**tileImageDesc).dataSize, dstTileDuration, (SampleDescriptionHandle)tileImageDesc, 1L, 0, NULL);
                if (err != noErr)
                    goto bail;
            }
        }       
    }
    
    HUnlock(tileData);
    
    EndMediaEdits(tileMedia);
    
    
    /* add the media to the track, at time 0 */
    err = InsertMediaIntoTrack(tileTrack, 0, 0, GetMediaDuration(tileMedia), fixed1);
    if (err != noErr)
        goto bail;
 
    /* a panorama image track should always be disabled */
    SetTrackEnabled(tileTrack, false);
    
bail:
    if (tileData      != NULL)  DisposeHandle(tileData);
    if (tileImageDesc != NULL)  DisposeHandle((Handle)tileImageDesc);
    #if USE_TILE_DISPLAY_WINDOW
        if (tileWindow!= NULL)  DisposeWindow(tileWindow);
    #endif /* USE_TILE_DISPLAY_WINDOW */
 
    return(err);
}
 
 
/*******************************************************************************
 * TilePictsToNewTrack
 *******************************************************************************/
 
static OSErr
TilePictsToNewTrack(
    long            numPicts,
    FSSpecHandle    srcPictSpecs,
    long            overlapTiles,           /* Whether the tiles should overlap or not */
    CodecType       tileCodec,
    CodecQ          tileQuality,
    long            tilesH,
    long            tilesV,
    short           tileDepth,
    Movie           dstMovie,               /* to be tiled and added to this movie. */
    TimeValue       dstSequenceDuration,    /* duration of the whole sequence */
    long            *pDstTrackWidth,        /* returned track width,  in integral units */
    long            *pDstTrackHeight,       /* returned track height, in integral units */
    Track           *pDstTrack              /* the track added to the dstMovie */
)
{
    GraphicsImportComponent     theImporter     = NULL;
    Rect                        pictRect, tileRect, drawRect;
    long                        tileWidth, tileHeight;
    GWorldPtr                   gw              = NULL;
    PixMapHandle                pm              = NULL;
    Track                       tileTrack       = NULL;
    Media                       tileMedia       = NULL;
    ImageDescriptionHandle      tileImageDesc   = NULL;
    Handle                      tileData        = NULL;
    Ptr                         tileDataPtr     = NULL;
    long                        tileFrameSize;
    TimeValue                   dstTileDuration = dstSequenceDuration / (numPicts * tilesH * tilesV);
    OSErr                       err             = noErr;
    long                        i, j, k;
    FSSpec                      fsSpec;
    #if USE_TILE_DISPLAY_WINDOW
        WindowRef               tileWindow;
    #endif /* USE_TILE_DISPLAY_WINDOW */
    
    if ((srcPictSpecs == NULL) || (srcPictSpecs[0] == NULL) || (srcPictSpecs[0][0].name[0] == 0)) {
        err = paramErr;
        goto bail;
    }
 
    /* Create a GWorld to draw the uncompressed tiles into */
    fsSpec = srcPictSpecs[0][0];
    err = GetGraphicsImporterForFile(&fsSpec, &theImporter);
    if (err != noErr)
        goto bail;
    err = GraphicsImportGetBoundsRect(theImporter, &pictRect);
    if (err != noErr)
        goto bail;
    CloseComponent(theImporter);
    theImporter = NULL;
    tileWidth   = pictRect.right  - pictRect.left;
    tileHeight  = pictRect.bottom - pictRect.top;
    if (overlapTiles) {
        tileWidth  += tilesH - 1;   /* Adjust for overlap at seams */
        tileHeight += tilesV - 1;   /* Adjust for overlap at seams */
    }
    if ((tileWidth % tilesH != 0) || (tileHeight % tilesV != 0)) {  /* Check for evenly divisible tiles */
        err = paramErr;     /* Oops: tiles are not evenly divisible */
        goto bail;
    }
    tileWidth  /= tilesH;
    tileHeight /= tilesV;
    if (pDstTrackWidth  != NULL)    *pDstTrackWidth  = tileWidth;
    if (pDstTrackHeight != NULL)    *pDstTrackHeight = tileHeight;
    MacSetRect(&tileRect, 0, 0, tileWidth, tileHeight);
    err = NewGWorld(&gw, tileDepth, &tileRect, NULL, NULL, 0L);
    if (err != noErr)
        goto bail;
 
    
    /* Debugging or progress window */
    #if USE_TILE_DISPLAY_WINDOW
        MacSetRect(&tileRect, 0, 0, tileWidth, tileHeight);
        MacOffsetRect(&tileRect, 20, 100);
        tileWindow = NewCWindow(NULL, &tileRect, "\pUncompressed tiles", true, noGrowDocProc, (WindowPtr)-1L, true, 0);
        MacOffsetRect(&tileRect, -20, -100);
    #endif /* USE_TILE_DISPLAY_WINDOW */
 
    tileTrack = NewMovieTrack(dstMovie, tileWidth << 16, tileHeight << 16, kNoVolume);  
    tileMedia = NewTrackMedia(tileTrack, VideoMediaType, kQTVRStandardTimeScale, NULL, 0);
    if ((tileTrack == NULL) || (tileMedia == NULL))
        goto bail;
    if (pDstTrack != NULL)  *pDstTrack = tileTrack;
    
    /* Dice the picture into pieces, compress them, and add the compressed tiles as video samples to the movie */
    tileImageDesc = (ImageDescriptionHandle)MyNewHandleClear(4); /* this is resized and filled in below by FCompressImage */
    if (tileImageDesc == NULL)
        goto bail;
    pm = GetGWorldPixMap(gw);
    LockPixels(pm);
    err = GetMaxCompressionSize(pm, &tileRect, tileDepth, tileQuality, tileCodec, (CompressorComponent)anyCodec, &tileFrameSize);
    UnlockPixels(pm);
    if (err != noErr)
        goto bail;
    tileData = MyNewHandle(tileFrameSize);
    if (tileData == NULL)
        goto bail;
    BeginMediaEdits(tileMedia);
    
    for (k = 0; k < numPicts; k++) {
        fsSpec = srcPictSpecs[0][k];
        err = GetGraphicsImporterForFile(&fsSpec, &theImporter);
        if (err != noErr)
            goto bail;
        for (j = 0; j < tilesV; j++) {
            for (i = 0; i < tilesH; i++) {
                /* Offset to the proper subrect for this tile */
                drawRect = pictRect;
                if (overlapTiles)   MacOffsetRect(&drawRect, -i * (tileWidth-1), -j * (tileHeight-1)); 
                else                MacOffsetRect(&drawRect, -i *  tileWidth,    -j *  tileHeight); 
                
                /* Draw the picture into the window we've created */
                #if USE_TILE_DISPLAY_WINDOW
                    if (tileWindow != NULL) {
                        GraphicsImportSetGWorld(theImporter, (CGrafPtr)tileWindow, NULL);
                        GraphicsImportSetBoundsRect(theImporter, &drawRect);
                        GraphicsImportDraw(theImporter);
                    }
                #endif /* USE_TILE_DISPLAY_WINDOW */
 
                /* Draw the picture into the uncompressed tile GWorld */
                GraphicsImportSetGWorld(theImporter, gw, NULL);
                GraphicsImportSetBoundsRect(theImporter, &drawRect);
                GraphicsImportDraw(theImporter);
                
                /* Compress the tile and add it tile to the movie */
                HLock(tileData);
                tileDataPtr = *tileData;
                LockPixels(pm);
                err = FCompressImage(pm, &tileRect, tileDepth, tileQuality, tileCodec, (CompressorComponent)anyCodec,
                    NULL, 0, 0, NULL, NULL, tileImageDesc, tileDataPtr
                );
                UnlockPixels(pm);
                if (err != noErr)
                    goto bail;
                err = AddMediaSample(tileMedia, tileData, 0, (**tileImageDesc).dataSize, dstTileDuration,
                    (SampleDescriptionHandle)tileImageDesc, 1L, 0, NULL
                );
                if (err != noErr)
                    goto bail;
            }
        }
        CloseComponent(theImporter);
        theImporter = NULL;
    }
    
    EndMediaEdits(tileMedia);
    DisposeHandle(tileData);
    tileData = NULL;
    
    /* add the media to the track, at time 0 */
    err = InsertMediaIntoTrack(tileTrack, 0, 0, GetMediaDuration(tileMedia), fixed1);
    if (err != noErr)
        goto bail;
 
    /* a panorama image track should always be disabled */
    SetTrackEnabled(tileTrack, false);
    
bail:
    if (theImporter   != NULL)  CloseComponent(theImporter);
    if (gw            != NULL)  DisposeGWorld(gw);
    if (tileData      != NULL)  DisposeHandle(tileData);
    if (tileImageDesc != NULL)  DisposeHandle((Handle)tileImageDesc);
    #if USE_TILE_DISPLAY_WINDOW
        if (tileWindow!= NULL)  DisposeWindow(tileWindow);
    #endif /* USE_TILE_DISPLAY_WINDOW */
 
    return(err);
}
 
 
/********************************************************************************
 * FlattenQTVRMovie
 ********************************************************************************/
 
static OSErr
FlattenQTVRMovie(Movie srcMovie, unsigned short flattenFlags, unsigned short previewResolution, FSSpec *dstMovieSpec)
{
    ComponentDescription desc;
    Component flattener;
    ComponentInstance qtvrExport = nil;
    OSErr err;
 
    desc.componentType          = MovieExportType;
    desc.componentSubType       = MovieFileType;
    desc.componentManufacturer  = QTVRFlattenerType; 
    desc.componentFlags         = 0;
    desc.componentFlagsMask     = 0;
    flattener = FindNextComponent(NULL, &desc);
    
    /* Use the specialized QuickTimeVR flattener if it is available */
    if (flattener && ((qtvrExport = OpenComponent(flattener)) != NULL)) {
        QTAtomContainer exportData;
        QTAtom          parent;
        Boolean         makePreview = (flattenFlags & kVRMakePano_GeneratePreview)      ? true : false;
        Boolean         blur        = (flattenFlags & kVRMakePano_BlurGeneratedPreview) ? true : false;
        err = QTNewAtomContainer(&exportData);
        err = QTInsertChild(exportData, kParentAtomIsContainer, kQTVRFlattenerSettingsParentAtomType, 1, 0, 0,               nil,                &parent);
        err = QTInsertChild(exportData, parent,                 kQTVRFlattenerCreatePreviewAtomType,  1, 0, sizeof(Boolean), &makePreview,       nil);
        if (makePreview) {
            err = QTInsertChild(exportData, parent,             kQTVRFlattenerPreviewResAtomType,     1, 0, sizeof(short),   &previewResolution, nil);
            err = QTInsertChild(exportData, parent,             kQTVRFlattenerBlurPreviewAtomType,    1, 0, sizeof(Boolean), &blur,              nil);
        }
        MovieExportSetSettingsFromAtomContainer(qtvrExport, exportData);
        QTDisposeAtomContainer(exportData);
 
        DeleteMovieFile(dstMovieSpec);  /* I don't know why, but sometimes this is necessary */
        err = MovieExportToFile(qtvrExport, dstMovieSpec, srcMovie, NULL, 0, 0);
        CloseComponent(qtvrExport);
    }
    else {  /* Use the generic movie flattener */
        Movie dstMovie;
        SetMovieProgressProc(srcMovie, (MovieProgressUPP)-1, 0L);
        dstMovie = FlattenMovieData(srcMovie,
            flattenDontInterleaveFlatten | flattenAddMovieToDataFork | flattenForceMovieResourceBeforeMovieData,
            dstMovieSpec, FOUR_CHAR_CODE('TVOD'),
            smSystemScript, createMovieFileDeleteCurFile | createMovieFileDontCreateResFile
        );
        if ((dstMovie == NULL) || ((err = GetMoviesError()) == fnOpnErr)) {
            DeleteMovieFile(dstMovieSpec);  /* I don't know why, but sometimes this is necessary */
            dstMovie = FlattenMovieData(srcMovie,
                flattenDontInterleaveFlatten | flattenAddMovieToDataFork | flattenForceMovieResourceBeforeMovieData,
                dstMovieSpec, FOUR_CHAR_CODE('TVOD'),
                smSystemScript, createMovieFileDeleteCurFile | createMovieFileDontCreateResFile
            );
            if ((dstMovie == NULL) && ((err = GetMoviesError()) == noErr))
                err = writErr; /* Something is wrong */
        }
        if (dstMovie != NULL)
            DisposeMovie(dstMovie);
    }
    return(err);
}
 
 
#pragma mark -
/********************************************************************************
 * CreateVRWorld
 *
 *  Create a VR world atom container and add the basic required atoms to it
 *  Also, create a node information atom container and add a node header atom to it.
 *  Return both atom containers.
 *
 *  The caller is responsible for disposing of the VR world and the node information atom
 *  (by calling QTDisposeAtomContainer).
 *
 *  This function assumes that the scene described by the VR world contains
 *  a single node whose type is specified by the theNodeType parameter.
 ********************************************************************************/
 
static OSErr
CreateVRWorld(
    OSType                  theNodeType,
    const VRMakeQTVRParams  *qtvrParams,
    QTAtomContainer         *theVRWorld,
    QTAtomContainer         *theNodeInfo
)
{
    QTAtomContainer         myVRWorld           = NULL;
    QTAtomContainer         myNodeInfo          = NULL;
    QTVRWorldHeaderAtom     myVRWorldHeaderAtom;
    QTAtom                  myImagingParentAtom;
    QTAtom                  myNodeParentAtom;
    QTAtom                  myNodeAtom;
    QTVRPanoImagingAtom     myPanoImagingAtom;
    QTVRNodeLocationAtom    myNodeLocationAtom;
    QTVRNodeHeaderAtom      myNodeHeaderAtom;
    UInt32                  myIndex;
    OSErr                   err                 = noErr;
 
    /* Create a VR world atom container */
    err = QTNewAtomContainer(&myVRWorld);
    if (err != noErr)
        goto bail;
 
    /* Add a VR world header atom to the VR world */
    myVRWorldHeaderAtom.majorVersion = EndianU16_NtoB(kQTVRMajorVersion);
    myVRWorldHeaderAtom.minorVersion = EndianU16_NtoB(kQTVRMinorVersion);
 
    /* Insert the scene name string, if we have one; if not, set nameAtomID to 0 */
    if (qtvrParams->sceneName) {
        QTAtomID            myID;
        
        err = AddCStringToAtomContainer(myVRWorld, kParentAtomIsContainer, qtvrParams->sceneName, &myID);
        myVRWorldHeaderAtom.nameAtomID  = EndianU32_NtoB(myID);
    } else {
        myVRWorldHeaderAtom.nameAtomID  = EndianU32_NtoB(0L);
    }
    
    myVRWorldHeaderAtom.defaultNodeID   = EndianU32_NtoB(kDefaultNodeID);
    myVRWorldHeaderAtom.vrWorldFlags    = EndianU32_NtoB(0L);
    myVRWorldHeaderAtom.reserved1       = EndianU32_NtoB(0L);
    myVRWorldHeaderAtom.reserved2       = EndianU32_NtoB(0L);
 
    /* Add the atom to the atom container (the VR world) */
    err = QTInsertChild(myVRWorld, kParentAtomIsContainer, kQTVRWorldHeaderAtomType, 1, 1, sizeof(QTVRWorldHeaderAtom), &myVRWorldHeaderAtom, NULL);
    if (err != noErr)
        goto bail;
        
    /* Add an imaging parent atom to the VR world and insert imaging atoms into it
     *
     * Imaging atoms describe the default imaging characteristics for the different types of nodes in the scene;
     * currently, only the panorama imaging atoms are defined, so we'll include those (even in object movies)
     */
 
    err = QTInsertChild(myVRWorld, kParentAtomIsContainer, kQTVRImagingParentAtomType, 1, 1, 0, NULL, &myImagingParentAtom);
    if (err != noErr)
        goto bail;
        
    /* Fill in the fields of the panorama imaging atom structure */
    myPanoImagingAtom.majorVersion      = EndianU16_NtoB(kQTVRMajorVersion);
    myPanoImagingAtom.minorVersion      = EndianU16_NtoB(kQTVRMinorVersion);
    myPanoImagingAtom.correction        = EndianU32_NtoB(kQTVRFullCorrection);
    myPanoImagingAtom.imagingValidFlags = EndianU32_NtoB(kQTVRValidCorrection | kQTVRValidQuality | kQTVRValidDirectDraw);
    myPanoImagingAtom.reserved1         = EndianU32_NtoB(0L);
    myPanoImagingAtom.reserved2         = EndianU32_NtoB(0L);
    for (myIndex = 0; myIndex < 6; myIndex++)
        myPanoImagingAtom.imagingProperties[myIndex] = EndianU32_NtoB(0L);
    
    /* Add a panorama imaging atom for kQTVRMotion state */
    myPanoImagingAtom.quality       = EndianU32_NtoB(qtvrParams->dynamicQuality);
    myPanoImagingAtom.directDraw    = EndianU32_NtoB(true);
    myPanoImagingAtom.imagingMode   = EndianU32_NtoB(kQTVRMotion);
    err = QTInsertChild(myVRWorld, myImagingParentAtom, kQTVRPanoImagingAtomType, 0, 0, sizeof(QTVRPanoImagingAtom), &myPanoImagingAtom, NULL);
    if (err != noErr)
        goto bail;
        
    /* Add a panorama imaging atom for kQTVRStatic state */
    myPanoImagingAtom.quality       = EndianU32_NtoB(qtvrParams->staticQuality);
    myPanoImagingAtom.directDraw    = EndianU32_NtoB(false);
    myPanoImagingAtom.imagingMode   = EndianU32_NtoB(kQTVRStatic);
    err = QTInsertChild(myVRWorld, myImagingParentAtom, kQTVRPanoImagingAtomType, 0, 0, sizeof(QTVRPanoImagingAtom), &myPanoImagingAtom, NULL);
    if (err != noErr)
        goto bail;
        
    /* Add a node parent atom to the VR world and insert node ID atoms into it */
    err = QTInsertChild(myVRWorld, kParentAtomIsContainer, kQTVRNodeParentAtomType, 1, 1, 0, NULL, &myNodeParentAtom);
    if (err != noErr)
        goto bail;
        
    /* Add a node ID atom */
    err = QTInsertChild(myVRWorld, myNodeParentAtom, kQTVRNodeIDAtomType, kDefaultNodeID, 0, 0, NULL, &myNodeAtom);
    if (err != noErr)
        goto bail;
    
    /* Add a single node location atom to the node ID atom */
    myNodeLocationAtom.majorVersion     = EndianU16_NtoB(kQTVRMajorVersion);
    myNodeLocationAtom.minorVersion     = EndianU16_NtoB(kQTVRMinorVersion);
    myNodeLocationAtom.nodeType         = EndianU32_NtoB(theNodeType);
    myNodeLocationAtom.locationFlags    = EndianU32_NtoB(kQTVRSameFile);
    myNodeLocationAtom.locationData     = EndianU32_NtoB(0);
    myNodeLocationAtom.reserved1        = EndianU32_NtoB(0);
    myNodeLocationAtom.reserved2        = EndianU32_NtoB(0);
    
    /* Insert the node location atom into the node ID atom */
    err = QTInsertChild(myVRWorld, myNodeAtom, kQTVRNodeLocationAtomType, 1, 1, sizeof(QTVRNodeLocationAtom), &myNodeLocationAtom, NULL);
    if (err != noErr)
        goto bail;
    
    /*  Create a node information atom container and add a node header atom to it */
    
    err = QTNewAtomContainer(&myNodeInfo);
    if (err != noErr)
        goto bail;
 
    myNodeHeaderAtom.majorVersion   = EndianU16_NtoB(kQTVRMajorVersion);
    myNodeHeaderAtom.minorVersion   = EndianU16_NtoB(kQTVRMinorVersion);
    myNodeHeaderAtom.nodeType       = EndianU32_NtoB(theNodeType);
    myNodeHeaderAtom.nodeID         = EndianU32_NtoB(kDefaultNodeID);
    myNodeHeaderAtom.commentAtomID  = EndianU32_NtoB(0L);
    myNodeHeaderAtom.reserved1      = EndianU32_NtoB(0L);
    myNodeHeaderAtom.reserved2      = EndianU32_NtoB(0L);
    
    /* Insert the node name string into the node info atom container */
    if (qtvrParams->nodeName) {
        QTAtomID            myID;
        
        err = AddCStringToAtomContainer(myNodeInfo, kParentAtomIsContainer, qtvrParams->nodeName, &myID);
        myNodeHeaderAtom.nameAtomID = EndianU32_NtoB(myID);
    } else {
        myNodeHeaderAtom.nameAtomID = EndianU32_NtoB(0L);
    }
    
    /* Insert the node header atom into the node info atom container */
    err = QTInsertChild(myNodeInfo, kParentAtomIsContainer, kQTVRNodeHeaderAtomType, 1, 1, sizeof(QTVRNodeHeaderAtom), &myNodeHeaderAtom, NULL);
    if (err != noErr)
        goto bail;
    
bail:
    /* return the atom containers that we've created and configured here */
    *theVRWorld     = myVRWorld;
    *theNodeInfo    = myNodeInfo;
    
    return(err);
}
 
 
/*******************************************************************************
 * CreateQTVRTrack
 *******************************************************************************/
 
static OSErr
CreateQTVRTrack(const VRMakeQTVRParams *qtvrParams, TimeValue dstTrackDuration, Movie movie, Track *qtvrTrack)
{
    Track                           myQTVRTrack;
    Media                           myQTVRMedia;
    QTAtomContainer                 myVRWorld           = NULL;
    QTAtomContainer                 myNodeInfo          = NULL;
    QTVRSampleDescriptionHandle     myQTVRDesc          = NULL;
    long                            mySize;
    OSErr                           err;
 
    myQTVRTrack = NewMovieTrack(movie, qtvrParams->windowWidth << 16, qtvrParams->windowHeight << 16, kFullVolume);
    myQTVRMedia = NewTrackMedia(myQTVRTrack, kQTVRQTVRType, kQTVRStandardTimeScale, NULL, 0);
    if ((myQTVRTrack == NULL) || (myQTVRMedia == NULL)) {
        err = GetMoviesError();
        goto bail;
    }
    
    SetMovieTimeScale(movie, kQTVRStandardTimeScale);
 
    /* create a VR world atom container and a node information atom container;
     * remember that the VR world becomes part of the QTVR sample description,
     * and the node information atom container becomes the media sample data
     */
    err = CreateVRWorld(kQTVRPanoramaType, qtvrParams, &myVRWorld, &myNodeInfo);
    if ((err != noErr) || (myVRWorld == NULL) || (myNodeInfo == NULL))
        goto bail;
    
    /* Add hot spot information if given */
    AddAllHotSpots2x0(myNodeInfo, qtvrParams->hotSpots);
    
    /* create a QTVR sample description */
    mySize = sizeof(QTVRSampleDescription) + GetHandleSize((Handle)myVRWorld) - sizeof(long);
    myQTVRDesc = (QTVRSampleDescriptionHandle)MyNewHandleClear(mySize);
    if (myQTVRDesc == NULL)
        goto bail;
        
    (**myQTVRDesc).descSize     = mySize;
    (**myQTVRDesc).descType     = kQTVRQTVRType;
    (**myQTVRDesc).reserved1    = 0;
    (**myQTVRDesc).reserved2    = 0;
    (**myQTVRDesc).dataRefIndex = 0;
 
    /* copy the VR world atom container into the data field of the QTVR sample description */
    BlockMoveData(*((Handle)myVRWorld), &((**myQTVRDesc).data), GetHandleSize((Handle)myVRWorld));
    
    /* Create the media sample and add it to the QTVR track */
    BeginMediaEdits(myQTVRMedia);
    err = AddMediaSample(myQTVRMedia, (Handle)myNodeInfo, 0, GetHandleSize((Handle)myNodeInfo), dstTrackDuration, (SampleDescriptionHandle)myQTVRDesc, 1, 0, NULL);
    if (err != noErr)
        goto bail;
    EndMediaEdits(myQTVRMedia);
    err = InsertMediaIntoTrack(myQTVRTrack, 0, 0, dstTrackDuration, fixed1);
    
bail:
    if (myQTVRDesc  != NULL)    DisposeHandle((Handle)myQTVRDesc);
    if (myVRWorld   != NULL)    QTDisposeAtomContainer(myVRWorld);
    if (myNodeInfo  != NULL)    QTDisposeAtomContainer(myNodeInfo);
    *qtvrTrack = myQTVRTrack;
    return(err);
}
 
 
/*******************************************************************************
 * CreatePanoMediaSample
 *******************************************************************************/
 
static OSErr
CreatePanoMediaSample(
    const VRMakeQTVRParams      *qtvrParams,
    const MyPanoTrackDescriptor *panoDescriptor,
    QTAtomContainer             *thePanoSample
)
{
    OSErr               err;
    QTAtomContainer     panoSample;
    QTVRPanoSampleAtom  myPanoSampleData;
 
 
    /* Create a pano media sample container */
    err = QTNewAtomContainer(&panoSample);
    if (err != noErr)
        goto bail;
    
    /* Insert the pano sample atom into the pano sample atom container */
    SetPanoSampleData(qtvrParams, panoDescriptor, &myPanoSampleData);
    err = QTInsertChild(panoSample, kParentAtomIsContainer, kQTVRPanoSampleDataAtomType, 1, 1, sizeof(QTVRPanoSampleAtom), &myPanoSampleData, NULL);
    if (err != noErr)
        goto bail;
 
 
    /* Attach a fast start preview track, if applicable */
    if (panoDescriptor->fastStartRefTrackIndex) {
        QTVRTrackRefEntry trackRefEntry;
        trackRefEntry.trackRefType    = EndianU32_NtoB(kQTVRImageTrackRefType);
        trackRefEntry.trackResolution = EndianU16_NtoB(kQTVRPreviewTrackRes);
        trackRefEntry.trackRefIndex   = EndianU32_NtoB(panoDescriptor->fastStartRefTrackIndex);
        err = QTInsertChild(panoSample, kParentAtomIsContainer,
            kQTVRTrackRefArrayAtomType, 1, 1, sizeof(QTVRTrackRefEntry), &trackRefEntry, nil);
    }
 
 
    /* Set the view and face orientation for cubic panoramas;
     * this atom will be ignored in pre-cubic versions of QTVR
     */
    if (panoDescriptor->versionToCreate == kQTVRCubicVersion1) {
 
        /* Specify the default and media-constrained view parameters */
        QTVRCubicViewAtom       myCubicViewAtom;
        SetCubicViewData(qtvrParams, &myCubicViewAtom);
        err = QTInsertChild(panoSample, kParentAtomIsContainer, kQTVRCubicViewAtomType, 1, 1, sizeof(QTVRCubicViewAtom), &myCubicViewAtom, NULL);
        if (err != noErr)
            goto bail;
 
        /* Specify the orientation of each face */
        #define FORCE_CUBIC_FACE_DATA
        #ifndef FORCE_CUBIC_FACE_DATA
            if (qtvrParams->tilesH * qtvrParams->tilesV != 1)       /* We use the default if the faces are not subtiled */
        #endif /* FORCE_CUBIC_FACE_DATA */
        {
            QTVRCubicFaceData   *cubicData;
            cubicData = (QTVRCubicFaceData*)NewPtr(sizeof(QTVRCubicFaceData) * 6 * qtvrParams->tilesH * qtvrParams->tilesV);
            SetCubicFaceData(qtvrParams, cubicData);
            err = QTInsertChild(panoSample, kParentAtomIsContainer,
                kQTVRCubicFaceDataAtomType, 1, 1, sizeof(QTVRCubicFaceData) * 6 * qtvrParams->tilesH * qtvrParams->tilesV, cubicData, nil
            );
            DisposePtr((Ptr)cubicData);
            if (err != noErr)
                goto bail;
        }
    }
 
 
    /* add pan constraint, tilt constraint, and field-of-view constraint atoms here
     * [left as an exercise for the reader; here's the basic idea:]
     */
    if (false) {
        QTVRAngleRangeAtom      myPanRangeAtom;
        
        myPanRangeAtom.minimumAngle = EndianF32_NtoB(  0.0);
        myPanRangeAtom.maximumAngle = EndianF32_NtoB(270.0);
        
        err = QTInsertChild(panoSample, kParentAtomIsContainer, kQTVRPanConstraintAtomType, 1, 1, sizeof(QTVRAngleRangeAtom), &myPanRangeAtom, NULL);
    }
    
bail:
    *thePanoSample = (err == noErr) ? panoSample : NULL;
    return(err);
}
 
 
/*******************************************************************************
 * InitMyPanoDesc
 *
 *  Create a (version 2.0) panorama track, appending the given video tracks.
 *******************************************************************************/
 
static void
InitMyPanoDesc(MyPanoTrackDescriptor *myPanoDesc, long versionToCreate)
{
    myPanoDesc->versionToCreate         = versionToCreate;
    myPanoDesc->imageTileWidth          = 0;
    myPanoDesc->imageTileHeight         = 0;
    myPanoDesc->imageRefTrackIndex      = 0;
    myPanoDesc->hotSpotTileWidth        = 0;
    myPanoDesc->hotSpotTileHeight       = 0;
    myPanoDesc->hotSpotRefTrackIndex    = 0;
    myPanoDesc->fastStartTileWidth      = 0;
    myPanoDesc->fastStartTileHeight     = 0;
    myPanoDesc->fastStartRefTrackIndex  = 0;
}
 
 
/*******************************************************************************
 * CreatePanoTrackFromMovies
 *
 *  Create a (version 2.0) panorama track, appending the given video tracks.
 *******************************************************************************/
 
static OSErr
CreatePanoTrackFromMovies(
    const FSSpec            *srcMovieSpec,
    const FSSpec            *srcHSMovieSpec,
    const FSSpec            *srcFSMovieSpec,
    const VRMakeQTVRParams  *qtvrParams,
    long                    versionToCreate,
    Movie                   dstMovie,
    Track                   qtvrTrack,
    Track                   *thePanoTrack
)
{
    Track                       panoTrack           = NULL;
    Track                       imageTrack          = NULL;
    Track                       hsImageTrack        = NULL;
    Track                       fsImageTrack        = NULL;
    Media                       panoMedia           = NULL;
    SampleDescriptionHandle     sampleDesc          = NULL;
    QTAtomContainer             myPanoSample        = NULL;
    OSErr                       err                 = noErr;
    VRMakeQTVRParams            myParams            = *qtvrParams;
    MyPanoTrackDescriptor       myPanoDesc;
 
 
    /* Initialize myPanoDesc */
    InitMyPanoDesc(&myPanoDesc, versionToCreate);
 
    /* Create a new pano track and media */
    panoTrack = NewMovieTrack(dstMovie, myParams.windowWidth<<16, myParams.windowHeight<<16, 0);
    if (panoTrack == NULL) {
        err = GetMoviesError();
        goto bail;
    }
    panoMedia = NewTrackMedia(panoTrack, kQTVRPanoramaType, kQTVRStandardTimeScale, 0, 0);
    if (panoMedia == NULL) {
        err = GetMoviesError();
        goto bail;
    }
    
    /* Create a fast start image track, if given; a reference to it is contained in the pano track */
    if ((srcFSMovieSpec != NULL) && (srcFSMovieSpec->name[0] != 0)) {
        err = ImportFirstVideoTrackFromFile(srcFSMovieSpec, dstMovie, myParams.trackDuration, 0,
            &myPanoDesc.fastStartTileWidth, &myPanoDesc.fastStartTileHeight, &fsImageTrack
        );
        if (err != noErr)
            goto bail;
    }   
    
    /* Create a panorama image track; a reference to it is contained in the pano track */
    err = ImportFirstVideoTrackFromFile(srcMovieSpec, dstMovie, myParams.trackDuration, 0,
        &myPanoDesc.imageTileWidth, &myPanoDesc.imageTileHeight, &imageTrack
    );
    if (err != noErr)
        goto bail;
    
    /* Create a hot spot image track, if given; a reference to it is contained in the pano track */
    if ((srcHSMovieSpec != NULL) && (srcHSMovieSpec->name[0] != 0)) {
        err = ImportFirstVideoTrackFromFile(srcHSMovieSpec, dstMovie, myParams.trackDuration, 0,
            &myPanoDesc.hotSpotTileWidth, &myPanoDesc.hotSpotTileHeight, &hsImageTrack
        );
        if (err != noErr)
            goto bail;
    }   
    
    /* Create track references from QTVR track to panorama track,
     * and from the panorama track to the panorama image track and the hot spot image track
     */
    if (panoTrack    != NULL)   AddTrackReference(qtvrTrack, panoTrack,    kQTVRPanoramaType,        NULL);
    if (imageTrack   != NULL)   AddTrackReference(panoTrack, imageTrack,   kQTVRImageTrackRefType,   &myPanoDesc.imageRefTrackIndex);
    if (hsImageTrack != NULL)   AddTrackReference(panoTrack, hsImageTrack, kQTVRHotSpotTrackRefType, &myPanoDesc.hotSpotRefTrackIndex);
    if (fsImageTrack != NULL)   AddTrackReference(panoTrack, fsImageTrack, kQTVRImageTrackRefType,   &myPanoDesc.fastStartRefTrackIndex);
 
 
    /* Create a sample description; this contains no real info, but AddMediaSample requires it. */
    sampleDesc = (SampleDescriptionHandle)MyNewHandleClear(sizeof(SampleDescription));
 
    /* Check pano parameters */
    VerifyPanoParameters(&myParams, &myPanoDesc);
 
    /* Create panorama media sample and add it to the panorama track */
    err = CreatePanoMediaSample(&myParams, &myPanoDesc, &myPanoSample);
    if (err != noErr)
        goto bail;
    BeginMediaEdits(panoMedia);
    err = AddMediaSample(panoMedia, (Handle)myPanoSample, 0, GetHandleSize((Handle)myPanoSample), myParams.trackDuration, (SampleDescriptionHandle)sampleDesc, 1, 0, NULL);
    if (err != noErr)
        goto bail;
    EndMediaEdits(panoMedia);
    err = InsertMediaIntoTrack(panoTrack, 0, 0, myParams.trackDuration, fixed1);
    QTDisposeAtomContainer(myPanoSample);
    DisposeHandle((Handle)sampleDesc);
    
bail:
    *thePanoTrack = panoTrack;
    return(err);
}
 
 
/*******************************************************************************
 * CreatePanoTrackFromPicts
 *
 *  Create a (version 2.0) panorama track, appending the given Picts.
 *******************************************************************************/
 
static OSErr
CreatePanoTrackFromPicts(
    long                    numFaces,
    FSSpecHandle            srcPictSpecs,       /* Source     images */
    FSSpecHandle            srcHSPictSpecs,     /* Hot spot   images */
    FSSpecHandle            srcFSPictSpecs,     /* Fast start images */
    long                    versionToCreate,
    const VRMakeQTVRParams  *qtvrParams,
    Movie                   dstMovie,
    Track                   qtvrTrack,
    Track                   *thePanoTrack
)
{
    Track                       imageTrack          = NULL;
    Track                       hsImageTrack        = NULL;
    Track                       fsImageTrack        = NULL;
    Track                       panoTrack           = NULL;
    Media                       panoMedia           = NULL;
    long                        imageRefIndex       = 0;
    long                        hsRefIndex          = 0;
    long                        fsRefIndex          = 0;
    SampleDescriptionHandle     sampleDesc          = NULL;
    QTAtomContainer             myPanoSample        = NULL;
    OSErr                       err                 = noErr;
    long                        overlapTiles        = (long)(versionToCreate == kQTVRCubicVersion1);
    VRMakeQTVRParams            myParams            = *qtvrParams;
    MyPanoTrackDescriptor       myPanoDesc;
 
 
    /* Initialize myPanoDesc */
    InitMyPanoDesc(&myPanoDesc, versionToCreate);
 
    /* Create a new pano track and media */
    panoTrack = NewMovieTrack(dstMovie, myParams.windowWidth<<16, myParams.windowHeight<<16, 0);
    if (panoTrack == NULL) {
        err = GetMoviesError();
        goto bail;
    }
    panoMedia = NewTrackMedia(panoTrack, kQTVRPanoramaType, kQTVRStandardTimeScale, 0, 0);
    if (panoTrack == NULL) {
        err = GetMoviesError();
        goto bail;
    }
    
    /* Create a fast start image track, if given; a reference to it is contained in the pano track */
    if (HasOneFSSpec(srcFSPictSpecs)) {
        err = TilePictsToNewTrack(numFaces, srcFSPictSpecs, overlapTiles, myParams.previewCodec, myParams.previewQuality,
            myParams.tilesH, myParams.tilesV, 24, dstMovie, myParams.trackDuration,
            &myPanoDesc.fastStartTileWidth, &myPanoDesc.fastStartTileHeight, &fsImageTrack
        );
        if (err != noErr)
            goto bail;
    }   
    
    /* Create a panorama image track; a reference to it is contained in the pano track */
    err = TilePictsToNewTrack(numFaces, srcPictSpecs, overlapTiles, myParams.tileCodec, myParams.tileQuality,
        myParams.tilesH, myParams.tilesV, 24, dstMovie, myParams.trackDuration,
        &myPanoDesc.imageTileWidth, &myPanoDesc.imageTileHeight, &imageTrack
    );
    if (err != noErr)
        goto bail;
    
    /* Create a hot spot image track, if given; a reference to it is contained in the pano track */
    if (HasOneFSSpec(srcHSPictSpecs)) {
        err = TilePictsToNewTrack(numFaces, srcHSPictSpecs, overlapTiles, kGraphicsCodecType, codecLosslessQuality,
            myParams.tilesH, myParams.tilesV, 8, dstMovie, myParams.trackDuration,
            &myPanoDesc.hotSpotTileWidth, &myPanoDesc.hotSpotTileHeight, &hsImageTrack
        );
        if (err != noErr)
            goto bail;
    }   
    
    
    /* Create track references from QTVR track to panorama track,
     * and from the panorama track to the panorama image track and the hot spot image track
     */
    if (panoTrack    != NULL)   AddTrackReference(qtvrTrack, panoTrack,    kQTVRPanoramaType,        NULL);
    if (imageTrack   != NULL)   AddTrackReference(panoTrack, imageTrack,   kQTVRImageTrackRefType,   &myPanoDesc.imageRefTrackIndex);
    if (hsImageTrack != NULL)   AddTrackReference(panoTrack, hsImageTrack, kQTVRHotSpotTrackRefType, &myPanoDesc.hotSpotRefTrackIndex);
    if (fsImageTrack != NULL)   AddTrackReference(panoTrack, fsImageTrack, kQTVRImageTrackRefType,   &myPanoDesc.fastStartRefTrackIndex);
 
 
    /* Create a sample description; this contains no real info, but AddMediaSample requires it. */
    sampleDesc = (SampleDescriptionHandle)MyNewHandleClear(sizeof(SampleDescription));
 
    /* Check pano parameters */
    VerifyPanoParameters(&myParams, &myPanoDesc);
 
    /* Create panorama media sample and add it to the panorama track */
    err = CreatePanoMediaSample(&myParams, &myPanoDesc, &myPanoSample);
    if (err != noErr)
        goto bail;
    BeginMediaEdits(panoMedia);
    err = AddMediaSample(panoMedia, (Handle)myPanoSample, 0, GetHandleSize((Handle)myPanoSample), myParams.trackDuration, (SampleDescriptionHandle)sampleDesc, 1, 0, NULL);
    if (err != noErr)
        goto bail;
    EndMediaEdits(panoMedia);
    err = InsertMediaIntoTrack(panoTrack, 0, 0, myParams.trackDuration, fixed1);
    
bail:
    if (myPanoSample != NULL)   QTDisposeAtomContainer(myPanoSample);
    if (sampleDesc   != NULL)   DisposeHandle((Handle)sampleDesc);
    *thePanoTrack = panoTrack;
    return(err);
}
 
 
/*******************************************************************************
 * CreatePanoTrackFromGWorlds
 *
 *  Create a (version 2.0) panorama track, appending the given GWorlds.
 *******************************************************************************/
 
static OSErr
CreatePanoTrackFromGWorlds(
    long                    numFaces,
    GWorldPtr               *panoGWs,
    GWorldPtr               *hsGWs,
    GWorldPtr               *fsGWs,
    long                    versionToCreate,
    const VRMakeQTVRParams  *qtvrParams,
    Movie                   dstMovie,
    Track                   qtvrTrack,
    Track                   *thePanoTrack
)
{
    Track                       imageTrack          = NULL;
    Track                       hsImageTrack        = NULL;
    Track                       fsImageTrack        = NULL;
    Track                       panoTrack           = NULL;
    Media                       panoMedia           = NULL;
    long                        imageRefIndex       = 0;
    long                        hsRefIndex          = 0;
    long                        fsRefIndex          = 0;
    SampleDescriptionHandle     sampleDesc          = NULL;
    QTAtomContainer             myPanoSample        = NULL;
    OSErr                       err                 = noErr;
    long                        overlapTiles        = (long)(versionToCreate == kQTVRCubicVersion1);
    VRMakeQTVRParams            myParams            = *qtvrParams;
    MyPanoTrackDescriptor       myPanoDesc;
 
 
    /* Initialize myPanoDesc */
    InitMyPanoDesc(&myPanoDesc, versionToCreate);
 
    /* Create a new pano track and media */
    panoTrack = NewMovieTrack(dstMovie, myParams.windowWidth<<16, myParams.windowHeight<<16, 0);
    if (panoTrack == NULL) {
        err = GetMoviesError();
        goto bail;
    }
    panoMedia = NewTrackMedia(panoTrack, kQTVRPanoramaType, kQTVRStandardTimeScale, 0, 0);
    if (panoTrack == NULL) {
        err = GetMoviesError();
        goto bail;
    }
    
    /* Create a fast start image track, if given; a reference to it is contained in the pano track */
    if ((fsGWs != NULL) && (fsGWs[0] != NULL)) {
        err = TileGWorldsToNewTrack(numFaces, fsGWs, overlapTiles, myParams.previewCodec, myParams.previewQuality,
            myParams.tilesH, myParams.tilesV, 24, dstMovie, myParams.trackDuration,
            &myPanoDesc.fastStartTileWidth, &myPanoDesc.fastStartTileHeight, &fsImageTrack
        );
        if (err != noErr)
            goto bail;
    }   
    
    /* Create a panorama image track; a reference to it is contained in the pano track */
    err = TileGWorldsToNewTrack(numFaces, panoGWs, overlapTiles, myParams.tileCodec, myParams.tileQuality,
        myParams.tilesH, myParams.tilesV, 24, dstMovie, myParams.trackDuration,
        &myPanoDesc.imageTileWidth, &myPanoDesc.imageTileHeight, &imageTrack
    );
    if (err != noErr)
        goto bail;
    
    /* Create a hot spot image track, if given; a reference to it is contained in the pano track */
    if ((hsGWs != NULL) && (hsGWs[0] != NULL)) {
        err = TileGWorldsToNewTrack(numFaces, hsGWs, overlapTiles, kGraphicsCodecType, codecLosslessQuality,
            myParams.tilesH, myParams.tilesV, 8, dstMovie, myParams.trackDuration,
            &myPanoDesc.hotSpotTileWidth, &myPanoDesc.hotSpotTileHeight, &hsImageTrack
        );
        if (err != noErr)
            goto bail;
    }   
    
    
    /* Create track references from QTVR track to panorama track,
     * and from the panorama track to the panorama image track and the hot spot image track
     */
    if (panoTrack    != NULL)   AddTrackReference(qtvrTrack, panoTrack,    kQTVRPanoramaType,        NULL);
    if (imageTrack   != NULL)   AddTrackReference(panoTrack, imageTrack,   kQTVRImageTrackRefType,   &myPanoDesc.imageRefTrackIndex);
    if (hsImageTrack != NULL)   AddTrackReference(panoTrack, hsImageTrack, kQTVRHotSpotTrackRefType, &myPanoDesc.hotSpotRefTrackIndex);
    if (fsImageTrack != NULL)   AddTrackReference(panoTrack, fsImageTrack, kQTVRImageTrackRefType,   &myPanoDesc.fastStartRefTrackIndex);
 
 
    /* Create a sample description; this contains no real info, but AddMediaSample requires it. */
    sampleDesc = (SampleDescriptionHandle)MyNewHandleClear(sizeof(SampleDescription));
 
    /* Check pano parameters */
    VerifyPanoParameters(&myParams, &myPanoDesc);
 
    /* Create panorama media sample and add it to the panorama track */
    err = CreatePanoMediaSample(&myParams, &myPanoDesc, &myPanoSample);
    if (err != noErr)
        goto bail;
    BeginMediaEdits(panoMedia);
    err = AddMediaSample(panoMedia, (Handle)myPanoSample, 0, GetHandleSize((Handle)myPanoSample), myParams.trackDuration, (SampleDescriptionHandle)sampleDesc, 1, 0, NULL);
    if (err != noErr)
        goto bail;
    EndMediaEdits(panoMedia);
    err = InsertMediaIntoTrack(panoTrack, 0, 0, myParams.trackDuration, fixed1);
    
bail:
    if (myPanoSample != NULL)   QTDisposeAtomContainer(myPanoSample);
    if (sampleDesc   != NULL)   DisposeHandle((Handle)sampleDesc);
    *thePanoTrack = panoTrack;
    return(err);
}
 
 
#pragma mark -
#pragma mark ### Cylindrical API ###
#pragma mark -
/********************************************************************************
 ********************************************************************************
 ********************************************************************************
 ***        API
 ********************************************************************************
 ********************************************************************************
 ********************************************************************************/
 
 
/*******************************************************************************
 * VRMovieToQTVRCylPano1x0
 * Create a single-node panoramic QTVR movie from the specified tile movie(s).
 *
 * NOTE: This function builds a movie that conforms to version 1.0 of the QuickTime VR file format.
 *
 * The newly-created movie contains references to the original tile movie, not the actual movie data.
 * We do this because we assume that the caller will flatten the movie into a third movie, which will
 * contain the movie data. Also, the interim file is much smaller than it would be if we copied the data,
 * thus saving time and disk space.
 *******************************************************************************/
 
OSErr
VRMovieToQTVRCylPano1x0(
    VRMakeQTVRParams    *qtvrParams,    /* Parameters used to create the movie */
    FSSpec              *srcTileSpec,   /* Cylindrical tile movie for the panoramic image */
    FSSpec              *srcHSTileSpec, /* Cylindrical tile movie for the hot spot image */
    FSSpec              *dstMovieSpec   /* Destination movie */
)
{
    PanoramaDescriptionHandle   myPanoDesc          = NULL;
    PanoSampleHeaderAtomHandle  myPanoHeader            = NULL;
    HotSpotTableAtomHandle      myHotSpotTable      = NULL;
    StringTableAtomHandle       myStringTable       = NULL;
    short                       myResRefNum         = -1;
    short                       myResID             = movieInDataForkResID;
    short                       myHSResRefNum       = -1;
    short                       myTilResRefNum      = -1;
    short                       myHSTilResRefNum    = -1;
    Movie                       myMovie             = NULL;
    Movie                       myTileMovie         = NULL;
    Movie                       myHSTileMovie       = NULL;
    Track                       myPanoTrack;
    Media                       myPanoMedia;
    Track                       myImageTrack;
    Track                       myHSImageTrack;
    Fixed                       myTheta;
    UInt16                      myIndex;
    long                        myTrackWidth, myTrackHeight;
    ComponentResult             err                 = noErr;
    
    /*
     * create a new movie
     */
 
    /* create a movie file for the destination movie */
    err = CreateMovieFile(dstMovieSpec, FOUR_CHAR_CODE('TVOD'), smCurrentScript, kCreateMovieFlags, &myResRefNum, &myMovie);
    if (err != noErr)
        goto bail;
 
    /*
     * copy the video track from the tile movie to the new movie; this is the "scene track"
     */
 
    /* open the tile movie file */
    err = ImportFirstVideoTrackFromFile(srcTileSpec, myMovie, qtvrParams->trackDuration, 0, &myTrackWidth, &myTrackHeight, &myImageTrack);
    if (err != noErr)
        goto bail;
    
    /*
     * copy the hot spot image track from the tile movie to the new movie
     */
    if ((srcHSTileSpec != NULL) && (srcHSTileSpec->name[0] != 0)) {
        err = ImportFirstVideoTrackFromFile(srcHSTileSpec, myMovie, qtvrParams->trackDuration, 0, &myTrackWidth, &myTrackHeight, &myHSImageTrack);
        if (err != noErr)
            goto bail;
    }
    
    /*
     * create a panorama track and add it to the movie
     */
    
    /* create a panorama track and media */
    myPanoTrack = NewMovieTrack(myMovie, qtvrParams->windowWidth << 16, qtvrParams->windowHeight << 16, 0);
    myPanoMedia = NewTrackMedia(myPanoTrack, kQTVROldPanoType, kQTVRStandardTimeScale, NULL, 0);
    if (((err = GetMoviesError()) != noErr) || (myPanoTrack == NULL) || (myPanoMedia == NULL))
        goto bail;
 
    /* create a panorama sample description */
    myPanoDesc = (PanoramaDescriptionHandle)MyNewHandleClear(sizeof(PanoramaDescription));
    if (myPanoDesc == NULL)
        goto bail;
 
    /* fill in the panorama sample description;
     * all the data in this sample description that follows the first 4 long words must be in big-endian format;
     * the first four long words are used by AddMediaSample and must therefore be in native-endian format
     */
    (**myPanoDesc).size                 = sizeof(PanoramaDescription);
    (**myPanoDesc).type                 = kPanDescType;
    (**myPanoDesc).reserved1            = 0L;
    (**myPanoDesc).reserved2            = 0L;
    
    (**myPanoDesc).majorVersion         = EndianU16_NtoB(0);
    (**myPanoDesc).minorVersion         = EndianU16_NtoB(0);
    (**myPanoDesc).sceneTrackID         = EndianU32_NtoB(GetTrackID(myImageTrack));
    (**myPanoDesc).loResSceneTrackID    = EndianU32_NtoB(0L);       /* no lo-res video track */
    
    for (myIndex = 1; myIndex < 6; myIndex++) {
        (**myPanoDesc).reserved3[myIndex] = EndianU32_NtoB(0L);
        (**myPanoDesc).reserved4[myIndex] = EndianU32_NtoB(0L);
    }
    
    (**myPanoDesc).hotSpotTrackID       = EndianU32_NtoB(GetTrackID(myHSImageTrack));
    (**myPanoDesc).loResHotSpotTrackID  = EndianU32_NtoB(0L);   /* no lo-res hot spot track */
 
    myTheta = 180.0 * (atan((myTrackWidth * qtvrParams->tilesH) * D_PI / (myTrackHeight * qtvrParams->tilesV))) / D_PI * 65536.0;
    
    (**myPanoDesc).hPanStart            = EndianF32NtoFixedB(qtvrParams->minPan);
    (**myPanoDesc).hPanEnd              = EndianF32NtoFixedB(qtvrParams->maxPan);
    (**myPanoDesc).vPanTop              = EndianF32NtoFixedB(qtvrParams->maxTilt);
    (**myPanoDesc).vPanBottom           = EndianF32NtoFixedB(qtvrParams->minTilt);
    (**myPanoDesc).minimumZoom          = EndianF32NtoFixedB(qtvrParams->minFieldOfView);
    (**myPanoDesc).maximumZoom          = EndianF32NtoFixedB(qtvrParams->maxFieldOfView);
    
    (**myPanoDesc).sceneSizeX           = EndianU32_NtoB((long)(myTrackWidth  * qtvrParams->tilesH));
    (**myPanoDesc).sceneSizeY           = EndianU32_NtoB((long)(myTrackHeight * qtvrParams->tilesV));
    (**myPanoDesc).numFrames            = EndianU32_NtoB((long)qtvrParams->tilesV);
    (**myPanoDesc).sceneNumFramesX      = EndianU16_NtoB(qtvrParams->tilesH);
    (**myPanoDesc).sceneNumFramesY      = EndianU16_NtoB(qtvrParams->tilesV);
    (**myPanoDesc).sceneColorDepth      = EndianU16_NtoB(32);
    
    (**myPanoDesc).hotSpotSizeX         = EndianU32_NtoB((long)(myTrackWidth  * qtvrParams->tilesH));
    (**myPanoDesc).hotSpotSizeY         = EndianU32_NtoB((long)(myTrackHeight * qtvrParams->tilesV));
    (**myPanoDesc).hotSpotNumFramesX    = EndianU16_NtoB(qtvrParams->tilesH);
    (**myPanoDesc).hotSpotNumFramesY    = EndianU16_NtoB(qtvrParams->tilesV);
    (**myPanoDesc).hotSpotColorDepth    = EndianU16_NtoB(8);
 
    /* create a panorama header atom;
     * the data in this atom (which is *not* a QTAtom) must be in big-endian format
     */
    myPanoHeader = (PanoSampleHeaderAtomHandle)MyNewHandleClear(sizeof(PanoSampleHeaderAtom));
    if (myPanoHeader == NULL)
        goto bail;
        
    (**myPanoHeader).size               = EndianU32_NtoB(sizeof(PanoSampleHeaderAtom));
    (**myPanoHeader).type               = EndianU32_NtoB(kPanHeaderType);
    (**myPanoHeader).nodeID             = EndianU32_NtoB(0L);
 
    /* set the default pan, tilt, and zoom */
    (**myPanoHeader).defHPan            = EndianF32NtoFixedB(qtvrParams->defaultPan);
    (**myPanoHeader).defVPan            = EndianF32NtoFixedB(qtvrParams->defaultTilt);
    (**myPanoHeader).defZoom            = EndianF32NtoFixedB(qtvrParams->defaultFieldOfView);
 
    /* set constraints for this node; use 0 for the default constraints */
    (**myPanoHeader).minHPan            = EndianS32_NtoB(0L);
    (**myPanoHeader).minVPan            = EndianS32_NtoB(0L);
    (**myPanoHeader).maxHPan            = EndianS32_NtoB(0L);
    (**myPanoHeader).maxVPan            = EndianS32_NtoB(0L);
    (**myPanoHeader).maxZoom            = EndianS32_NtoB(0L);
 
    (**myPanoHeader).nameStrOffset      = EndianU32_NtoB(0L);       /* no name or comment */
    (**myPanoHeader).commentStrOffset   = EndianU32_NtoB(0L);
 
    /* create a string table atom;
     * the data in this atom (which is *not* a QTAtom) must be in big-endian format
     */
    myStringTable = (StringTableAtomHandle)MyNewHandleClear(sizeof(StringTableAtom));
    if (myStringTable == NULL)
        goto bail;
        
    (**myStringTable).size              = EndianU32_NtoB(sizeof(StringTableAtom));
    (**myStringTable).type              = EndianU32_NtoB(kStringTableType);
    (**myStringTable).bunchOstrings[0]  = EndianU32_NtoB(0);
 
    /* create a hot spot table atom and expand the string table atom to include the
     * hot spot names and comments
     */
    myHotSpotTable = MakeQTVRHotSpot1x0(myStringTable);
 
    /* append the hot spot table atom and the string table atom to the panorama header atom
     * [left as an exercise for the reader]
     */
    
    /* Add the media sample to the panorama track */
    BeginMediaEdits(myPanoMedia);
    err = AddMediaSample(myPanoMedia, (Handle)myPanoHeader, 0, GetHandleSize((Handle)myPanoHeader), qtvrParams->trackDuration, (SampleDescriptionHandle)myPanoDesc, 1, 0, NULL);
    if (err != noErr)
        goto bail;
    EndMediaEdits(myPanoMedia);
    err = InsertMediaIntoTrack(myPanoTrack, 0, 0, qtvrParams->trackDuration, fixed1);
    
    /* Add a user data item that identifies the QTVR movie controller */
    err = SetQTControllerType(myMovie, kQTVROldPanoType);
    if (err != noErr)
        goto bail;
        
    /* Add the movie resource to the panorama movie */
    err = AddMovieResource(myMovie, myResRefNum, &myResID, NULL);
    
bail:
    if (myPanoDesc       != NULL)   DisposeHandle((Handle)myPanoDesc);  
    if (myPanoHeader     != NULL)   DisposeHandle((Handle)myPanoHeader);    
    if (myResRefNum      != -1)     CloseMovieFile(myResRefNum);    
    if (myHSResRefNum    != -1)     CloseMovieFile(myHSResRefNum);  
    if (myMovie          != NULL)   DisposeMovie(myMovie);      
    if (myTilResRefNum   != -1)     CloseMovieFile(myTilResRefNum); 
    if (myHSTilResRefNum != -1)     CloseMovieFile(myHSTilResRefNum);
    if (myTileMovie      != NULL)   DisposeMovie(myTileMovie);
    if (myHSTileMovie    != NULL)   DisposeMovie(myHSTileMovie);
        
    return(err);
}
 
 
/*******************************************************************************
 * VRMovieToQTVRCylPano2
 * Create a single-node panoramic QTVR movie from the specified tile movie(s).
 *
 * NOTE: This function builds a movie that conforms to version 2.0 of the QuickTime VR file format.
 *
 * The newly-created movie contains references to the original tile movie, not the actual movie data.
 * We do this because we assume that the caller will flatten the movie into a third movie, which will
 * contain the movie data. Also, the interim file is much smaller than it would be if we copied the data,
 * thus saving time and disk space.
 *******************************************************************************/
 
static OSErr
VRMovieToQTVRCylPano2(
    VRMakeQTVRParams    *qtvrParams,    /* Parameters used to create the movie */
    long                panoType,       /* Parameters used to create the movie */
    FSSpec              *srcTileSpec,   /* Cylindrical tile movie for the panoramic image */
    FSSpec              *srcHSTileSpec, /* Cylindrical tile movie for the hot spot image */
    FSSpec              *srcFSTileSpec, /* Cylindrical tile movie for the fast start image */
    FSSpec              *dstMovieSpec   /* Destination movie */
)
{
    short               myResRefNum         = -1;
    Movie               tmpMovie            = NULL;
    Track               myQTVRTrack;
    Track               myPanoTrack;
    ComponentResult     err                 = noErr;
    FSSpec              tmpSpec;
    
    /* Create a movie file for the destination movie */
    MakeTempFSSpec(dstMovieSpec, ".MV~", &tmpSpec);
    err = CreateMovieFile(&tmpSpec, FOUR_CHAR_CODE('TVOD'), smCurrentScript, kCreateMovieFlags, &myResRefNum, &tmpMovie);
    if (err != noErr)
        goto bail;
 
    /* Create the QTVR movie track and media */
    err = CreateQTVRTrack(qtvrParams, qtvrParams->trackDuration, tmpMovie, &myQTVRTrack);
    if (err != noErr)
        goto bail;
    
    /* Create panorama track and media, and add them to the movie */
    err = CreatePanoTrackFromMovies(srcTileSpec, srcHSTileSpec, srcFSTileSpec,
        qtvrParams, panoType, tmpMovie, myQTVRTrack,
        &myPanoTrack
    );
    if (err != noErr)
        goto bail;
        
    /* Add a user data item that identifies the QTVR movie controller */
    err = SetQTControllerType(tmpMovie, kQTVRQTVRType);
    if (err != noErr)
        goto bail;
        
    /* Create the final, flattened movie, from the temporary file into a new movie file;
     * put the movie resource first so that FastStart is possible
     */
    err = FlattenQTVRMovie(tmpMovie, qtvrParams->flattenerFlags, qtvrParams->flattenerPreviewResolution, dstMovieSpec);
    
bail:
    if (myResRefNum != -1)      CloseMovieFile(myResRefNum);
    if (tmpMovie    != NULL)    DisposeMovie(tmpMovie);
    DeleteMovieFile(&tmpSpec);
        
    return(err);
}
 
 
/*******************************************************************************
 * VRMovieToQTVRCylPano2v0
 * Create a single-node panoramic QTVR movie from the specified tile movie(s).
 *
 * NOTE: This function builds a movie that conforms to version 2.0 of the QuickTime VR file format.
 *
 * The newly-created movie contains references to the original tile movie, not the actual movie data.
 * We do this because we assume that the caller will flatten the movie into a third movie, which will
 * contain the movie data. Also, the interim file is much smaller than it would be if we copied the data,
 * thus saving time and disk space.
 *******************************************************************************/
 
OSErr
VRMovieToQTVRCylPano2v0(
    VRMakeQTVRParams    *qtvrParams,    /* Parameters used to create the movie */
    FSSpec              *srcTileSpec,   /* Cylindrical tile movie for the panoramic image */
    FSSpec              *srcHSTileSpec, /* Cylindrical tile movie for the hot spot image */
    FSSpec              *srcFSTileSpec, /* Cylindrical tile movie for the fast start image */
    FSSpec              *dstMovieSpec   /* Destination movie */
)
{
    return(VRMovieToQTVRCylPano2(qtvrParams, kQTVRCylindricalVersion2v, srcTileSpec, srcHSTileSpec, srcFSTileSpec, dstMovieSpec));
}
 
 
/*******************************************************************************
 * VRMovieToQTVRCylPano2h0
 *
 *  Create a single-node QuickTime VR panoramic movie from the specified image file.
 *******************************************************************************/
 
OSErr
VRMovieToQTVRCylPano2h0(
    VRMakeQTVRParams    *qtvrParams,    /* Parameters used to create the movie */
    FSSpec              *srcTileSpec,   /* Cylindrical tile movie for the panoramic image */
    FSSpec              *srcHSTileSpec, /* Cylindrical tile movie for the hot spot image */
    FSSpec              *srcFSTileSpec, /* Cylindrical tile movie for the fast start image */
    FSSpec              *dstMovieSpec   /* Destination movie */
)
{
    return(VRMovieToQTVRCylPano2(qtvrParams, kQTVRCylindricalVersion2h, srcTileSpec, srcHSTileSpec, srcFSTileSpec, dstMovieSpec));
}
 
 
/*******************************************************************************
 * VRPictToQTVRCylPano2
 *
 *  Create a single-node QuickTime VR panoramic movie from the specified image file.
 *******************************************************************************/
 
static OSErr
VRPictToQTVRCylPano2(
    VRMakeQTVRParams    *qtvrParams,        /* Parameters used to create the movie */
    long                panoType,           /* Version 1, or version 2 (recommended) */
    FSSpec              *srcPictSpec,       /* Source   image */
    FSSpec              *srcHSPictSpec,     /* Hot spot image */
    FSSpec              *srcFSPictSpec,     /* Fast start image */
    FSSpec              *dstMovieSpec       /* Destination movie */
)
{
    short               myResRefNum         = -1;
    Movie               tmpMovie            = NULL;
    Track               myQTVRTrack;
    Track               myPanoTrack;
    ComponentResult     err                 = noErr;
    FSSpec              *srcH, *hsH, *fsH;
    FSSpec              tmpSpec;
    
    /* Create a movie file for the destination movie */
    MakeTempFSSpec(dstMovieSpec, ".MV~", &tmpSpec);
    err = CreateMovieFile(&tmpSpec, FOUR_CHAR_CODE('TVOD'), smCurrentScript, kCreateMovieFlags, &myResRefNum, &tmpMovie);
    if (err != noErr)
        goto bail;
 
    /* Create the QTVR movie track and media */
    err = CreateQTVRTrack(qtvrParams, qtvrParams->trackDuration, tmpMovie, &myQTVRTrack);
    if (err != noErr)
        goto bail;
    
    /* Create panorama track and media, and add them to the movie */
    srcH = srcPictSpec; hsH = srcHSPictSpec; fsH = srcFSPictSpec;
    err = CreatePanoTrackFromPicts(1, &srcH, (hsH ? &hsH : NULL), (fsH ? &fsH : NULL),
        panoType, qtvrParams, tmpMovie, myQTVRTrack,
        &myPanoTrack
    );
    if (err != noErr)
        goto bail;
        
    /* Add a user data item that identifies the QTVR movie controller */
    err = SetQTControllerType(tmpMovie, kQTVRQTVRType);
    if (err != noErr)
        goto bail;
        
    /* Create the final, flattened movie, from the temporary file into a new movie file;
     * put the movie resource first so that FastStart is possible
     */
    err = FlattenQTVRMovie(tmpMovie, qtvrParams->flattenerFlags, qtvrParams->flattenerPreviewResolution, dstMovieSpec);
    
bail:
    if (myResRefNum != -1)      CloseMovieFile(myResRefNum);
    if (tmpMovie     != NULL)   DisposeMovie(tmpMovie);
    DeleteMovieFile(&tmpSpec);
 
    return(err);
}
 
 
/*******************************************************************************
 * VRPictToQTVRCylPano2v0
 *******************************************************************************/
 
OSErr
VRPictToQTVRCylPano2v0(
    VRMakeQTVRParams    *qtvrParams,    /* Parameters used to create the movie */
    FSSpec              *srcPictSpec,   /* Source   image, rotated 90 degrees counter-clockwise (classic orientation) */
    FSSpec              *srcHSPictSpec, /* Hot spot image, rotated 90 degrees counter-clockwise (classic orientation) */
    FSSpec              *srcFSPictSpec, /* Fast start image, rotated 90 degrees counter-clockwise (classic orientation) */
    FSSpec              *dstMovieSpec   /* Destination movie */
)
{
    return(VRPictToQTVRCylPano2(qtvrParams, kQTVRCylindricalVersion2v, srcPictSpec, srcHSPictSpec, srcFSPictSpec, dstMovieSpec));
}
 
 
/*******************************************************************************
 * VRPictToQTVRCylPano2h0
 *******************************************************************************/
 
OSErr
VRPictToQTVRCylPano2h0(
    VRMakeQTVRParams    *qtvrParams,    /* Parameters used to create the movie */
    FSSpec              *srcPictSpec,   /* Source   image, non-rotated (modern orientation) */
    FSSpec              *srcHSPictSpec, /* Hot spot image, non-rotated (modern orientation)) */
    FSSpec              *srcFSPictSpec, /* Fast start image, non-rotated (modern orientation)) */
    FSSpec              *dstMovieSpec   /* Destination movie */
)
{
    return(VRPictToQTVRCylPano2(qtvrParams, kQTVRCylindricalVersion2h, srcPictSpec, srcHSPictSpec, srcFSPictSpec, dstMovieSpec));
}
 
 
/*******************************************************************************
 * VRGWorldToQTVRCylPano2
 *
 *  Create a single-node QuickTime VR panoramic movie from the specified image file.
 *******************************************************************************/
 
static OSErr
VRGWorldToQTVRCylPano2(
    VRMakeQTVRParams    *qtvrParams,        /* Parameters used to create the movie */
    long                panoType,           /* Version 1, or version 2 (recommended) */
    GWorldPtr           srcGW,              /* Source GWorld */
    GWorldPtr           srcHSGW,            /* Source hot spot GWorld */
    GWorldPtr           srcFSGW,            /* Source fast start GWorld */
    FSSpec              *dstMovieSpec       /* Destination movie */
)
{
    short               myResRefNum         = -1;
    Movie               tmpMovie            = NULL;
    Track               myQTVRTrack;
    Track               myPanoTrack;
    ComponentResult     err;
    GWorldPtr           srGW, hsGW, fsGW;
    FSSpec              tmpSpec;
    
    /* Create a movie file for the destination movie */
    MakeTempFSSpec(dstMovieSpec, ".MV~", &tmpSpec);
    err = CreateMovieFile(&tmpSpec, FOUR_CHAR_CODE('TVOD'), smCurrentScript, kCreateMovieFlags, &myResRefNum, &tmpMovie);
    if (err != noErr)
        goto bail;
 
    /* Create the QTVR movie track and media */
    err = CreateQTVRTrack(qtvrParams, qtvrParams->trackDuration, tmpMovie, &myQTVRTrack);
    if (err != noErr)
        goto bail;
    
    /* Create panorama track and media, and add them to the movie */
    srGW = srcGW; hsGW = srcHSGW; fsGW = srcFSGW;
    err = CreatePanoTrackFromGWorlds(1, &srcGW, (hsGW ? &hsGW : NULL), (fsGW ? &fsGW : NULL),
        panoType, qtvrParams, tmpMovie, myQTVRTrack,
        &myPanoTrack
    );
    if (err != noErr)
        goto bail;
        
    /* Add a user data item that identifies the QTVR movie controller */
    err = SetQTControllerType(tmpMovie, kQTVRQTVRType);
    if (err != noErr)
        goto bail;
        
    /* Create the final, flattened movie, from the temporary file into a new movie file;
     * put the movie resource first so that FastStart is possible
     */
    err = FlattenQTVRMovie(tmpMovie, qtvrParams->flattenerFlags, qtvrParams->flattenerPreviewResolution, dstMovieSpec);
    
bail:
    if (myResRefNum != -1)      CloseMovieFile(myResRefNum);
    if (tmpMovie     != NULL)   DisposeMovie(tmpMovie);
    DeleteMovieFile(&tmpSpec);
 
    return(err);
}
 
 
/*******************************************************************************
 * VRGWorldToQTVRCylPano2v0
 *******************************************************************************/
 
OSErr
VRGWorldToQTVRCylPano2v0(
    VRMakeQTVRParams    *qtvrParams,    /* Parameters used to create the movie */
    GWorldPtr           srcGW,          /* Source GWorld, rotated 90 degrees counter-clockwise (classic orientation) */
    GWorldPtr           srcHSGW,        /* Source hot spot GWorld, rotated 90 degrees counter-clockwise (classic orientation) */
    GWorldPtr           srcFSGW,        /* Source fast start GWorld, rotated 90 degrees counter-clockwise (classic orientation) */
    FSSpec              *dstMovieSpec   /* Destination movie */
)
{
    return(VRGWorldToQTVRCylPano2(qtvrParams, kQTVRCylindricalVersion2v, srcGW, srcHSGW, srcFSGW, dstMovieSpec));
}
 
 
/*******************************************************************************
 * VRGWorldToQTVRCylPano2h0
 *******************************************************************************/
 
OSErr
VRGWorldToQTVRCylPano2h0(
    VRMakeQTVRParams    *qtvrParams,    /* Parameters used to create the movie */
    GWorldPtr           srcGW,          /* Source GWorld, non-rotated (modern orientation) */
    GWorldPtr           srcHSGW,        /* Source hot spot GWorld, non-rotated (modern orientation) */
    GWorldPtr           srcFSGW,        /* Source fast start GWorld, non-rotated (modern orientation) */
    FSSpec              *dstMovieSpec   /* Destination movie */
)
{
    return(VRGWorldToQTVRCylPano2(qtvrParams, kQTVRCylindricalVersion2h, srcGW, srcHSGW, srcFSGW, dstMovieSpec));
}
 
 
#pragma mark -
#pragma mark ### Cubic API ###
#pragma mark -
/*******************************************************************************
 * VRMovieToQTVRCubicPano
 *
 *  Create a single-node cubic panoramic QTVR movie from the specified 6-frame movie.
 *******************************************************************************/
 
OSErr
VRMovieToQTVRCubicPano(
    VRMakeQTVRParams    *qtvrParams,        /* Parameters used to create the movie */
    FSSpec              *srcFramesSpec,     /* Source movie with the panorama faces */
    FSSpec              *srcHSFramesSpec,   /* Source movie with the hot spot faces */
    FSSpec              *srcFSFramesSpec,   /* Source movie with the fast start faces */
    FSSpec              *dstMovieSpec       /* Destination movie */
)
{
    FSSpec              tmpSpec;
    short               myResRefNum         = -1;
    Movie               tmpMovie                = NULL;
    Track               tmpQTVRTrack;
    Track               tmpPanoTrack;
    ComponentResult     err;
    
    /* Create a temporary version of the panorama movie file,
     * located in the same directory as the destination panorama movie file
     */
    MakeTempFSSpec(dstMovieSpec, ".MV~", &tmpSpec);
    err = CreateMovieFile(&tmpSpec, FOUR_CHAR_CODE('TVOD'), smCurrentScript, kCreateMovieFlags, &myResRefNum, &tmpMovie);
    if (err != noErr)
        goto bail;
 
    /* Create the QTVR movie track and media */
    err = CreateQTVRTrack(qtvrParams, qtvrParams->trackDuration, tmpMovie, &tmpQTVRTrack);
    if (err != noErr)
        goto bail;
    
    /* Create panorama track and media, and add them to the movie */
    err = CreatePanoTrackFromMovies(srcFramesSpec, srcHSFramesSpec, srcFSFramesSpec,
        qtvrParams, kQTVRCubicVersion1, tmpMovie, tmpQTVRTrack,
        &tmpPanoTrack
    );
    if (err != noErr)
        goto bail;
        
    /* Add a user data item that identifies the QTVR movie controller */
    err = SetQTControllerType(tmpMovie, kQTVRQTVRType);
    if (err != noErr)
        goto bail;
        
    /* Create the final, flattened movie, from the temporary file into a new movie file;
     * put the movie resource first so that FastStart is possible
     */
    err = FlattenQTVRMovie(tmpMovie, qtvrParams->flattenerFlags, qtvrParams->flattenerPreviewResolution, dstMovieSpec);
 
bail:
    if (myResRefNum != -1)      CloseMovieFile(myResRefNum);
    if (tmpMovie     != NULL)   DisposeMovie(tmpMovie);
    DeleteMovieFile(&tmpSpec);
    
    return(err);
}
 
 
/********************************************************************************
 * VRPictsToQTVRCubicPano
 *
 * This converts a set of 6 picture files to a cubic QuickTime VR panorama movie.
 *
 * This implements tiling of the faces, but the face dimensions must be appropriately divisible:
 *      (width  - tilesH + 1) / tilesH = integer
 *      (height - tilesV + 1) / tilesV = integer
 *  e.g. {dim=512,tiles=1}, {dim=511,tiles=2}, {dim=510,tiles=3}
 ********************************************************************************/
 
OSErr
VRPictsToQTVRCubicPano(
    VRMakeQTVRParams    *qtvrParams,        /* Parameters used to create the movie */
    FSSpecHandle        srcPictSpecs,       /* Source     images */
    FSSpecHandle        srcHSPictSpecs,     /* Hot spot   images */
    FSSpecHandle        srcFSPictSpecs,     /* Fast start images */
    FSSpec              *dstMovieSpec       /* Destination movie */
)
{
    FSSpec              tmpSpec;
    short               tmpRefNum           = -1;
    Movie               tmpMovie            = NULL;
    Track               qtvrTrack           = NULL;
    Track               panoTrack           = NULL;
    ComponentResult     err;
    
    /* Create a temporary version of the panorama movie file,
     * located in the same directory as the destination panorama movie file
     */
    MakeTempFSSpec(dstMovieSpec, ".MV~", &tmpSpec);
    err = CreateMovieFile(&tmpSpec, FOUR_CHAR_CODE('TVOD'), smCurrentScript, kCreateMovieFlags, &tmpRefNum, &tmpMovie);
    if (err != noErr)
        goto bail;
 
    /* Create the QTVR movie track and media */
    err = CreateQTVRTrack(qtvrParams, qtvrParams->trackDuration, tmpMovie, &qtvrTrack);
    if (err != noErr)
        goto bail;
    
    /*  Create panorama track and media, and add them to the movie */
    err = CreatePanoTrackFromPicts(6, srcPictSpecs, srcHSPictSpecs, srcFSPictSpecs, kQTVRCubicVersion1,
        qtvrParams, tmpMovie, qtvrTrack, &panoTrack
    );
    if (err != noErr)
        goto bail;
        
    /* Add a user data item that identifies the QTVR movie controller */
    err = SetQTControllerType(tmpMovie, kQTVRQTVRType);
    if (err != noErr)
        goto bail;
        
    /* Create the final, flattened movie, from the temporary file into a new movie file;
     * put the movie resource first so that FastStart is possible
     */
    err = FlattenQTVRMovie(tmpMovie, qtvrParams->flattenerFlags, qtvrParams->flattenerPreviewResolution, dstMovieSpec);
 
bail:
    if (tmpRefNum != -1)    CloseMovieFile(tmpRefNum);
    if (tmpMovie  != NULL)  DisposeMovie(tmpMovie);
    DeleteMovieFile(&tmpSpec);
    
    return(err);
}
 
 
/********************************************************************************
 * VRGWorldsToQTVRCubicPano
 *
 *  Given 6 GWorlds (and possible 6 hotspot GWorlds), create a cubic panorama movie.
 *
 *  This implements tiling of the faces, but the face dimensions must be appropriately divisible:
 *      (width  - tilesH + 1) / tilesH = integer
 *      (height - tilesV + 1) / tilesV = integer
 *  e.g. {dim=512,tiles=1}, {dim=511,tiles=2}, {dim=510,tiles=3}
 ********************************************************************************/
 
OSErr
VRGWorldsToQTVRCubicPano(
    VRMakeQTVRParams    *qtvrParams,    /* Parameters used to create the movie */
    GWorldPtr           *srcGWs,        /* 6 Source GWorlds - in standard order */
    GWorldPtr           *srcHSGWs,      /* 6 Source hot spot GWorlds - in standard order */
    GWorldPtr           *srcFSGWs,      /* 6 Source fast start GWorlds - in standard order */
    FSSpec              *dstMovieSpec   /* Destination movie */
)
{
    FSSpec              tmpSpec;
    short               tmpRefNum           = -1;
    Movie               tmpMovie            = NULL;
    Track               qtvrTrack           = NULL;
    Track               panoTrack           = NULL;
    ComponentResult     err;
    
    /* Create a temporary version of the panorama movie file,
     * located in the same directory as the destination panorama movie file
     */
    MakeTempFSSpec(dstMovieSpec, ".MV~", &tmpSpec);
    err = CreateMovieFile(&tmpSpec, FOUR_CHAR_CODE('TVOD'), smCurrentScript, kCreateMovieFlags, &tmpRefNum, &tmpMovie);
    if (err != noErr)
        goto bail;
 
    /* Create the QTVR movie track and media */
    err = CreateQTVRTrack(qtvrParams, qtvrParams->trackDuration, tmpMovie, &qtvrTrack);
    if (err != noErr)
        goto bail;
    
    /*  Create panorama track and media, and add them to the movie */
    err = CreatePanoTrackFromGWorlds(6, srcGWs, srcHSGWs, srcFSGWs, kQTVRCubicVersion1,
        qtvrParams, tmpMovie, qtvrTrack, &panoTrack
    );
    if (err != noErr)
        goto bail;
        
    /* Add a user data item that identifies the QTVR movie controller */
    err = SetQTControllerType(tmpMovie, kQTVRQTVRType);
    if (err != noErr)
        goto bail;
        
    /* Create the final, flattened movie, from the temporary file into a new movie file;
     * put the movie resource first so that FastStart is possible
     */
    err = FlattenQTVRMovie(tmpMovie, qtvrParams->flattenerFlags, qtvrParams->flattenerPreviewResolution, dstMovieSpec);
 
bail:
    if (tmpRefNum != -1)    CloseMovieFile(tmpRefNum);
    if (tmpMovie  != NULL)  DisposeMovie(tmpMovie);
    DeleteMovieFile(&tmpSpec);
    
    return(err);
}