//////////
//
//	File:		VREffects.c
//
//	Contains:	QuickTime video effects support for QuickTime VR movies.
//
//	Written by:	Tim Monroe
//				Parts modeled on ShowEffect sample code by Dan Crow.
//
//	Copyright:	© 1997-1998 by Apple Computer, Inc., all rights reserved.
//
//	Change History (most recent first):
//
//	   <10>	 	03/22/00	rtm		made changes to get things running under CarbonLib
//	   <9>	 	06/17/99	rtm		added HLock and HUnlock calls to VREffects_RunTransitionEffect
//	   <8>	 	05/06/99	rtm		added support for MakeImageDescriptionForEffect (QT 4.0 and later)
//	   <7>	 	04/24/98	rtm		added VRMoov_DoIdle call to VREffects_RunTransitionEffect, to service
//									scene-wide sounds while a transition is occurring 
//	   <6>	 	02/26/98	rtm		reworked VREffects_SetupTransitionEffect to use QTVRUpdate, not CopyBits;
//									now we work much better on Windows (finally!);
//									added VRScript_CheckForExpiredCommand call to VREffects_RunTransitionEffect
//	   <5>	 	02/10/98	rtm		fixed source rectangles in VREffects_SetupTransitionEffect
//	   <4>	 	02/05/98	rtm		cleaned up GWorld handling; remember, lock those pixmaps!
//	   <3>	 	11/01/97	rtm		added Endian macros to VREffects_MakeEffectDescription
//	   <2>	 	10/29/97	rtm		further work: got node transition effects working on Mac
//	   <1>	 	10/27/97	rtm		first file
//	   
//	This file provides functions to incorporate QuickTime video effects into QuickTime VR movies.
//	QuickTime video effects were introduced with QuickTime 3.0; they support many SMPTE transitions
//	and other special effects. This file defines functions that use QT video effects in these ways:
//
//	* run effects while transitioning between nodes (let's call these "transition effects")
//	* (this space for rent)
//
//////////

//	TO DO:
//	+ implement parameter handling
//	+ currently only one transition per node pair is supported; allow multiple transition effects

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

#include "VREffects.h"


//////////
//
// VREffects_InitWindowData
// Initialize for QuickTime video effects.
//
//////////

void VREffects_InitWindowData (WindowObject theWindowObject)
{
	ApplicationDataHdl			myAppData;

	myAppData = (ApplicationDataHdl)QTFrame_GetAppDataFromWindowObject(theWindowObject);
	if (myAppData != NULL) {
		(**myAppData).fSourceGWorld = NULL;
		(**myAppData).fTargetGWorld = NULL;
		(**myAppData).fSourceGWDesc = NULL;
		(**myAppData).fTargetGWDesc = NULL;
		(**myAppData).fActiveTransition = NULL;
	}
}


//////////
//
// VREffects_DumpWindowData
// Dump the window-specific data for QuickTime video effects.
//
//////////

void VREffects_DumpWindowData (WindowObject theWindowObject)
{
	ApplicationDataHdl			myAppData;
	
	myAppData = (ApplicationDataHdl)QTFrame_GetAppDataFromWindowObject(theWindowObject);
	if (myAppData != NULL) {

		// dispose of transition effects GWorlds used by this window object
		if ((**myAppData).fSourceGWorld != NULL) {
			DisposeGWorld((**myAppData).fSourceGWorld);
			(**myAppData).fSourceGWorld = NULL;
		}
		
		if ((**myAppData).fTargetGWorld != NULL) {
			DisposeGWorld((**myAppData).fTargetGWorld);
			(**myAppData).fTargetGWorld = NULL;
		}
		
		// dispose of the image descriptions
		if ((**myAppData).fSourceGWDesc != NULL) {
			DisposeHandle((Handle)(**myAppData).fSourceGWDesc);
			(**myAppData).fSourceGWDesc = NULL;
		}
		
		if ((**myAppData).fTargetGWDesc != NULL) {
			DisposeHandle((Handle)(**myAppData).fTargetGWDesc);
			(**myAppData).fTargetGWDesc = NULL;
		}
		
	}
}


//////////
//
// VREffects_DoIdle
// Do any effects-related processing that can or should occur at idle time.
// Returns true if the caller should call QTVRUpdate, false otherwise.
//
//////////

Boolean VREffects_DoIdle (WindowObject theWindowObject)
{
	ApplicationDataHdl		myAppData;
	Boolean					myNeedUpdate = false;

	myAppData = (ApplicationDataHdl)QTFrame_GetAppDataFromWindowObject(theWindowObject);
	if (myAppData != NULL) {
	
		// nothing needed for transition effects
		
	}
	
	return(myNeedUpdate);
}
		
				
//////////
//
// VREffects_GetTransitionEffect
// Return the first transition effect enlisted for the specified fromNode/toNode pair.
//
//////////

VRScriptTransitionPtr VREffects_GetTransitionEffect (WindowObject theWindowObject, UInt32 fromNodeID, UInt32 toNodeID)
{
	ApplicationDataHdl		myAppData = NULL;
	VRScriptTransitionPtr	myPointer = NULL;
	VRScriptTransitionPtr	myNext = NULL;
	
	myAppData = (ApplicationDataHdl)QTFrame_GetAppDataFromWindowObject(theWindowObject);
	if (myAppData == NULL)
		return(myPointer);
		
	// walk the transition effects list and see if any apply to this node
	myPointer = (VRScriptTransitionPtr)(**myAppData).fListArray[kVREntry_TransitionEffect];
	while (myPointer != NULL) {
		myNext = myPointer->fNextEntry;
		
		if ((myPointer->fFromNodeID == fromNodeID) || (myPointer->fFromNodeID == kVRAnyNode))
			if ((myPointer->fToNodeID == toNodeID) || (myPointer->fToNodeID == kVRAnyNode))
				return(myPointer);
	
		myPointer = myNext;
	}
	
	return(myPointer);
}


//////////
//
// VREffects_MakeEffectDescription
// Create an effect description.
//
// The effect description specifies which video effect is desired and the parameters for that effect.
// It also describes the source(s) for the effect. An effect description is simply an atom container
// that holds atoms with the appropriate information.
//
// Note that because we are creating an atom container, we must pass big-endian data (hence the calls
// to EndianU32_NtoB).
//
// Currently we support up to two sources.
//
//////////

QTAtomContainer VREffects_MakeEffectDescription (OSType theEffectType, long theEffectNum, OSType theSourceName1, OSType theSourceName2)
{
	QTAtomContainer		mySample = NULL;
	OSType				myType;
	OSErr				myErr = noErr;

	// create a new, empty effect description
	myErr = QTNewAtomContainer(&mySample);
	if (myErr != noErr)
		return(NULL);
	
	// create the effect ID atom: the atom type is kParameterWhatName, and the atom ID is kParameterWhatID
	myType = EndianU32_NtoB(theEffectType);
	myErr = QTInsertChild(mySample, kParentAtomIsContainer, kParameterWhatName, kParameterWhatID, 0, sizeof(myType), &myType, NULL);
	if (myErr != noErr)
		return(NULL);
		
	// add the first source, if it's not kSourceNoneName
	if (theSourceName1 != kSourceNoneName) {
		myType = EndianU32_NtoB(theSourceName1);
		myErr = QTInsertChild(mySample, kParentAtomIsContainer, kEffectSourceName, 1, 0, sizeof(myType), &myType, NULL);
	}
								
	// add the second source, if it's not kSourceNoneName
	if (theSourceName2 != kSourceNoneName) {
		myType = EndianU32_NtoB(theSourceName2);
		myErr = QTInsertChild(mySample, kParentAtomIsContainer, kEffectSourceName, 2, 0, sizeof(myType), &myType, NULL);
	}
	
	// add a parameter for SMPTE effects
	myType = EndianU32_NtoB(theEffectNum);
	myErr = QTInsertChild(mySample, kParentAtomIsContainer, FOUR_CHAR_CODE('wpID'), 1, 0, sizeof(myType), &myType, NULL);
	
	return(mySample);
}


//////////
//
// VREffects_MakeSampleDescription
// Create a description of an effect sample.
//
//////////

ImageDescriptionHandle VREffects_MakeSampleDescription (OSType theEffectType, short theWidth, short theHeight)
{
	ImageDescriptionHandle		mySampleDesc = NULL;

#if USES_MAKE_IMAGE_DESC_FOR_EFFECT
	OSErr						myErr = noErr;
	
	// create a new sample description
	myErr = MakeImageDescriptionForEffect(theEffectType, &mySampleDesc);
	if (myErr != noErr)
		return(NULL);
#else
	// create a new sample description
	mySampleDesc = (ImageDescriptionHandle)NewHandleClear(sizeof(ImageDescription));
	if (mySampleDesc == NULL)
		return(NULL);
	
	// fill in the fields of the sample description
	(**mySampleDesc).cType = theEffectType;
	(**mySampleDesc).idSize = sizeof(ImageDescription);
	(**mySampleDesc).hRes = 72L << 16;
	(**mySampleDesc).vRes = 72L << 16;
	(**mySampleDesc).dataSize = 0L;
	(**mySampleDesc).frameCount = 1;
	(**mySampleDesc).depth = 0;
	(**mySampleDesc).clutID = -1;
#endif

	(**mySampleDesc).vendor = kAppleManufacturer;
	(**mySampleDesc).temporalQuality = codecNormalQuality;
	(**mySampleDesc).spatialQuality = codecNormalQuality;
	(**mySampleDesc).width = theWidth;
	(**mySampleDesc).height = theHeight;
	
	return(mySampleDesc);
}


//////////
//
// VREffects_SetupTransitionEffect
// Prepare for a transition from the current node to the target node:
// * make a copy of the current screen image
// * set movie and controller GWorlds to an offscreen graphics world
//
//////////

OSErr VREffects_SetupTransitionEffect (WindowObject theWindowObject, UInt32 fromNodeID, UInt32 toNodeID)
{
	ApplicationDataHdl		myAppData = NULL;
	VRScriptTransitionPtr	myPointer = NULL;
	Rect					myRect;
	OSErr					myErr = noErr;

	myAppData = (ApplicationDataHdl)QTFrame_GetAppDataFromWindowObject(theWindowObject);
	if (myAppData == NULL)
		return(paramErr);
		
	if (((**theWindowObject).fMovie == NULL) || ((**theWindowObject).fController == NULL))
		return(paramErr);
		
	// lock the application data handle
	HLock((Handle)myAppData);
	
	// see if any transition effects are enlisted for the current fromNode/toNode pair
	myPointer = VREffects_GetTransitionEffect(theWindowObject, fromNodeID, toNodeID);
	(**myAppData).fActiveTransition = myPointer;
	if (myPointer == NULL)
		goto bail;		// if there is no active transition effect, just bail

	// make sure our slate is clean
	VREffects_DumpWindowData(theWindowObject);
	VREffects_DumpEntryMem(myPointer);

	// get the size of the movie
	GetMovieBox((**theWindowObject).fMovie, &myRect);
	MacOffsetRect(&myRect, -myRect.left, -myRect.top);
	
	// create effect and sample descriptions			
	myPointer->fEffectDesc = VREffects_MakeEffectDescription(myPointer->fEffectType, myPointer->fEffectNum, kSourceOneName, kSourceTwoName);
	myPointer->fSampleDesc = VREffects_MakeSampleDescription(myPointer->fEffectType, myRect.right, myRect.bottom);
	if ((myPointer->fEffectDesc == NULL) || (myPointer->fSampleDesc == NULL))
		goto bail;

	// create an offscreen GWorld that is the size and depth of the movie window;
	// this is the source of the transition effect
	myErr = NewGWorld(&(**myAppData).fSourceGWorld, 0, &myRect, NULL, NULL, 0L);
	if (myErr != noErr)
		goto bail;

	LockPixels(GetGWorldPixMap((**myAppData).fSourceGWorld));	
	
	// create an offscreen GWorld that is the size and depth of the movie window;
	// this is the destination of the transition effect
	myErr = NewGWorld(&(**myAppData).fTargetGWorld, 0, &myRect, NULL, NULL, 0L);
	if (myErr != noErr)
		goto bail;

	LockPixels(GetGWorldPixMap((**myAppData).fTargetGWorld));
	
	// draw the current screen image into the source offscreen GWorld
	SetMovieGWorld((**theWindowObject).fMovie, (CGrafPtr)(**myAppData).fSourceGWorld, NULL);
	QTVRUpdate((**theWindowObject).fInstance, kQTVRCurrentMode);
		
	// set the movie and controller GWorlds to the destination GWorld
	MCSetControllerPort((**theWindowObject).fController, (CGrafPtr)(**myAppData).fTargetGWorld);
	SetMovieGWorld((**theWindowObject).fMovie, (CGrafPtr)(**myAppData).fTargetGWorld, NULL);
	MCMovieChanged((**theWindowObject).fController, (**theWindowObject).fMovie);
	
bail:
	// make sure we've got no transition scheduled if an error occurred
	if (myErr != noErr)
		(**myAppData).fActiveTransition = NULL;

	// unlock the application data handle
	HUnlock((Handle)myAppData);
	
	return(myErr);
}


//////////
//
// VREffects_RunTransitionEffect
// Run a transition from the previous node to the current node.
//
//////////

OSErr VREffects_RunTransitionEffect (WindowObject theWindowObject)
{
	ApplicationDataHdl			myAppData = NULL;
	VRScriptTransitionPtr		myPointer = NULL;
	ImageSequenceDataSource		mySrc1 = 0;
	ImageSequenceDataSource		mySrc2 = 0;
	ICMFrameTimeRecord			myFrameTime;
	TimeValue					myFrame;
	PixMapHandle				mySrcPixMap;
	PixMapHandle				myDstPixMap;
	OSErr						myErr = paramErr;

	myAppData = (ApplicationDataHdl)QTFrame_GetAppDataFromWindowObject(theWindowObject);
	if (myAppData == NULL)
		goto bail;
		
	HLock((Handle)myAppData);

	// if there is no active transition effect, just bail
	myPointer = (**myAppData).fActiveTransition;
	if (myPointer == NULL)
		goto bail;
	
	if ((myPointer->fEffectDesc == NULL) || (myPointer->fSampleDesc == NULL))
		goto bail;
		
	// make an effects sequence
	HLock((Handle)myPointer->fEffectDesc);
	
	// prepare the decompression sequence for playback	
	myErr = DecompressSequenceBeginS(
							&(myPointer->fSequenceID),
							myPointer->fSampleDesc,
#if TARGET_CPU_68K
							StripAddress(*((Handle)myPointer->fEffectDesc)),
#else
							*((Handle)myPointer->fEffectDesc),
#endif
							GetHandleSize((Handle)myPointer->fEffectDesc),
							(CGrafPtr)QTFrame_GetPortFromWindowReference((**theWindowObject).fWindow),
							NULL,
							NULL,
							NULL,
							ditherCopy,
							NULL,
							0,
							codecNormalQuality,
							NULL);

	HUnlock((Handle)myPointer->fEffectDesc);
	if (myErr != noErr)
		goto bail;

	// get the pixel maps for the GWorlds (which were already locked in VREffects_SetupTransitionEffect)
	mySrcPixMap = GetGWorldPixMap((**myAppData).fSourceGWorld);
	myDstPixMap = GetGWorldPixMap((**myAppData).fTargetGWorld);
	if ((mySrcPixMap == NULL) || (myDstPixMap == NULL))
		goto bail;

	// make the effect sources
	MakeImageDescriptionForPixMap(mySrcPixMap, &(**myAppData).fSourceGWDesc);
	myErr = CDSequenceNewDataSource(
							myPointer->fSequenceID,
							&mySrc1,
							kSourceOneName,
							1,
							(Handle)(**myAppData).fSourceGWDesc,
							NULL,
							0);
	if (myErr != noErr)
		goto bail;

	CDSequenceSetSourceData(mySrc1, GetPixBaseAddr(mySrcPixMap), (**(**myAppData).fSourceGWDesc).dataSize);

	MakeImageDescriptionForPixMap(myDstPixMap, &(**myAppData).fTargetGWDesc);
	myErr = CDSequenceNewDataSource(
							myPointer->fSequenceID,
							&mySrc2,
							kSourceTwoName,
							1,
							(Handle)(**myAppData).fTargetGWDesc,
							NULL,
							0);
	if (myErr != noErr)
		goto bail;

	CDSequenceSetSourceData(mySrc2, GetPixBaseAddr(myDstPixMap), (**(**myAppData).fTargetGWDesc).dataSize);

	// dispose of any existing time base
	if (myPointer->fTimeBase != NULL)
		DisposeTimeBase(myPointer->fTimeBase);
		
	// create a new time base and associate it with the decompression sequence
	// (this time base MUST be disposed of before the application exits or bad
	// things may happen; we dispose of it when we call VREffects_DumpEntryMem
	// below)
	myPointer->fTimeBase = NewTimeBase();
	myErr = GetMoviesError();
	if (myErr != noErr)
		goto bail;
		
	SetTimeBaseRate(myPointer->fTimeBase, 0);
	myErr = CDSequenceSetTimeBase(myPointer->fSequenceID, myPointer->fTimeBase);
	if (myErr != noErr)
		goto bail;

	HLock((Handle)myPointer->fEffectDesc);
	
	// now run the effect thru from start to finish
	for (myFrame = 1; myFrame <= myPointer->fNumberOfSteps; myFrame++) {

		// set the timebase time to the step of the sequence to be rendered
		SetTimeBaseValue(myPointer->fTimeBase, myFrame, myPointer->fNumberOfSteps);
		
		myFrameTime.value.hi				= 0;
		myFrameTime.value.lo				= myFrame;
		myFrameTime.scale					= myPointer->fNumberOfSteps;
		myFrameTime.base					= 0;
		myFrameTime.duration				= myPointer->fNumberOfSteps;
		myFrameTime.rate					= 0;
		myFrameTime.recordSize				= sizeof(myFrameTime);
		myFrameTime.frameNumber				= 1;
		myFrameTime.flags					= icmFrameTimeHasVirtualStartTimeAndDuration;
		myFrameTime.virtualStartTime.hi		= 0;
		myFrameTime.virtualStartTime.lo		= 0;
		myFrameTime.virtualDuration			= myPointer->fNumberOfSteps;
		
		
		myErr = DecompressSequenceFrameWhen(myPointer->fSequenceID,
#if TARGET_CPU_68K
											StripAddress(*((Handle)myPointer->fEffectDesc)),
#else
											*((Handle)myPointer->fEffectDesc),
#endif
											GetHandleSize((Handle)myPointer->fEffectDesc),
											0,
											0,
											NULL,
											&myFrameTime);
		if (myErr != noErr)
			goto bail;
			
		// on very long transitions (or very slow machines), scene-wide sound-only movies need to be serviced
		// (but not every frame, eh?)
		if (myFrame % kDoIdleStep == 0)
			VRMoov_DoIdle(theWindowObject);
	}

	// see whether the enlisted effect has expired
	VRScript_CheckForExpiredCommand(theWindowObject, (VRScriptGenericPtr)myPointer);
	
	// if we got this far, all is okay
	myErr = noErr;
	
bail:
	if ((myPointer != NULL) && (myPointer->fEffectDesc != NULL))
		HUnlock((Handle)myPointer->fEffectDesc);
	
	// dispose of the effects GWorlds, if necessary
	VREffects_DumpWindowData(theWindowObject);
	
	// dispose of the effect sequence and time base
	VREffects_DumpEntryMem(myPointer);
	
	// reset the movie and controller GWorlds to the on-screen window
	MCSetControllerPort((**theWindowObject).fController, (CGrafPtr)QTFrame_GetPortFromWindowReference((**theWindowObject).fWindow));
	SetMovieGWorld((**theWindowObject).fMovie, (CGrafPtr)QTFrame_GetPortFromWindowReference((**theWindowObject).fWindow), NULL);
	MCMovieChanged((**theWindowObject).fController, (**theWindowObject).fMovie);
	
	// mark this transition as having been run
	(**myAppData).fActiveTransition = NULL;
	
	if (mySrcPixMap != NULL)
		UnlockPixels(mySrcPixMap);
		
	if (myDstPixMap != NULL)
		UnlockPixels(myDstPixMap);
		
	if (myAppData != NULL)
		HUnlock((Handle)myAppData);

	return(myErr);
}


//////////
//
// VREffects_DumpEntryMem
// Release any memory associated with the specified list entry.
//
//////////

void VREffects_DumpEntryMem (VRScriptTransitionPtr theEntry)
{	
	if (theEntry != NULL) {
		// if an effect sequence is currently set up, end it
		if (theEntry->fSequenceID != 0L) {
			CDSequenceEnd(theEntry->fSequenceID);
			theEntry->fSequenceID = 0L;
		}
			
		// if a time base currently exists, dispose of it
		if (theEntry->fTimeBase != NULL) {
			DisposeTimeBase(theEntry->fTimeBase);
			theEntry->fTimeBase = NULL;
		}
		
		// dispose of the effect description
		if (theEntry->fEffectDesc != NULL) {
			QTDisposeAtomContainer(theEntry->fEffectDesc);
			theEntry->fEffectDesc = NULL;
		}
		
		// dispose of the sample description
		if (theEntry->fSampleDesc != NULL) {
			DisposeHandle((Handle)theEntry->fSampleDesc);
			theEntry->fSampleDesc = NULL;
		}
	}
}