//////////
//
//	File:		VRMakeObject.c
//
//	Contains:	Code for creating a QuickTime VR object movie from a linear QuickTime movie.
//
//	Written by:	Tim Monroe
//				Based on MakeQTVRObject code by Pete Falco and Michael Chen (and others?).
//
//	Copyright:	© 1991-1998 by Apple Computer, Inc., all rights reserved.
//
//	Change History (most recent first):
//
//	   <6>	 	03/21/00	rtm		made changes to get things running under CarbonLib
//	   <5>	 	02/01/99	rtm		reworked prompt and filename handling to remove "\p" sequences
//	   <4>	 	09/30/98	rtm		tweaked call to AddMovieResource to create single-fork movies;
//									tweaked call to FlattenMovieData to enable FastStart option
//	   <3>	 	01/22/98	rtm		version 2.0 objects working on MacOS and Windows
//	   <2>	 	01/21/98	rtm		version 1.0 objects working on MacOS and Windows
//	   <1>	 	01/20/98	rtm		first file from QTVRObjectAuthoring.c in MakeQTVRObject 1.0b2
//
//	This file contains functions that convert a linear QuickTime movie into a QuickTime VR object movie.
//	Here we can create both version 1.0 and version 2.0 QTVR object movies.
//
//
//	VERSION 2.0 FILE FORMAT
//
//	The definitive source of information about creating QTVR 2.0 object 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 objects:
//
//	An object movie is a QuickTime movie that contains at least three tracks: a QTVR track, an object
//	track, and an object 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.
//
//	An object movie also contains a single "object track", which contains information specific to the
//	object nodes in a scene. An object track has a media sample for each media sample in the QTVR track.
//	As a result, our object track will have one sample. The QTVRObjectSampleAtom structure defines the media
//	sample data. 
//
//	The actual image data for an object node is contained in an "object image track". The individual
//	frames in that track are various views of the object. There may also be a "hot spot image track" that
//	contains the hot spot images. This sample code does not create hot spot image tracks.
//
//	So, our general strategy, given a linear QuickTime movie, is as follows:
//		(1) Create a new, empty movie. Call this movie the "QTVR movie".
//		(2) Create a QTVR track and its associated media.
//		(3) Create a VR world atom container; this is stored in the sample description for the QTVR track.
//		(4) Create a node information atom container for each node; this is stored as a media sample
//	        in the QTVR track.
//		(5) Create an object track and add it to the movie.
//		(6)	Create an object image track by copying the video track from the QuickTime movie to the QTVR movie.
//		(7) Set up track references from the QTVR track to the object track, and from the object track
//	        to the object image track.
//		(8) Add a user data item that identifies the QTVR movie controller.
//		(9) Flatten the QTVR movie into the final object movie.
//
//
//	VERSION 1.0 FILE FORMAT
//
//	The definitive source of information about creating QTVR version 1.0 object movies is Technote 1036,
//	"QuickTime VR 1.0 Object Movie File Format" released in March 1996, available online at the address
//	<http://devworld.apple.com/dev/technotes/tn/tn1036.html>. Here is a condensed version of the info
//	in that technote:
//
//	For version 1.0 object movies, the file format is quite simple. A single-node object movie contains
//	an "object video track", an active video track that contains the various views of the object in the
//	movie frames. An object video track is essentially just a standard QuickTime video track and is the
//	same as the version 2 object image tack.
//
//	What distinguishes an object movie from a standard linear QuickTime movie is the manner in which
//	the frames of the video track are displayed to the user. This is determined by a special piece of
//	user data stored in the object movie file, which selects the QuickTime VR movie controller.
//
//	Various display parameters of the object movie (for instance, the default pan angle) are contained in
//	another piece of user data, of type 'NAVG'. The data in this user data item is structured according
//	to the QTVRObjectFileFormat1x0Record structure.
//
//	A QuickTime VR object movie can also contain a movie poster of the object and a movie file preview.
//	A movie poster is a single view of the object that can be used to represent the object. A poster is
//	defined by specifying a time in the object video track. In general, the poster view should be the same
//	as the initial object view specified in the 'NAVG' user data item. A movie file preview is some part
//	of the object movie that is displayed in order to give the user an idea of what's in the entire movie
//	(for instance, in Standard File Package dialog boxes).
//
//	Version 1.0 object movies do not support hot spots.
//
//	So, our general strategy, given a linear QuickTime movie, is as follows:
//		(1) Create a new, empty movie. Call this movie the "QTVR movie".
//		(2)	Create an object video track by copying the video track from the linear movie to the QTVR movie.
//		(3) Add a user data item of type 'NAVG' to the QTVR movie that specifies object parameters.
//		(4)	Add a user data item of type 'ctyp' that identifies the QTVR movie controller.
//		(5) Set the poster time to the desired view of the object.
//		(6) Create a movie file preview and add it to the movie.
//		(7) Flatten the QTVR movie into the final object movie.
//
//
//	NOTES:
//
//	*** (1) ***
//	The routines in this file use lots of hard-coded values. A real-life application would want to elicit the
//	actual values for a specific object movie from the user. (Hey, this is only sample code!)
//
//	*** (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 VRObject_CreateVRWorld for some examples.
//	Similarly, data in the version 1.0 'NAVG' user data item must be big-endian.
//
//////////

#include "VRMakeObject.h"

UInt32					gVersionToCreate = kQTVRVersion2;		// the version of the file format we create


//////////
//
// VRObject_PromptUserForMovieFileAndMakeObject
// Let the user select a linear QuickTime movie file, then make a QTVR object movie from it.
//
//////////

void VRObject_PromptUserForMovieFileAndMakeObject (void)
{
	OSType 					myTypeList = MovieFileType;
	short					myNumTypes = 1;
	FSSpec					myMoovSpec;
	FSSpec					myDestSpec;
	StringPtr 				myMoviePrompt = QTUtils_ConvertCToPascalString(kObjSaveMoviePrompt);
	StringPtr 				myMovieFileName = QTUtils_ConvertCToPascalString(kObjSaveMovieFileName);
	Boolean					myIsSelected;
	Boolean					myIsReplacing;
	QTFrameFileFilterUPP	myFilterUPP = QTFrame_GetFileFilterUPP((ProcPtr)VRObject_FileFilterFunction);
	OSErr					myErr = noErr;

#if TARGET_OS_MAC
	myNumTypes = 0;
#endif

	// have the user select a linear QuickTime movie file
	myErr = QTFrame_GetOneFileWithPreview(myNumTypes, (QTFrameTypeListPtr)&myTypeList, &myMoovSpec, myFilterUPP);
	if (myErr != noErr)
		goto bail;
	
	// have the user select the name of the new object movie file
	myErr = QTFrame_PutFile(myMoviePrompt, myMovieFileName, &myDestSpec, &myIsSelected, &myIsReplacing);
	if (!myIsSelected)
		goto bail;

	// just do it...
	VRObject_MakeObjectMovie(&myMoovSpec, &myDestSpec, gVersionToCreate);

	// ...and let the user know we're done
	QTFrame_Beep();
	
bail:
	// now clean up after ourselves
	free(myMoviePrompt);
	free(myMovieFileName);
}


//////////
//
// VRObject_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.
//
//////////

OSErr VRObject_CreateVRWorld (QTAtomContainer *theVRWorld, QTAtomContainer *theNodeInfo, OSType theNodeType)
{
	QTAtomContainer			myVRWorld = NULL;
	QTAtomContainer			myNodeInfo = NULL;
	QTVRWorldHeaderAtom		myVRWorldHeaderAtom;
	QTAtom					myImagingParentAtom;
	QTAtom					myNodeParentAtom;
	QTAtom					myNodeAtom;
	QTVRPanoImagingAtom		myPanoImagingAtom;
	QTVRNodeLocationAtom	myNodeLocationAtom;
	QTVRNodeHeaderAtom		myNodeHeaderAtom;
	UInt16					myIndex;
	OSErr					myErr = noErr;

	//////////
	//
	// create a VR world atom container
	//
	//////////

	myErr = QTNewAtomContainer(&myVRWorld);
	if (myErr != 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 (false) {
		Str255				myStr = "\pMy Scene";
		QTAtomID			myID;
		
		myErr = VRObject_AddStr255ToAtomContainer(myVRWorld, kParentAtomIsContainer, myStr, &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)
	myErr = QTInsertChild(myVRWorld, kParentAtomIsContainer, kQTVRWorldHeaderAtomType, 1, 1, sizeof(QTVRWorldHeaderAtom), &myVRWorldHeaderAtom, NULL);
	if (myErr != 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)
	//
	//////////
	
	myErr = QTInsertChild(myVRWorld, kParentAtomIsContainer, kQTVRImagingParentAtomType, 1, 1, 0, NULL, &myImagingParentAtom);
	if (myErr != 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);
	for (myIndex = 0; myIndex < 6; myIndex++)
		myPanoImagingAtom.imagingProperties[myIndex] = EndianU32_NtoB(0L);
	myPanoImagingAtom.reserved1 = EndianU32_NtoB(0L);
	myPanoImagingAtom.reserved2 = EndianU32_NtoB(0L);
	
	// add a panorama imaging atom for kQTVRMotion state
	myPanoImagingAtom.quality = EndianU32_NtoB(codecLowQuality);
	myPanoImagingAtom.directDraw = EndianU32_NtoB(true);
	myPanoImagingAtom.imagingMode = EndianU32_NtoB(kQTVRMotion);
	myErr = QTInsertChild(myVRWorld, myImagingParentAtom, kQTVRPanoImagingAtomType, 0, 0, sizeof(QTVRPanoImagingAtom), &myPanoImagingAtom, NULL);
	if (myErr != noErr)
		goto bail;
		
	// add a panorama imaging atom for kQTVRStatic state
	myPanoImagingAtom.quality = EndianU32_NtoB(codecHighQuality);
	myPanoImagingAtom.directDraw = EndianU32_NtoB(false);
	myPanoImagingAtom.imagingMode = EndianU32_NtoB(kQTVRStatic);
	myErr = QTInsertChild(myVRWorld, myImagingParentAtom, kQTVRPanoImagingAtomType, 0, 0, sizeof(QTVRPanoImagingAtom), &myPanoImagingAtom, NULL);
	if (myErr != noErr)
		goto bail;
		
	//////////
	//
	// add a node parent atom to the VR world and insert node ID atoms into it
	//
	//////////
	
	myErr = QTInsertChild(myVRWorld, kParentAtomIsContainer, kQTVRNodeParentAtomType, 1, 1, 0, NULL, &myNodeParentAtom);
	if (myErr != noErr)
		goto bail;
		
	// add a node ID atom
	myErr = QTInsertChild(myVRWorld, myNodeParentAtom, kQTVRNodeIDAtomType, kDefaultNodeID, 0, 0, 0, &myNodeAtom);
	if (myErr != 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
	myErr = QTInsertChild(myVRWorld, myNodeAtom, kQTVRNodeLocationAtomType, 1, 1, sizeof(QTVRNodeLocationAtom), &myNodeLocationAtom, NULL);
	if (myErr != noErr)
		goto bail;
	
	//////////
	//
	// create a node information atom container and add a node header atom to it
	//
	//////////
	
	myErr = QTNewAtomContainer(&myNodeInfo);
	if (myErr != 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 (false) {
		Str255				myStr = "\pMy Node";
		QTAtomID			myID;
		
		myErr = VRObject_AddStr255ToAtomContainer(myNodeInfo, kParentAtomIsContainer, myStr, &myID);
		myNodeHeaderAtom.nameAtomID = EndianU32_NtoB(myID);
	} else
		myNodeHeaderAtom.nameAtomID = EndianU32_NtoB(0L);
	
	// insert the node header atom into the node info atom container
	myErr = QTInsertChild(myNodeInfo, kParentAtomIsContainer, kQTVRNodeHeaderAtomType, 1, 1, sizeof(QTVRNodeHeaderAtom), &myNodeHeaderAtom, NULL);
	if (myErr != noErr)
		goto bail;
	
	//////////
	//
	// create hot spot atoms and add them to the node information atom container
	// [left as an exercise for the reader]
	//
	//////////
	
bail:
	// return the atom containers that we've created and configured here
	*theVRWorld = myVRWorld;
	*theNodeInfo = myNodeInfo;
	
	return(myErr);
}


//////////
//
// VRObject_CreateObjectTrack
// Configure the specified object track. Note that theSrcMovie is the linear QuickTime movie.
//
//////////

OSErr VRObject_CreateObjectTrack (Movie theSrcMovie, Track theObjectTrack, Media theObjectMedia)
{
	SampleDescriptionHandle		mySampleDesc = NULL;
	QTAtomContainer				myObjectSample;
	QTVRObjectSampleAtom		myObjectSampleData;
	TimeValue					myDuration;
	TimeValue					myCurrTime;
	Float32						myInitialPan, myInitialTilt;
	OSErr						myErr = noErr;

	//////////
	//
	// get some information from the linear QuickTime movie
	//
	//////////
	
	// get the duration of a single video frame
	GetMovieNextInterestingTime(theSrcMovie, nextTimeMediaSample, 0, NULL, (TimeValue)0, fixed1, NULL, &myDuration);

	// get the movie's current time, and convert it to an initial pan/tilt pair
	myCurrTime = GetMovieTime(theSrcMovie, NULL);
	
	VRObject_GetPanAndTiltFromTime(myCurrTime,
									kDefaultFrameDuration,
									kDefaultNumOfColumns,
									kDefaultNumOfRows,
									kDefaultLoopSize,
									kDefaultStartPan,
									kDefaultEndPan,
									kDefaultStartTilt,
									kDefaultEndTilt,
									&myInitialPan, &myInitialTilt);

	//////////
	//
	// add a media sample to the object track
	//
	//////////
	
	// create a sample description; this contains no real info, but AddMediaSample requires it
	mySampleDesc = (SampleDescriptionHandle)NewHandleClear(sizeof(SampleDescription));

	myErr = QTNewAtomContainer(&myObjectSample);
	if (myErr != noErr)
		goto bail;
	
	myObjectSampleData.majorVersion = EndianU16_NtoB(kQTVRMajorVersion);
	myObjectSampleData.minorVersion = EndianU16_NtoB(kQTVRMinorVersion);
	
	myObjectSampleData.movieType = EndianU16_NtoB(kDefaultMovieType);
	myObjectSampleData.viewStateCount = EndianU16_NtoB(kDefaultViewStateCount);
	myObjectSampleData.defaultViewState = EndianU16_NtoB(kDefaultDefaultViewState);
	myObjectSampleData.mouseDownViewState = EndianU16_NtoB(kDefaultMouseDownViewState);
	
	myObjectSampleData.viewDuration = EndianU32_NtoB(myDuration);
	myObjectSampleData.columns = EndianU32_NtoB((UInt32)kDefaultNumOfColumns);
	myObjectSampleData.rows = EndianU32_NtoB((UInt32)kDefaultNumOfRows);
	
	myObjectSampleData.mouseMotionScale = kDefaultMouseMotionScale;
	myObjectSampleData.minPan = kDefaultStartPan;
	myObjectSampleData.maxPan = kDefaultEndPan;
	myObjectSampleData.defaultPan = myInitialPan;
	myObjectSampleData.minTilt = kDefaultStartTilt;
	myObjectSampleData.maxTilt = kDefaultEndTilt;
	myObjectSampleData.defaultTilt = myInitialTilt;
	myObjectSampleData.minFieldOfView = kDefaultMinFieldOfView;
	myObjectSampleData.fieldOfView = kDefaultFieldOfView;
	myObjectSampleData.defaultFieldOfView = kDefaultFieldOfView;
	myObjectSampleData.defaultViewCenterH = kDefaultDefaultViewCenterH;
	myObjectSampleData.defaultViewCenterV = kDefaultDefaultViewCenterV;
	myObjectSampleData.viewRate = kDefaultViewRate;
	myObjectSampleData.frameRate = kDefaultFrameRate;

	VRObject_ConvertFloatToBigEndian(&myObjectSampleData.mouseMotionScale);
	VRObject_ConvertFloatToBigEndian(&myObjectSampleData.minPan);
	VRObject_ConvertFloatToBigEndian(&myObjectSampleData.maxPan);
	VRObject_ConvertFloatToBigEndian(&myObjectSampleData.defaultPan);
	VRObject_ConvertFloatToBigEndian(&myObjectSampleData.minTilt);
	VRObject_ConvertFloatToBigEndian(&myObjectSampleData.maxTilt);
	VRObject_ConvertFloatToBigEndian(&myObjectSampleData.defaultTilt);
	VRObject_ConvertFloatToBigEndian(&myObjectSampleData.minFieldOfView);
	VRObject_ConvertFloatToBigEndian(&myObjectSampleData.fieldOfView);
	VRObject_ConvertFloatToBigEndian(&myObjectSampleData.defaultFieldOfView);
	VRObject_ConvertFloatToBigEndian(&myObjectSampleData.defaultViewCenterH);
	VRObject_ConvertFloatToBigEndian(&myObjectSampleData.defaultViewCenterV);
	VRObject_ConvertFloatToBigEndian(&myObjectSampleData.viewRate);
	VRObject_ConvertFloatToBigEndian(&myObjectSampleData.frameRate);

	myObjectSampleData.animationSettings = EndianU32_NtoB(kDefaultAnimationSettings);
	myObjectSampleData.controlSettings = EndianU32_NtoB(kDefaultControlSettings); 
	
	// insert the object sample atom into the object sample atom container
	myErr = QTInsertChild(myObjectSample, kParentAtomIsContainer, kQTVRObjectInfoAtomType, 1, 1, sizeof(QTVRObjectSampleAtom), &myObjectSampleData, NULL);
	if (myErr != noErr)
		goto bail;
	
	// get the duration of the object image track (which is the same as the duration of the linear video track)
	myDuration = GetMovieDuration(theSrcMovie);
	
	// create the media sample
	BeginMediaEdits(theObjectMedia);

	myErr = AddMediaSample(theObjectMedia, (Handle)myObjectSample, 0, GetHandleSize((Handle)myObjectSample), myDuration, (SampleDescriptionHandle)mySampleDesc, 1, 0, NULL);
	if (myErr != noErr)
		goto bail;

	EndMediaEdits(theObjectMedia);

	// add the media to the track
	myErr = InsertMediaIntoTrack(theObjectTrack, 0, 0, myDuration, fixed1);
	
bail:
	return(myErr);
}


//////////
//
// VRObject_CreateQTVRMovieVers1x0
// Create a single-node QuickTime VR object movie from the specified QuickTime movie.
//
// NOTE: This function builds a movie that conforms to version 1.0 of the QuickTime VR file format.
//
//////////

OSErr VRObject_CreateQTVRMovieVers1x0 (FSSpec *theObjMovSpec, FSSpec *theSrcMovSpec)
{
	QTVRObjectFileFormat1x0Ptr		myObjFormatPtr = NULL;
	Movie							myObjMovie = NULL;
	Movie							mySrcMovie = NULL;
 	short							myObjResRefNum = 0;
	short							mySrcResRefNum = 0;
	short							myResID = movieInDataForkResID;
	long							myFlags = createMovieFileDeleteCurFile | createMovieFileDontCreateResFile;
	UserData						myUserData;
	TimeValue						myDuration;
	TimeValue						myCurrTime;
	Float32							myInitialPan, myInitialTilt;
	Track							myImageTrack;
	OSErr							myErr = noErr;
	
	//////////
	//
	// create a new movie
	//
	//////////

	// create a movie file for the destination movie
	myErr = CreateMovieFile(theObjMovSpec, FOUR_CHAR_CODE('TVOD'), smCurrentScript, myFlags, &myObjResRefNum, &myObjMovie);
	if (myErr != noErr)
		goto bail;

	//////////
	//
	// copy the video track from the linear movie to the new movie; this is the "object video track"
	//
	//////////

	// open the source linear movie file
	myErr = OpenMovieFile(theSrcMovSpec, &mySrcResRefNum, fsRdPerm);
	if (myErr != noErr)
		goto bail;
	
	myErr = NewMovieFromFile(&mySrcMovie, mySrcResRefNum, NULL, 0, 0, 0);
	if (myErr != noErr)
		goto bail;
	
	SetMoviePlayHints(mySrcMovie, hintsHighQuality, hintsHighQuality);

	// copy the video track from the linear movie to the object movie
	myErr = VRObject_ImportVideoTrack(mySrcMovie, myObjMovie, &myImageTrack);
	if (myErr != noErr)
		goto bail;
	
	//////////
	//
	// get some information from the linear QuickTime movie
	//
	//////////
	
	// get the duration of a single video frame
	GetMovieNextInterestingTime(mySrcMovie, nextTimeMediaSample, 0, NULL, (TimeValue)0, fixed1, NULL, &myDuration);

	// get the movie's current time, and convert it to an initial pan/tilt pair
	myCurrTime = GetMovieTime(mySrcMovie, NULL);
	
	VRObject_GetPanAndTiltFromTime(myCurrTime,
									kDefaultFrameDuration,
									kDefaultNumOfColumns,
									kDefaultNumOfRows,
									kDefaultLoopSize,
									kDefaultStartPan,
									kDefaultEndPan,
									kDefaultStartTilt,
									kDefaultEndTilt,
									&myInitialPan, &myInitialTilt);
	//////////
	//
	// add a user data item of type 'NAVG' to the QTVR movie
	//
	//////////
	
	// create an object file format record
 	myObjFormatPtr = (QTVRObjectFileFormat1x0Ptr)NewPtrClear(sizeof(QTVRObjectFileFormat1x0Record));
	if (myObjFormatPtr == NULL)
		goto bail;

	// fill in the object file format record
	(*myObjFormatPtr).versionNumber = EndianU16_NtoB(1);
	(*myObjFormatPtr).numberOfColumns = EndianU16_NtoB(kDefaultNumOfColumns);
	(*myObjFormatPtr).numberOfRows = EndianU16_NtoB(kDefaultNumOfRows);
	(*myObjFormatPtr).reserved1 = EndianU16_NtoB(0);
	(*myObjFormatPtr).loopSize = EndianU16_NtoB(kDefaultLoopSize);
	(*myObjFormatPtr).frameDuration	= EndianU16_NtoB((short)myDuration);
	(*myObjFormatPtr).movieType = EndianU16_NtoB(kDefaultMovieType);
	(*myObjFormatPtr).loopTicks = EndianU16_NtoB(kDefaultLoopTicks);
	
	(*myObjFormatPtr).fieldOfView = EndianS32_NtoB(FloatToFixed(kDefaultFieldOfView));
	(*myObjFormatPtr).startHPan = EndianS32_NtoB(FloatToFixed(kDefaultStartPan));
	(*myObjFormatPtr).endHPan = EndianS32_NtoB(FloatToFixed(kDefaultEndPan));
	(*myObjFormatPtr).endVPan = EndianS32_NtoB(FloatToFixed(kDefaultEndTilt));
	(*myObjFormatPtr).startVPan = EndianS32_NtoB(FloatToFixed(kDefaultStartTilt));
	(*myObjFormatPtr).initialHPan = EndianS32_NtoB(FloatToFixed(myInitialPan));
	(*myObjFormatPtr).initialVPan = EndianS32_NtoB(FloatToFixed(myInitialTilt));
	(*myObjFormatPtr).reserved2 = EndianU32_NtoB(0L);
	
	// get the movie's user data list
	myUserData = GetMovieUserData(myObjMovie);
	if (myUserData == NULL) {
		myErr = userDataItemNotFound;
		goto bail;
	}
	
	// add the object file format data as a user data item to the movie
	myErr = SetUserDataItem(myUserData, myObjFormatPtr, sizeof(QTVRObjectFileFormat1x0Record), kObjectFormat1x0DataType, 0);
	if (myErr != noErr)
		goto bail;
	
	//////////
	//
	// add a user data item that identifies the QTVR movie controller
	//
	//////////
	
	myErr = VRObject_SetControllerType(myObjMovie, kQTVROldObjectType);
	if (myErr != noErr)
		goto bail;
		
	//////////
	//
	// set the movie's poster time, current time, and preview
	//
	//////////
	
	// set the poster time to the linear movie's current time
	SetMoviePosterTime(myObjMovie, myCurrTime);

	// set the movie's current time to the poster view time
	SetMovieTimeValue(myObjMovie, myCurrTime);

	// make a movie preview with duration equal to the animation loop
	SetMoviePreviewTime(myObjMovie, myCurrTime, (*myObjFormatPtr).frameDuration * (*myObjFormatPtr).loopSize);

	//////////
	//
	// add the movie resource to the object movie
	//
	//////////
	
	myErr = AddMovieResource(myObjMovie, myObjResRefNum, &myResID, NULL);
	
bail:
	if (myObjFormatPtr != NULL)
		DisposePtr((Ptr)myObjFormatPtr);
	
	if (mySrcResRefNum != 0)
		CloseMovieFile(mySrcResRefNum);
	
	if (mySrcMovie != NULL)
		DisposeMovie(mySrcMovie);
		
	if (myObjResRefNum != 0)
		CloseMovieFile(myObjResRefNum);
	
	if (myObjMovie != NULL)
		DisposeMovie(myObjMovie);
		
	return(myErr);
}


//////////
//
// VRObject_CreateQTVRMovieVers2x0
// Create a single-node QuickTime VR object movie from the specified QuickTime movie.
//
// NOTE: This function builds a movie that conforms to version 2.0 of the QuickTime VR file format.
//
//////////

OSErr VRObject_CreateQTVRMovieVers2x0 (FSSpec *theObjMovSpec, FSSpec *theSrcMovSpec)
{
	Handle							myHandle = NULL;
	SampleDescriptionHandle			mySampleDesc = NULL;
	QTVRSampleDescriptionHandle		myQTVRDesc = NULL;
	QTAtomContainer					myVRWorld;
	QTAtomContainer					myNodeInfo;
	Movie							myObjMovie = NULL;
	Movie							mySrcMovie = NULL;
 	short							myObjResRefNum = 0;
	short							mySrcResRefNum = 0;
	short							myResID = movieInDataForkResID;
	Track							myQTVRTrack = NULL;
	Media							myQTVRMedia = NULL;
	Track							myObjectTrack = NULL;
	Media							myObjectMedia = NULL;
	Track							myImageTrack = NULL;
	long							mySize;
	long							myFlags = createMovieFileDeleteCurFile | createMovieFileDontCreateResFile;
	TimeValue						myDuration;
	TimeScale						myScale;
	Fixed							myWidth, myHeight;
	OSErr							myErr = noErr;
	
	//////////
	//
	// create a new movie
	//
	//////////

	// create a movie file for the destination movie
	myErr = CreateMovieFile(theObjMovSpec, FOUR_CHAR_CODE('TVOD'), smCurrentScript, myFlags, &myObjResRefNum, &myObjMovie);
	if (myErr != noErr)
		goto bail;

	//////////
	//
	// copy the video track from the linear movie to the new movie; this is the "object image track"
	//
	//////////

	// open the source linear movie file
	myErr = OpenMovieFile(theSrcMovSpec, &mySrcResRefNum, fsRdPerm);
	if (myErr != noErr)
		goto bail;
	
	myErr = NewMovieFromFile(&mySrcMovie, mySrcResRefNum, NULL, 0, 0, 0);
	if (myErr != noErr)
		goto bail;
	
	SetMoviePlayHints(mySrcMovie, hintsHighQuality, hintsHighQuality);

	// copy the video track from the linear movie to the object movie
	myErr = VRObject_ImportVideoTrack(mySrcMovie, myObjMovie, &myImageTrack);
	if (myErr != noErr)
		goto bail;
	
	//////////
	//
	// get some information from the linear QuickTime movie
	//
	//////////
	
	// get the duration and dimensions of the object image track
	myDuration = GetTrackDuration(myImageTrack);
	GetTrackDimensions(myImageTrack, &myWidth, &myHeight);
	myScale = GetMediaTimeScale(GetTrackMedia(myImageTrack));
	
	//////////
	//
	// create the QTVR movie track and media
	//
	//////////

	myQTVRTrack = NewMovieTrack(myObjMovie, myWidth, myHeight, kFullVolume);
	myQTVRMedia = NewTrackMedia(myQTVRTrack, kQTVRQTVRType, myScale, NULL, 0);
	if ((myQTVRTrack == NULL) || (myQTVRMedia == NULL))
		goto bail;
		
	// 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
	myErr = VRObject_CreateVRWorld(&myVRWorld, &myNodeInfo, kQTVRObjectType);
	if (myErr != noErr)
		goto bail;
		
	if ((myVRWorld == NULL) || (myNodeInfo == NULL))
		goto bail;
	
	// create a QTVR sample description
	mySize = sizeof(QTVRSampleDescription) + GetHandleSize((Handle)myVRWorld) - sizeof(long);
	myQTVRDesc = (QTVRSampleDescriptionHandle)NewHandleClear(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
	BlockMove(*((Handle)myVRWorld), &((**myQTVRDesc).data), GetHandleSize((Handle)myVRWorld));
	
	// create the media sample
	BeginMediaEdits(myQTVRMedia);

	myErr = AddMediaSample(myQTVRMedia, (Handle)myNodeInfo, 0, GetHandleSize((Handle)myNodeInfo), myDuration, (SampleDescriptionHandle)myQTVRDesc, 1, 0, NULL);
	if (myErr != noErr)
		goto bail;

	EndMediaEdits(myQTVRMedia);
	
	// add the media to the track
	InsertMediaIntoTrack(myQTVRTrack, 0, 0, myDuration, fixed1);
	
	//////////
	//
	// create an object track and add it to the movie
	//
	//////////
	
	// create object track and media
	myObjectTrack = NewMovieTrack(myObjMovie, myWidth, myHeight, 0);
	myObjectMedia = NewTrackMedia(myObjectTrack, kQTVRObjectType, myScale, NULL, 0);
	if ((myObjectTrack == NULL) || (myObjectMedia == NULL))
		goto bail;
	
	myErr = VRObject_CreateObjectTrack(mySrcMovie, myObjectTrack, myObjectMedia);
	if (myErr != noErr)
		goto bail;
		
	//////////
	//
	// create track references from QTVR track to object track
	// and from the object track to the object image track
	//
	//////////
	
	if (myObjectTrack != NULL)
		AddTrackReference(myQTVRTrack, myObjectTrack, kQTVRObjectType, NULL);
		
	if (myImageTrack != NULL)
		AddTrackReference(myObjectTrack, myImageTrack, kQTVRImageTrackRefType, NULL);

	//////////
	//
	// add a user data item that identifies the QTVR movie controller
	//
	//////////
	
	myErr = VRObject_SetControllerType(myObjMovie, kQTVRQTVRType);
	if (myErr != noErr)
		goto bail;
		
	//////////
	//
	// add the movie resource to the object movie
	//
	//////////
	
	myErr = AddMovieResource(myObjMovie, myObjResRefNum, &myResID, NULL);
	
bail:
	if (mySampleDesc != NULL)
		DisposeHandle((Handle)mySampleDesc);
	
	if (myQTVRDesc != NULL)
		DisposeHandle((Handle)myQTVRDesc);
	
	if (myVRWorld != NULL)
		QTDisposeAtomContainer(myVRWorld);
		
	if (myNodeInfo != NULL)
		QTDisposeAtomContainer(myNodeInfo);

	if (myObjResRefNum != 0)
		CloseMovieFile(myObjResRefNum);
	
	if (myObjMovie != NULL)
		DisposeMovie(myObjMovie);
		
	if (mySrcResRefNum != 0)
		CloseMovieFile(mySrcResRefNum);
		
	if (mySrcMovie != NULL) 
		DisposeMovie(mySrcMovie);
		
	return(myErr);
}


//////////
//
// VRObject_MakeObjectMovie
// Create a single-node QuickTime VR object movie from the specified linear QuickTime movie file.
//
//////////

OSErr VRObject_MakeObjectMovie (FSSpec *theMovieSpec, FSSpec *theDestSpec, long theVersion)
{
	FSSpec						myTempSpec;
	Movie						myTempMovie = NULL;
	Movie						myObjectMovie = NULL;
	short						myTempResRefNum = 0;
	OSErr						myErr = noErr;

	// create a temporary version of the object movie file,
	// located in the same directory as the destination object movie file;
	// to create a new file name, we'll just change the last character of the destination movie file name
	// (no doubt you could do a better job here!)
	myTempSpec = *theDestSpec;
	
	if (myTempSpec.name[myTempSpec.name[0]] == 't')
		myTempSpec.name[myTempSpec.name[0]] = '@';
	else
		myTempSpec.name[myTempSpec.name[0]] = 't';
	
	// create a single node object movie in the temp file
	if (theVersion == kQTVRVersion1)
		myErr = VRObject_CreateQTVRMovieVers1x0(&myTempSpec, theMovieSpec);
	else if (theVersion == kQTVRVersion2)
		myErr = VRObject_CreateQTVRMovieVers2x0(&myTempSpec, theMovieSpec);
		
	if (myErr != noErr)
		goto bail;

	// create the final, flattened movie
	myErr = OpenMovieFile(&myTempSpec, &myTempResRefNum, fsRdPerm);
	if (myErr != noErr)
		goto bail;
				
	myErr = NewMovieFromFile(&myTempMovie, myTempResRefNum, NULL, 0, 0, 0);
	if (myErr != noErr)
		goto bail;
				
	// flatten the temporary file into a new movie file;
	// put the movie resource first so that FastStart is possible
	myObjectMovie = FlattenMovieData(myTempMovie, flattenDontInterleaveFlatten | flattenAddMovieToDataFork | flattenForceMovieResourceBeforeMovieData, theDestSpec, FOUR_CHAR_CODE('TVOD'), smSystemScript, createMovieFileDeleteCurFile | createMovieFileDontCreateResFile);

bail:	
	if (myObjectMovie != NULL)
		DisposeMovie(myObjectMovie);

	if (myTempMovie != NULL)
		DisposeMovie(myTempMovie);
		
	if (myTempResRefNum != 0)
		CloseMovieFile(myTempResRefNum);
		
	DeleteMovieFile(&myTempSpec);
					
	return(myErr);
}


//////////
//
// VRObject_ImportVideoTrack
// Copy a video track from one movie (the source) to another (the destination).
//
//////////

OSErr VRObject_ImportVideoTrack (Movie theSrcMovie, Movie theDstMovie, Track *theImageTrack)
{
	Track			mySrcTrack = NULL;
	Media			mySrcMedia = NULL;
	Track			myDstTrack = NULL;
	Media			myDstMedia = NULL;
	Fixed			myWidth, myHeight;
	OSType			myType;
	OSErr			myErr = noErr;
	
	ClearMoviesStickyError();
	
	// get the first video track in the source movie
	mySrcTrack = GetMovieIndTrackType(theSrcMovie, 1, VideoMediaType, movieTrackMediaType);
	if (mySrcTrack == NULL)
		return(paramErr);
	
	// get the track's media and dimensions	
	mySrcMedia = GetTrackMedia(mySrcTrack);
	GetTrackDimensions(mySrcTrack, &myWidth, &myHeight);
	
	// create a destination track
	myDstTrack = NewMovieTrack(theDstMovie, myWidth, myHeight, GetTrackVolume(mySrcTrack));

	// create a destination media
	GetMediaHandlerDescription(mySrcMedia, &myType, 0, 0);
	myDstMedia = NewTrackMedia(myDstTrack, myType, GetMediaTimeScale(mySrcMedia), 0, 0);
		
	// copy the entire track
	InsertTrackSegment(mySrcTrack, myDstTrack, 0, GetTrackDuration(mySrcTrack), 0);
	CopyTrackSettings(mySrcTrack, myDstTrack);
	SetTrackLayer(myDstTrack, GetTrackLayer(mySrcTrack));

	// an object video track should always be enabled
	SetTrackEnabled(myDstTrack, true);

	if (theImageTrack != NULL)
		*theImageTrack = myDstTrack;

	return(GetMoviesStickyError());
}


//////////
//
// VRObject_GetPanAndTiltFromTime
// Get the pan and tilt angles that correspond to the specified movie time.
//
//////////

OSErr VRObject_GetPanAndTiltFromTime (TimeValue theTime,
										TimeValue theFrameDuration,
										short theNumColumns,
										short theNumRows,
										short theLoopSize,
										Float32 theStartPan,
										Float32 theEndPan,
										Float32 theStartTilt,
										Float32 theEndTilt,
										Float32 *thePan, 
										Float32 *theTilt)
{
	short			myRow, myColumn;
	TimeValue		myTime;
	Float32			myPanRange;
	Float32			myTiltRange;
	OSErr			myErr = noErr;
	
	myPanRange = theEndPan - theStartPan;
	myTiltRange = theStartTilt - theEndTilt;

	theTime /= theFrameDuration;				// adjust for frame duration
	
	myTime = theTime / theLoopSize;
	myRow = myTime / theNumColumns;
	myColumn = myTime % theNumColumns;
	
	// note the mixed Float32 and integer math
	if (theNumColumns == 1)
		*thePan = theStartPan;
	else if (myPanRange == 360.0)
		*thePan = theStartPan + (myColumn * (myPanRange / (theNumColumns)));
	else
		*thePan = theStartPan + (myColumn * (myPanRange / (theNumColumns - 1)));
	
	if (theNumRows == 1)
		*theTilt = theStartTilt;
	else
		*theTilt = theStartTilt - (myRow * (myTiltRange / (theNumRows - 1)));
	
	return(myErr);
}


//////////
//
// VRObject_SetControllerType
// 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).
//
//////////

OSErr VRObject_SetControllerType (Movie theMovie, OSType theType)
{
	UserData		myUserData;
	OSErr			myErr = 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);
	myErr = SetUserDataItem(myUserData, &theType, sizeof(theType), kQTControllerType, 0);

	return(myErr);
}


//////////
//
// VRObject_AddStr255ToAtomContainer
// Add a Pascal string to the specified atom container; return (through theID) the ID of the new string atom.
//
//////////

OSErr VRObject_AddStr255ToAtomContainer (QTAtomContainer theContainer, QTAtom theParent, Str255 theString, QTAtomID *theID)
{
	OSErr					myErr = noErr;

	*theID = 0;				// initialize the returned atom ID
	
	if ((theContainer == NULL) || (theParent == 0))
		return(paramErr);
		
	if (theString[0] != 0) {
		QTAtom				myStringAtom;
		UInt16				mySize;
		QTVRStringAtomPtr	myStringAtomPtr = NULL;
		
		mySize = sizeof(QTVRStringAtom) - 4 + theString[0] + 1;
		myStringAtomPtr = (QTVRStringAtomPtr)NewPtrClear(mySize);
		
		if (myStringAtomPtr != NULL) {
			myStringAtomPtr->stringUsage = EndianU16_NtoB(1);
			myStringAtomPtr->stringLength = EndianU16_NtoB(theString[0]);
			BlockMove(theString + 1, myStringAtomPtr->theString, theString[0]);
			myStringAtomPtr->theString[theString[0]] = '\0';
			myErr = QTInsertChild(theContainer, theParent, kQTVRStringAtomType, 0, 0, mySize, (Ptr)myStringAtomPtr, &myStringAtom);
			DisposePtr((Ptr)myStringAtomPtr);
			
			if (myErr == noErr)
				QTGetAtomTypeAndID(theContainer, myStringAtom, NULL, theID);
		}
	}
	
	return(myErr);
}


//////////
//
// VRObject_ConvertFloatToBigEndian
// Convert the specified floating-point number to big-endian format.
//
//////////

void VRObject_ConvertFloatToBigEndian (float *theFloat)
{
	unsigned long		*myLongPtr;
	
	myLongPtr = (unsigned long *)theFloat;
	*myLongPtr = EndianU32_NtoB(*myLongPtr);
}


//////////
//
// VRObject_FileFilterFunction
// Filter files for a file-opening dialog box.
//
//////////

#if TARGET_OS_MAC
PASCAL_RTN Boolean VRObject_FileFilterFunction (AEDesc *theItem, void *theInfo, void *theCallBackUD, NavFilterModes theFilterMode)
{
#pragma unused(theCallBackUD, theFilterMode)
	Boolean					myIsOkay = true;
	NavFileOrFolderInfo		*myInfo = (NavFileOrFolderInfo *)theInfo;
	
	if (theItem->descriptorType == typeFSS)
		if (!myInfo->isFolder)
			if (myInfo->fileAndFolder.fileInfo.finderInfo.fdType == MovieFileType)
				myIsOkay = true;

	return(myIsOkay);
}
#endif
#if TARGET_OS_WIN32
PASCAL_RTN Boolean VRObject_FileFilterFunction (CInfoPBPtr thePBPtr)
{
#pragma unused(thePBPtr)
	return(false);
}
#endif


