//////////
//
//	File:		VRMovies.c
//
//	Contains:	Support for QuickTime movie playback in VR nodes.
//
//	Written by:	Tim Monroe
//				Some code borrowed from QTVRSamplePlayer by Bryce Wolfson.
//
//	Copyright:	© 1996-1998 by Apple Computer, Inc., all rights reserved.
//
//	Change History (most recent first):
//
//	   <16>	 	03/20/00	rtm		made changes to get things running under CarbonLib
//	   <15>	 	06/23/99	rtm		added VRMoov_MacRGBToWinRGB and VRMoov_WinRGBToMacRGB
//	   <14>	 	06/22/99	rtm		more work on matrices; now everything works okay (AFAICT)
//	   <13>	 	06/21/99	rtm		added call to UpdateMovie to back buffer imaging procedure
//	   <12>	 	06/18/99	rtm		got movie matrices working correctly; added color picking to Windows
//	   <11>	 	06/17/99	rtm		reworked geometry of back buffer yet again; now we copy from the
//									offscreen GWorld to back buffer using use DecompressSequenceFrameS
//	   <10>	 	05/16/99	rtm		added VRMoov_DumpWindowData; further work on geometry of back buffer
//	   <9>	 	05/15/99	rtm		reworked geometry handling to support horizontal back buffer in QT 4.0
//	   <8>	 	05/04/98	rtm		added automatic rotation of movie
//	   <7>	 	03/06/97	rtm		started to implement video masking
//	   <6>	 	03/05/97	rtm		added VRMoov_SetChromaColor; added fChromaColor to app data record
//	   <5>	 	03/04/97	rtm		fixed compositing problems at back buffer edges
//	   <4>	 	03/03/97	rtm		added VRMoov_SetVideoGraphicsMode to handle compositing
//									without using an offscreen GWorld
//	   <3>	 	02/27/97	rtm		further development: borrowed some ideas from QTVRSamplePlayer;
//									added VRMoov_SetEmbeddedMovieWidth etc.
//	   <2>	 	12/12/96	rtm		further development: borrowed some ideas from BoxMoov demo 
//	   <1>	 	12/11/96	rtm		first file 
//	   
// This code draws the QuickTime movie frames into the back buffer, either directly
// or indirectly (via an offscreen GWorld). Direct drawing gives the best performance,
// but indirect drawing is necessary for some visual effects. 
//
//////////

// TO DO:
// + finish video masking by custom 'hide' hot spots (so video goes *behind* such hot spots)
// + verify that everything works okay if *images* are used instead of movies (scaling seems to be a problem)


//////////
//	   
// header files
//	   
//////////

#include "VRMovies.h"


//////////
//	   
// constants
//	   
//////////

const RGBColor			kClearColor = {0x0000, 0xffff, 0x0000};		// the default chroma key color
const RGBColor			kBlackColor = {0x0000, 0x0000, 0x0000};
const RGBColor			kWhiteColor = {0xffff, 0xffff, 0xffff};


//////////
//	   
// global variables
//	   
//////////

#if TARGET_OS_MAC
UserEventUPP			gColorFilterUPP = NULL;						// UPP to our custom color picker dialog event filter
#endif

//////////
//
// VRMoov_InitWindowData
// Initialize any window-specific data.
//
//////////

ApplicationDataHdl VRMoov_InitWindowData (WindowObject theWindowObject)
{
#pragma unused(theWindowObject)

	ApplicationDataHdl	myAppData;
	
	myAppData = (ApplicationDataHdl)NewHandleClear(sizeof(ApplicationDataRecord));
	if (myAppData != NULL) {
		(**myAppData).fMovie = NULL;
		(**myAppData).fOffscreenGWorld = NULL;
		(**myAppData).fOffscreenPixMap = NULL;
		(**myAppData).fPrevBBufGWorld = NULL;
		(**myAppData).fMovieCenter.x = 0.0;
		(**myAppData).fMovieCenter.y = 0.0;
		(**myAppData).fMovieScale = 1.0;
		(**myAppData).fMovieWidth = kDefaultEmbMovieWidth;
		(**myAppData).fUseOffscreenGWorld = false;
		(**myAppData).fUseMovieCenter = true;
		(**myAppData).fQTMovieHasSound = false;
		(**myAppData).fCompositeMovie = false;
		(**myAppData).fUseHideRegion = false;
		(**myAppData).fChromaColor = kClearColor;
		(**myAppData).fHideRegion = NULL;
		(**myAppData).fBackBufferIsHoriz = false;
		(**myAppData).fImageDesc = NULL;
		(**myAppData).fImageSequence = 0;
		
		SetIdentityMatrix(&(**myAppData).fMovieMatrix);
		SetIdentityMatrix(&(**myAppData).fOrigMovieMatrix);
		
		// create a new routine descriptor
		(**myAppData).fBackBufferProc = NewQTVRBackBufferImagingUPP(VRMoov_BackBufferImagingProc);
	}

#if TARGET_OS_MAC	
	if (gColorFilterUPP == NULL)
		gColorFilterUPP = NewUserEventUPP(VRMoov_ColorDialogEventFilter);
#endif
		
	return(myAppData);
}


//////////
//
// VRMoov_DumpWindowData
// Dispose of any window-specific data.
//
//////////

void VRMoov_DumpWindowData (WindowObject theWindowObject)
{
	ApplicationDataHdl		myAppData = NULL;

	myAppData = (ApplicationDataHdl)QTFrame_GetAppDataFromWindowObject(theWindowObject);
	if (myAppData == NULL)
		return;

	VRMoov_DumpEmbeddedMovie(theWindowObject);
	
	DisposeQTVRBackBufferImagingUPP((**myAppData).fBackBufferProc);
}


///////////
//
// VRMoov_GetEmbeddedMovie
// Get the QuickTime movie to be embedded in a panorama.
// Returns a Boolean to indicate success (true) or failure (false).
//
//////////

Boolean VRMoov_GetEmbeddedMovie (WindowObject theWindowObject)
{
	FSSpec					myFSSpec;
	OSType 					myTypeList = kQTFileTypeMovie;
	OSErr					myErr = paramErr;

	// do some preliminary parameter checking
	if (theWindowObject == NULL)
		return(false);
	
	if (!QTFrame_IsWindowObjectOurs(theWindowObject))
		return(false);
	
	// elicit the movie file from user
	myErr = QTFrame_GetOneFileWithPreview(1, (QTFrameTypeListPtr)&myTypeList, &myFSSpec, NULL);
	if (myErr != noErr) {
		VRMoov_DumpEmbeddedMovie(theWindowObject); 	// clean up any existing embedded movie
		return(false);
	}
		
	return(VRMoov_LoadEmbeddedMovie(&myFSSpec, theWindowObject));
}


//////////
//
// VRMoov_LoadEmbeddedMovie
// Load the QuickTime movie in the specified file.
// Returns a Boolean to indicate success (true) or failure (false).
//
//////////

Boolean VRMoov_LoadEmbeddedMovie (FSSpec *theMovieFile, WindowObject theWindowObject)
{
	short					myMovieFileRef;
	Movie					myMovie;
	GWorldPtr				myGWorld = NULL;
	Rect					myRect;
	ApplicationDataHdl		myAppData = NULL;
	OSErr					myErr = paramErr;
		
	myAppData = (ApplicationDataHdl)QTFrame_GetAppDataFromWindowObject(theWindowObject);
	if (myAppData == NULL)
		goto bail;

	HLock((Handle)myAppData);
	
	//////////
	//
	// open the movie file and the movie
	//
	//////////
	
	myErr = OpenMovieFile(theMovieFile, &myMovieFileRef, fsRdPerm);
	if (myErr != noErr)
		goto bail;

	myErr = NewMovieFromFile(&myMovie, myMovieFileRef, NULL, (StringPtr)NULL, newMovieActive, NULL);
	if (myErr != noErr)
		goto bail;

	//////////
	//
	// get the movie geometry
	//
	//////////
	
	GetMovieBox(myMovie, &myRect);
	GetMovieMatrix(myMovie, &(**myAppData).fOrigMovieMatrix);
	MacOffsetRect(&myRect, -myRect.left, -myRect.top);
	SetMovieBox(myMovie, &myRect);

	// keep track of the movie and movie rectangle (in our app-specific data structure)
	(**myAppData).fMovie = myMovie;
	(**myAppData).fMovieBox = myRect;

	// determine the orientation of the back buffer
	(**myAppData).fBackBufferIsHoriz = QTVRUtils_IsBackBufferHorizontal((**theWindowObject).fInstance);
	
	// get rid of any existing offscreen graphics world
	if ((**myAppData).fOffscreenGWorld != NULL) {
		DisposeGWorld((**myAppData).fOffscreenGWorld);
		(**myAppData).fOffscreenGWorld = NULL;
	}
	
	// clear out any existing custom cover/uncover functions and reset the video media graphics mode
	// (these may have been modified for direct-screen drawing)
	SetMovieCoverProcs(myMovie, NULL, NULL, 0L);
	VRMoov_SetVideoGraphicsMode(myMovie, myAppData, false);
	
	//////////
	//
	// if necessary, create an offscreen graphics world
	//
	// this is where we'll image the movie before copying it into the back buffer
	// when we want to do special effects
	//
	//////////
	
	if ((**myAppData).fUseOffscreenGWorld) {
		myErr = NewGWorld(&myGWorld, 0, &myRect, NULL, NULL, 0);
		(**myAppData).fOffscreenGWorld = myGWorld;
		(**myAppData).fOffscreenPixMap = GetGWorldPixMap(myGWorld);
		
		// make an image description, which is needed by DecompressSequenceBegin
		LockPixels((**myAppData).fOffscreenPixMap);
		MakeImageDescriptionForPixMap((**myAppData).fOffscreenPixMap, &((**myAppData).fImageDesc));
		UnlockPixels((**myAppData).fOffscreenPixMap);
	} else {
		// set the video media graphics mode to drop out the chroma key color in a movie;
		// we also need to install an uncover function that doesn't erase the uncovered region
		if ((**myAppData).fCompositeMovie) {
			VRMoov_SetVideoGraphicsMode(myMovie, myAppData, true);
			SetMovieCoverProcs(myMovie, NewMovieRgnCoverUPP(VRMoov_UncoverProc), NULL, (long)theWindowObject);
		}
	}
	
	//////////
	//
	// install the back-buffer imaging procedure
	//
	//////////
	
	if ((**theWindowObject).fInstance != NULL)
		myErr = VRMoov_InstallBackBufferImagingProc((**theWindowObject).fInstance, theWindowObject);
		
	// start the movie playing in a loop
	VRMoov_LoopEmbeddedMovie(myMovie);

bail:
	// we don't want to edit the embedded movie, so we can close the movie file
	if (myMovieFileRef != 0)
		CloseMovieFile(myMovieFileRef);
		
	HUnlock((Handle)myAppData);

	return(myErr == noErr);
}
	
	
//////////
//
// VRMoov_LoopEmbeddedMovie
// Start the QuickTime movie playing in a loop.
//
//////////

void VRMoov_LoopEmbeddedMovie (Movie theMovie)
{
	TimeBase		myTimeBase;

	// throw the movie into loop mode
	myTimeBase = GetMovieTimeBase(theMovie);
	SetTimeBaseFlags(myTimeBase, GetTimeBaseFlags(myTimeBase) | loopTimeBase);

	// start playing the movie
	StartMovie(theMovie);
}
	

//////////
//
// VRMoov_DumpEmbeddedMovie
// Stop any existing embedded movie from playing and then clean up.
//
//////////

void VRMoov_DumpEmbeddedMovie (WindowObject theWindowObject)
{
	ApplicationDataHdl		myAppData;

	if (theWindowObject == NULL) 
		goto bail;
	
	myAppData = (ApplicationDataHdl)QTFrame_GetAppDataFromWindowObject(theWindowObject);
	if (myAppData == NULL)
		goto bail;
	
	// if we have an embedded movie, stop it from playing and dispose of it
	if ((**myAppData).fMovie != NULL) {
		StopMovie((**myAppData).fMovie);
		DisposeMovie((**myAppData).fMovie);
		(**myAppData).fMovie = NULL;
	}
	
	// get rid of any existing offscreen graphics world
	if ((**myAppData).fOffscreenGWorld != NULL) {
		DisposeGWorld((**myAppData).fOffscreenGWorld);
		(**myAppData).fOffscreenGWorld = NULL;
	}
	
	// clear the existing back buffer imaging proc
	QTVRSetBackBufferImagingProc((**theWindowObject).fInstance, NULL, 0, NULL, 0);
	
	// clear out any other movie-specific data
	(**myAppData).fPrevBBufGWorld = NULL;
	MacSetRect(&(**myAppData).fPrevBBufRect, 0, 0, 0, 0);
	
	MacSetRect(&(**myAppData).fMovieBox, 0, 0, 0, 0);
	SetIdentityMatrix(&(**myAppData).fMovieMatrix);
	SetIdentityMatrix(&(**myAppData).fOrigMovieMatrix);
	
	if ((**myAppData).fImageDesc != NULL) {
		DisposeHandle((Handle)(**myAppData).fImageDesc);
		(**myAppData).fImageDesc = NULL;
	}
	
	VRMoov_RemoveDecompSeq(theWindowObject);
	
	// make sure the back buffer is clean
	QTVRRefreshBackBuffer((**theWindowObject).fInstance, 0);

bail:
	return;
}
	

//////////
//
// VRMoov_InstallBackBufferImagingProc
// Install a back buffer imaging procedure.
// (This routine might sometimes be called to move or resize the area of interest within the panorama.)
//
//////////

OSErr VRMoov_InstallBackBufferImagingProc (QTVRInstance theInstance, WindowObject theWindowObject)
{
	ApplicationDataHdl		myAppData;
	QTVRAreaOfInterest		myArea;
	float					myWidth, myHeight;
	OSErr					myErr = noErr;

	//////////
	//
	// initialize; clean up any existing back buffer procedure
	//
	//////////
	
	if ((theInstance == NULL) || (theWindowObject == NULL)) 
		return(paramErr);

	myAppData = (ApplicationDataHdl)QTFrame_GetAppDataFromWindowObject(theWindowObject);
	if (myAppData == NULL) 
		return(paramErr);

	HLock((Handle)myAppData);
	
	// remove any existing back buffer imaging procedure
	if ((**myAppData).fBackBufferProc != NULL)
		QTVRSetBackBufferImagingProc(theInstance, NULL, 0, NULL, 0);

	//////////
	//
	// set the area of interest
	//
	// the panAngle and tiltAngle fields define the top-left corner, in panorama space, of the area of interest;
	// so here we do not have to worry about whether the back buffer is oriented vertically or horizontally
	//
	//////////
	
	// the application data structure holds the desired width, center, size, and scale of the movie
	myWidth = (**myAppData).fMovieWidth * (**myAppData).fMovieScale;
	myHeight = myWidth * (((float)(**myAppData).fMovieBox.bottom) / ((float)(**myAppData).fMovieBox.right));
	
	if ((**myAppData).fUseMovieCenter) {
		// use the stored movie center
		myArea.panAngle = (**myAppData).fMovieCenter.x + (myWidth/2);
		myArea.tiltAngle = (**myAppData).fMovieCenter.y + (myHeight/2);
	} else {
		// center the movie on the current pan and tilt angles
		myArea.panAngle = QTVRGetPanAngle(theInstance) + (myWidth/2);
		myArea.tiltAngle = QTVRGetTiltAngle(theInstance) + (myHeight/2);
	}

	myArea.width = myWidth;
	myArea.height = myHeight;

	//////////
	//
	// set the back buffer flags and install the back buffer procedure
	//
	//////////
	
	// make sure we get called on every idle event, so we can keep playing the embedded movie;
	// also make sure we get called on every back buffer update
	if ((**myAppData).fCompositeMovie)
		myArea.flags = kQTVRBackBufferEveryIdle | kQTVRBackBufferEveryUpdate | kQTVRBackBufferAlwaysRefresh;
	else
		myArea.flags = kQTVRBackBufferEveryIdle | kQTVRBackBufferEveryUpdate;
	
	// if the back buffer is oriented horizontally, set the appropriate flag
	if ((**myAppData).fBackBufferIsHoriz)
		myArea.flags |= kQTVRBackBufferHorizontal;

	// install our procedure
	myErr = QTVRSetBackBufferImagingProc(theInstance, (**myAppData).fBackBufferProc, 1, &myArea, (SInt32)theWindowObject);

	HUnlock((Handle)myAppData);
	return(myErr);
}
	
	
//////////
//
// VRMoov_CalcImagingMatrix
// Calculate the movie matrix required to draw the embedded movie into the specified rectangle.
//
//////////

OSErr VRMoov_CalcImagingMatrix (WindowObject theWindowObject, Rect *theBBufRect)
{
	ApplicationDataHdl		myAppData = NULL;
	Rect					myDestRect = *theBBufRect;

	myAppData = (ApplicationDataHdl)QTFrame_GetAppDataFromWindowObject(theWindowObject);
	if (myAppData == NULL) 
		return(paramErr);
		
	// reset the current movie matrix with the original movie matrix of the embedded movie;
	// we need to preserve that matrix in our calculations below if we are not drawing the
	// movie into an offscreen GWorld
	(**myAppData).fMovieMatrix = (**myAppData).fOrigMovieMatrix;
	
	// in general, it's easiest to construct the desired matrix by first doing the scaling
	// and then doing the rotation and translation (if necessary); so we need to swap the
	// right and bottom edges of the back buffer rectangle before doing the scaling, if a
	// rotation will also be necessary
	if (!(**myAppData).fBackBufferIsHoriz) {
		myDestRect.bottom = theBBufRect->right;
		myDestRect.right = theBBufRect->bottom;
	}
	
	// set up the scaling matrix
	// (MapMatrix concatenates the new matrix to the existing matrix, whereas RectMatrix first
	// sets the existing matrix to the identity matrix)
	if ((**myAppData).fUseOffscreenGWorld)
		RectMatrix(&(**myAppData).fMovieMatrix, &(**myAppData).fMovieBox, &myDestRect);
	else
		MapMatrix(&(**myAppData).fMovieMatrix, &(**myAppData).fMovieBox, &myDestRect);

	// add a rotation and translation, if necessary
	if (!(**myAppData).fBackBufferIsHoriz) {
		RotateMatrix(&(**myAppData).fMovieMatrix, Long2Fix(-90), 0, 0);
		TranslateMatrix(&(**myAppData).fMovieMatrix, 0, Long2Fix(RECT_HEIGHT(*theBBufRect)));
	}
	
	return(noErr);
}


//////////
//
// VRMoov_SetupDecompSeq
// Set up the decompression sequence for DecompressionSequenceFrameS.
//
// This needs to be called whenever either the rectangle or the GWorld of the back buffer changes.
//
//////////

OSErr VRMoov_SetupDecompSeq (WindowObject theWindowObject, GWorldPtr theDestGWorld)
{
	ApplicationDataHdl		myAppData = NULL;
	short					myMode = srcCopy;
	OSErr					myErr = noErr;
	
	myAppData = (ApplicationDataHdl)QTFrame_GetAppDataFromWindowObject(theWindowObject);
	if (myAppData == NULL) 
		return(paramErr);
	
	// make sure we don't have a decompression sequence already open
	VRMoov_RemoveDecompSeq(theWindowObject);
	
	// set up the transfer mode
	if ((**myAppData).fCompositeMovie)
		myMode = srcCopy | transparent;
		
	// set up the image decompression sequence
	myErr = DecompressSequenceBegin(	&(**myAppData).fImageSequence,
										(**myAppData).fImageDesc,
										(CGrafPtr)theDestGWorld,
										NULL,
										NULL,							// entire source image
										&(**myAppData).fMovieMatrix,
				   						myMode,
										NULL,							// no mask
										0,
										(**(**myAppData).fImageDesc).spatialQuality,
										NULL);
									
	return(myErr);
}


//////////
//
// VRMoov_RemoveDecompSeq
// Remove the decompression sequence for DecompressionSequenceFrameS.
//
//////////

OSErr VRMoov_RemoveDecompSeq (WindowObject theWindowObject)
{
	ApplicationDataHdl		myAppData = NULL;
	OSErr					myErr = paramErr;
	
	if (theWindowObject != NULL) {
		myAppData = (ApplicationDataHdl)QTFrame_GetAppDataFromWindowObject(theWindowObject);
		if (myAppData != NULL) 
			if ((**myAppData).fImageSequence != 0) {
				myErr = CDSequenceEnd((**myAppData).fImageSequence);
				(**myAppData).fImageSequence = 0;
			}
	}
	
	return(myErr);
}


//////////
//
// VRMoov_BackBufferImagingProc
// The back buffer imaging procedure: get a frame of movie and image it into the back buffer.
// Also, do any additional compositing that might be desired.
//
//////////

PASCAL_RTN OSErr VRMoov_BackBufferImagingProc (QTVRInstance theInstance, Rect *theRect, UInt16 theAreaIndex, UInt32 theFlagsIn, UInt32 *theFlagsOut, long theRefCon)
{
#pragma unused(theAreaIndex)
	WindowObject			myWindowObject = (WindowObject)theRefCon;
	ApplicationDataHdl		myAppData = NULL;
	Movie					myMovie = NULL;
	Boolean					myIsDrawing = theFlagsIn & kQTVRBackBufferRectVisible;
	GWorldPtr				myBBufGWorld, myMovGWorld;
	GDHandle				myBBufGDevice, myMovGDevice;
	Rect					myRect;
	OSErr					myErr = paramErr;
	
	//////////
	//
	// initialize; make sure that we've got the data we need to continue
	//
	//////////

	// assume we're not going to draw anything
	*theFlagsOut = 0;
	
	if ((theInstance == NULL) || (myWindowObject == NULL)) 
		goto bail;

	myAppData = (ApplicationDataHdl)QTFrame_GetAppDataFromWindowObject(myWindowObject);
	if (myAppData == NULL) 
		goto bail;

	HLock((Handle)myAppData);

	myMovie = (**myAppData).fMovie;
	if (myMovie == NULL) {
		// we don't have an embedded movie, so remove this back-buffer imaging procedure
		VRMoov_DumpEmbeddedMovie(myWindowObject);
		goto bail;
	}
	
	//////////
	//
	// make sure that the movie GWorld is set correctly;
	// note that we call SetMovieGWorld only if we have to (for performance reasons)
	//
	//////////
	
	// get the current graphics world
	// (on entry, the current graphics world is [usually] set to the back buffer)
	GetGWorld(&myBBufGWorld, &myBBufGDevice);

	// get the embedded movie's graphics world
	GetMovieGWorld(myMovie, &myMovGWorld, &myMovGDevice);

	if ((**myAppData).fUseOffscreenGWorld) {
		// we're using an offscreen graphics world, so set movie's GWorld to be that offscreen graphics world
		if (myMovGWorld != (**myAppData).fOffscreenGWorld)
			SetMovieGWorld(myMovie, (**myAppData).fOffscreenGWorld, GetGWorldDevice((**myAppData).fOffscreenGWorld));			
	} else {
		// we're not using an offscreen graphics world, so set movie GWorld to be the back buffer;
		if ((myMovGWorld != myBBufGWorld) || (myMovGDevice != myBBufGDevice))
			SetMovieGWorld(myMovie, myBBufGWorld, myBBufGDevice);
	}

	//////////
	//
	// make sure the movie rectangle and movie matrix are set correctly
	//
	//////////
	
	if (myIsDrawing) {
		// if we weren't previously visible, make sure we are now
		GetMovieBox(myMovie, &myRect);
		if (EmptyRect(&myRect))
			SetMovieBox(myMovie, &(**myAppData).fMovieBox);
		
		// when no offscreen GWorld is being used...
		if (!(**myAppData).fUseOffscreenGWorld) {
			
			// ...make sure the movie matrix is set correctly...
			if (!MacEqualRect(theRect, &(**myAppData).fPrevBBufRect)) {
				VRMoov_CalcImagingMatrix(myWindowObject, theRect);
				SetMovieMatrix(myMovie, &(**myAppData).fMovieMatrix);
			}
			
			// ...and, if we are compositing, force QuickTime to draw a movie frame when MoviesTask is called
			// (since the previous frame was automatically erased from the back buffer by QuickTime VR)
			if ((**myAppData).fCompositeMovie)
				UpdateMovie(myMovie);
		}
		
	} else {
		// if we're not visible, set the movie rectangle to an empty rectangle
		// so we're not wasting time trying to draw a movie frame
		MacSetRect(&myRect, 0, 0, 0, 0);
		SetMovieBox(myMovie, &myRect);
	}

	//////////
	//
	// draw a new movie frame into the movie's graphics world (and play movie sound)
	//
	//////////
	
	MoviesTask(myMovie, 1L);
//	MoviesTask(myMovie, 0);
	
	// if we got here, everything is okay so far
	myErr = noErr;
	
	//////////
	//
	// perform any additional compositing
	//
	//////////
	
	// that is, draw, using the current chroma key color, anything to be dropped out of the image;
	// note that this technique works *only* if we're using an offscreen graphics world
	if ((**myAppData).fUseOffscreenGWorld && (**myAppData).fCompositeMovie && myIsDrawing) {
		RGBColor	myColor;
	
		// since we're using an offscreen graphics world, make sure we draw there
		SetGWorld((**myAppData).fOffscreenGWorld, GetGWorldDevice((**myAppData).fOffscreenGWorld));

		// set up compositing environment
		GetForeColor(&myColor);
		RGBForeColor(&(**myAppData).fChromaColor);
		
		// do the drawing
		if ((**myAppData).fHideRegion != NULL)
			MacPaintRgn((**myAppData).fHideRegion);
			
		// restore original drawing environment
		RGBForeColor(&myColor);
		
		// restore original graphics world
		SetGWorld(myBBufGWorld, myBBufGDevice);
	}

	//////////
	//
	// if we're using an offscreen graphics world, copy it into the back buffer
	//
	//////////
	
	if (myIsDrawing) {
	
		if ((**myAppData).fUseOffscreenGWorld) {
			PixMapHandle	myPixMap;
			
			// if anything relevant to DecompressSequenceFrameS has changed, reset the decompression sequence
			if ((myBBufGWorld != (**myAppData).fPrevBBufGWorld) || !(MacEqualRect(theRect, &(**myAppData).fPrevBBufRect))) {
				VRMoov_CalcImagingMatrix(myWindowObject, theRect);
				VRMoov_SetupDecompSeq(myWindowObject, myBBufGWorld);
			}
			
			myPixMap = GetGWorldPixMap((**myAppData).fOffscreenGWorld);
			LockPixels(myPixMap);
			
			// set the chroma key color, if necessary
			if ((**myAppData).fCompositeMovie)
				RGBBackColor(&(**myAppData).fChromaColor);
			
			// copy the image from the offscreen graphics world into the back buffer
			myErr = DecompressSequenceFrameS(	(**myAppData).fImageSequence,
#if TARGET_CPU_68K
												StripAddress(GetPixBaseAddr(myPixMap)),
#else
												GetPixBaseAddr(myPixMap),
#endif
												(**(**myAppData).fImageDesc).dataSize,
												0,
												NULL,
												NULL);

			// reset the chroma key color;
			// we need to do this because the buffer we just drew into might NOT actually
			// be the real back buffer (see Virtual Reality Programming With QuickTime VR, p. 1-154);
			// the copy between the intermediate buffer and the back buffer respects the current back color.
			if ((**myAppData).fCompositeMovie)
				RGBBackColor(&kWhiteColor);
				
			UnlockPixels(myPixMap);
		}
	}
	
	//////////
	//
	// finish up
	//
	//////////
	
	// keep track of the GWorld and rectangle passed to us this time
	(**myAppData).fPrevBBufGWorld = myBBufGWorld;
	(**myAppData).fPrevBBufRect = *theRect;
	
	// if we drew something, tell QuickTime VR
	if (myIsDrawing)
		*theFlagsOut = kQTVRBackBufferFlagDidDraw;
	
bail:	
	HUnlock((Handle)myAppData);
	return(myErr);
}


//////////
//
// VRMoov_GetEmbeddedMovieWidth
// Get the width of the embedded movie.
//
//////////

float VRMoov_GetEmbeddedMovieWidth (WindowObject theWindowObject)
{
	ApplicationDataHdl		myAppData;
	float					myWidth;

	myAppData = (ApplicationDataHdl)QTFrame_GetAppDataFromWindowObject(theWindowObject);
	if (myAppData == NULL)
		myWidth = 0;
	else
		myWidth = (**myAppData).fMovieWidth;
		
	return(myWidth);
}


//////////
//
// VRMoov_SetEmbeddedMovieWidth
// Set the width of the embedded movie.
//
//////////

void VRMoov_SetEmbeddedMovieWidth (WindowObject theWindowObject, float theWidth)
{
	ApplicationDataHdl		myAppData;
	QTVRInstance			myInstance;

	myAppData = (ApplicationDataHdl)QTFrame_GetAppDataFromWindowObject(theWindowObject);
	if (myAppData == NULL)
		return;
		
	myInstance = (**theWindowObject).fInstance;
	if (myInstance == NULL)
		return;
	
	// install the desired width in our application data structure
	(**myAppData).fMovieWidth = theWidth;
	
	// clear out the existing area of interest
	QTVRRefreshBackBuffer(myInstance, 0);

	// reinstall the back buffer imaging procedure
	VRMoov_InstallBackBufferImagingProc(myInstance, theWindowObject);
}


//////////
//
// VRMoov_GetEmbeddedMovieCenter
// Get the center of the embedded movie.
//
//////////

void VRMoov_GetEmbeddedMovieCenter (WindowObject theWindowObject, QTVRFloatPoint *theCenter)
{
	ApplicationDataHdl		myAppData;

	myAppData = (ApplicationDataHdl)QTFrame_GetAppDataFromWindowObject(theWindowObject);
	if (myAppData == NULL) {
		theCenter->x = 0.0;
		theCenter->y = 0.0;
	} else {
		theCenter->x = (**myAppData).fMovieCenter.x;
		theCenter->y = (**myAppData).fMovieCenter.y;
	}
		
}


//////////
//
// VRMoov_SetEmbeddedMovieCenter
// Set the center of the embedded movie.
//
//////////

void VRMoov_SetEmbeddedMovieCenter (WindowObject theWindowObject, const QTVRFloatPoint *theCenter)
{
	ApplicationDataHdl		myAppData;
	QTVRInstance			myInstance;
	float					myX, myY;

	myAppData = (ApplicationDataHdl)QTFrame_GetAppDataFromWindowObject(theWindowObject);
	if (myAppData == NULL)
		return;
		
	myInstance = (**theWindowObject).fInstance;
	if (myInstance == NULL)
		return;
	
	myX = theCenter->x;
	myY = theCenter->y;
	
	// subject the values passed in to the current view constraints
	QTVRWrapAndConstrain(myInstance, kQTVRPan, myX, &myX);
	QTVRWrapAndConstrain(myInstance, kQTVRTilt, myY, &myY);
			
	// install the desired center in our application data structure
	(**myAppData).fMovieCenter.x = myX;
	(**myAppData).fMovieCenter.y = myY;
	
	// clear out the existing area of interest
	QTVRRefreshBackBuffer(myInstance, 0);

	// reinstall the back buffer imaging procedure
	VRMoov_InstallBackBufferImagingProc(myInstance, theWindowObject);
}


//////////
//
// VRMoov_GetEmbeddedMovieScale
// Get the scale of the embedded movie.
//
//////////

float VRMoov_GetEmbeddedMovieScale (WindowObject theWindowObject)
{
	ApplicationDataHdl		myAppData;
	float					myScale;

	myAppData = (ApplicationDataHdl)QTFrame_GetAppDataFromWindowObject(theWindowObject);
	if (myAppData == NULL)
		myScale = 0;
	else
		myScale = (**myAppData).fMovieScale;
		
	return(myScale);
}


//////////
//
// VRMoov_SetEmbeddedMovieScale
// Set the scale factor of the embedded movie.
//
//////////

void VRMoov_SetEmbeddedMovieScale (WindowObject theWindowObject, float theScale)
{
	ApplicationDataHdl		myAppData;
	QTVRInstance			myInstance;

	myAppData = (ApplicationDataHdl)QTFrame_GetAppDataFromWindowObject(theWindowObject);
	if (myAppData == NULL)
		return;
		
	myInstance = (**theWindowObject).fInstance;
	if (myInstance == NULL)
		return;
	
	// install the desired scale factor in our application data structure
	(**myAppData).fMovieScale = theScale;
	
	// clear out the existing area of interest
	QTVRRefreshBackBuffer(myInstance, 0);

	// reinstall the back buffer imaging procedure
	VRMoov_InstallBackBufferImagingProc(myInstance, theWindowObject);
}


//////////
//
// VRMoov_SetChromaColor
// Set the chroma key color for a window object:
// display color picker dialog and remember the newly-selected color.
//
//////////

void VRMoov_SetChromaColor (WindowObject theWindowObject)
{
#if TARGET_OS_MAC
	ColorPickerInfo		myColorInfo;
#endif	
#if TARGET_OS_WIN32
	static CHOOSECOLOR	myColorRec;
	static COLORREF		myColorRef[16];
#endif	
	ApplicationDataHdl	myAppData;

	myAppData = (ApplicationDataHdl)QTFrame_GetAppDataFromWindowObject(theWindowObject);
	if (myAppData == NULL)
		return;

#if TARGET_OS_MAC
	// pass in existing color
	myColorInfo.theColor.color.rgb.red = (**myAppData).fChromaColor.red;
	myColorInfo.theColor.color.rgb.green = (**myAppData).fChromaColor.green;
	myColorInfo.theColor.color.rgb.blue = (**myAppData).fChromaColor.blue;
	
	// not much here...
	myColorInfo.theColor.profile = 0L;
	myColorInfo.dstProfile = 0L;
	myColorInfo.flags = 0L;
	myColorInfo.placeWhere = kCenterOnMainScreen;
	myColorInfo.pickerType = 0L;
	myColorInfo.eventProc = gColorFilterUPP;
	myColorInfo.colorProc = NULL;
	myColorInfo.colorProcData = 0L;
	GetIndString(myColorInfo.prompt, kColorPickerTextStringID, 1);
	
	// set Edit menu info
	myColorInfo.mInfo.editMenuID = kEditMenuResID;
	myColorInfo.mInfo.undoItem = MENU_ITEM(IDM_EDITUNDO);
	myColorInfo.mInfo.cutItem = MENU_ITEM(IDM_EDITCUT);
	myColorInfo.mInfo.copyItem = MENU_ITEM(IDM_EDITCOPY);
	myColorInfo.mInfo.pasteItem = MENU_ITEM(IDM_EDITPASTE);
	myColorInfo.mInfo.clearItem = MENU_ITEM(IDM_EDITCLEAR);
	
	// call Color Picker
	if ((PickColor(&myColorInfo) == noErr) && (myColorInfo.newColorChosen)) {
		// install the newly chosen color in the palette
		(**myAppData).fChromaColor.red = myColorInfo.theColor.color.rgb.red;
		(**myAppData).fChromaColor.green = myColorInfo.theColor.color.rgb.green;
		(**myAppData).fChromaColor.blue = myColorInfo.theColor.color.rgb.blue;
	}
#endif
#if TARGET_OS_WIN32
	myColorRec.lStructSize = sizeof(CHOOSECOLOR);
	myColorRec.hwndOwner = NULL;
	myColorRec.hInstance = NULL;
	
	VRMoov_MacRGBToWinRGB(&(**myAppData).fChromaColor, &(myColorRec.rgbResult));
	
	myColorRec.lpCustColors = myColorRef;
	myColorRec.Flags = CC_RGBINIT | CC_FULLOPEN;
	myColorRec.lCustData = 0;
	myColorRec.lpfnHook = NULL;
	myColorRec.lpTemplateName = NULL;
	
	// call Common Color dialog
	if (ChooseColor(&myColorRec)) {
		// install the newly chosen color in the palette
		VRMoov_WinRGBToMacRGB(&(**myAppData).fChromaColor, myColorRec.rgbResult);
	}
#endif
}
	

//////////
//
// VRMoov_ColorDialogEventFilter
// Handle events before they get passed to the Color Picker dialog box.
//
//////////

PASCAL_RTN Boolean VRMoov_ColorDialogEventFilter (EventRecord *theEvent)
{
#if TARGET_OS_WIN32
#pragma unused(theEvent)
#endif
	Boolean				myEventHandled = false;
	OSErr				myErr = noErr;

#if TARGET_OS_MAC
	switch (theEvent->what) {
		case updateEvt: {
			if ((WindowPtr)theEvent->message != FrontWindow()) {
				QTFrame_HandleEvent(theEvent);
				myEventHandled = true;
			}
			break;
		}

		case nullEvent: {
			// do idle-time processing for all open windows in our window list
			WindowObject		myWindowObject = NULL;
			ApplicationDataHdl	myAppData = NULL;
			WindowReference		myWindow = NULL;

			myWindow = QTFrame_GetFrontMovieWindow();
			while (myWindow != NULL) {
				myWindowObject = (WindowObject)QTFrame_GetWindowObjectFromWindow(myWindow);
				myAppData = (ApplicationDataHdl)QTFrame_GetAppDataFromWindow(myWindow);
				if ((myWindowObject != NULL) && (myAppData != NULL))
					if ((**myAppData).fMovie != NULL)
						QTVRUpdate((**myWindowObject).fInstance, kQTVRCurrentMode);
				myWindow = QTFrame_GetNextMovieWindow(myWindow);
			}
			
			myEventHandled = false;
			break;
		}
		
		case kHighLevelEvent:
			myErr = AEProcessAppleEvent(theEvent);
			if (myErr != noErr)
				myEventHandled = true;
		 	break;
			
		default:
			myEventHandled = false;
			break;
	}	
	
#endif
	return(myEventHandled);
}


//////////
//
// VRMoov_UncoverProc
// The uncover function of the embedded movie.
//
//////////

PASCAL_RTN OSErr VRMoov_UncoverProc (Movie theMovie, RgnHandle theRegion, long theRefCon)
{
#pragma unused(theMovie, theRegion, theRefCon)
	return(noErr);
}


//////////
//
// VRMoov_SetVideoGraphicsMode
// Set the video media graphics mode of the embedded movie.
//
//////////

void VRMoov_SetVideoGraphicsMode (Movie theMovie, ApplicationDataHdl theAppData, Boolean theSetVGM)
{
	long				myTrackCount;
	short				myIndex;
	Track				myTrack = NULL;
	Media				myMedia = NULL;
	OSType				myMediaType;
	
	if ((theMovie == NULL) || (theAppData == NULL))
		return;
	
	myTrackCount = GetMovieTrackCount(theMovie);
	for (myIndex = 1; myIndex <= myTrackCount; myIndex++) {
		myTrack = GetMovieIndTrack(theMovie, myIndex);
		myMedia = GetTrackMedia(myTrack);
		GetMediaHandlerDescription(myMedia, &myMediaType, NULL, NULL);
		if (myMediaType == VideoMediaType) {
			if (theSetVGM)
				MediaSetGraphicsMode(GetMediaHandler(myMedia), srcCopy | transparent, &(**theAppData).fChromaColor);
			else
				MediaSetGraphicsMode(GetMediaHandler(myMedia), srcCopy, &kWhiteColor);
		}
	}
	
	return;
}


//////////
//
// VRMoov_GetVideoGraphicsPixelDepth
// Return the highest pixel depth supported by a QuickTime movie.
//
//////////

short VRMoov_GetVideoGraphicsPixelDepth (Movie theMovie)
{
	long				myTrackCount;
	short				myIndex;
	Track				myMovieTrack = NULL;
	Media				myMedia;
	OSType				myMediaType;
	short				myQuality;
	
	myTrackCount = GetMovieTrackCount(theMovie);
	for (myIndex = 1; myIndex <= myTrackCount; myIndex++) {
		myMovieTrack = GetMovieIndTrack(theMovie, myIndex);
		myMedia = GetTrackMedia(myMovieTrack);
		GetMediaHandlerDescription(myMedia, &myMediaType, NULL, NULL);
		if (myMediaType == VideoMediaType) {
			myQuality = GetMediaQuality(myMedia);
			if (myQuality >> 5)
				return(32);
			if (myQuality >> 4)
				return(16);
			if (myQuality >> 3)
				return(8);
			if (myQuality >> 2)
				return(4);
			if (myQuality >> 1)
				return(2);
			if (myQuality >> 0)
				return(1);
		}
	}
	
	return(0);
}


///////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// Color conversion utilities.
//
// Macintosh usually represents colors using an RGBColor structure, where each color component is a 16-bit
// unsigned integer. Windows represents colors using a 32-bit unsigned COLORREF, where each color component
// occupies 8 bits. The following two functions allow us to convert from Mac to Windows colors and back. We
// need to do this because we use Mac-style colors when doing compositing.
//
///////////////////////////////////////////////////////////////////////////////////////////////////////////

#if TARGET_OS_WIN32

#define kMaxMacRGBValue				0xffff
#define kMaxWinRGBValue				0x00ff
#define MAC_TO_WIN_COMP(color)		(color*((float)kMaxWinRGBValue/(float)kMaxMacRGBValue))
#define WIN_TO_MAC_COMP(color)		(color*((float)kMaxMacRGBValue/(float)kMaxWinRGBValue))


//////////
//
// VRMoov_MacRGBToWinRGB
// Convert an RGBColor structure into a COLORREF value.
//
//////////

void VRMoov_MacRGBToWinRGB (RGBColorPtr theRGBColor, COLORREF *theColorRef)
{
	*theColorRef = RGB(	(BYTE)MAC_TO_WIN_COMP(theRGBColor->red),
						(BYTE)MAC_TO_WIN_COMP(theRGBColor->green),
						(BYTE)MAC_TO_WIN_COMP(theRGBColor->blue));
}


//////////
//
// VRMoov_WinRGBToMacRGB
// Convert a COLORREF value into an RGBColor structure.
//
//////////

void VRMoov_WinRGBToMacRGB (RGBColorPtr theRGBColor, COLORREF theColorRef)
{
	theRGBColor->red = (unsigned long)WIN_TO_MAC_COMP(GetRValue(theColorRef));
	theRGBColor->green = (unsigned long)WIN_TO_MAC_COMP(GetGValue(theColorRef));
	theRGBColor->blue = (unsigned long)WIN_TO_MAC_COMP(GetBValue(theColorRef));
}
#endif	// TARGET_OS_WIN32


