/****************************/
/*   QT PARSER.C			*/
/****************************/

/*	PangeaVR - 3D accelerated plug-in for viewing panoramas.

	©2005 Pangea Software, Inc.  All rights reserved.
	Pangea Software, PangeaVR and various logos are trademarks of Pangea Software, Inc.
	
	Written by Brian Greenstone,   brian@pangeasoft.net	
	
	This program and source code is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  
	
	IMPORTANT:  The purpose in releasing this source code is to encourage others
				To port this program to other platforms, and to make improvments to it.
				However, to prevent things from getting out of control with lots of
				different versions of PangeaVR floating around the internet,
				Pangea Software, Inc. prohibits the public distribution of any versions
				of PangeaVR without prior approval from Pangea Software, Inc.
				
				Should you make any improvements to this source code and wish to
				have those changes rolled into an official release then you'll
				need to contact Pangea Software to have your modifications adapted.				
*/


/*

	SPECIAL NOTE:
	-----------------------------------------------
	
	This Quicktime .mov parser sucks!  I can't belive it actually works.  
	It will parse through the Atoms in the file and try it's best to handle them.
	However, the shoddy documentation on this made it impossible to completely figure out,
	so hopefully this will simply work.

*/


/****************************/
/*    EXTERNALS             */
/****************************/

extern	MyInstanceType	gMyInstances[];
extern	Ptr				gTempImageBuffer;
extern	Rect			gTempImageRect;

#ifdef TARGET_OS_MAC
extern	GWorldPtr			gTempImageGWorld;
#endif


/****************************/
/*    PROTOTYPES            */
/****************************/

static long ReadMovieAtom(void);
static void ReadAtom_STCO(long size);
static void ReadAtom_HDLR(int size);
//static void ReadAtom_STSD(int size);
static void ReadAtom_STSZ(long size);
static void SeekAndExtractImages(int instanceNum);
static void SeekAndExtractImages_Tiled(int instanceNum);
static void LoadTiles(int chunkNum);
static void FreeTiles(void);
static void CompositeTiles(int faceNum, int tileDimensions, int textureSize, int instanceNum);
static void SwizzleLong(long *value);
static void SwizzleInt(int *value);


/****************************/
/*    CONSTANTS             */
/****************************/

#define	MAX_TILE_DIMENSIONS	10												// this is the maximum # of tiles x/y that we'll support
#define	MAX_TILES_PER_FACE	(MAX_TILE_DIMENSIONS * MAX_TILE_DIMENSIONS)

#define	MAX_IMAGE_OFFSETS		(MAX_TILES_PER_FACE*6)						// we support tiling of up to 10x10 tiles for each of the 6 cube faces


/*********************/
/*    VARIABLES      */
/*********************/



static	u_long	gCurrentTrackMedia = 0;					// 'pano' or ...
static	Boolean	gAlreadyGotVideoMedia = false;			// we assume that the first video media track is what we want,
														// anything after that is the fast-start track, so we ignore it.
static	Boolean	gFoundJPEGs;
static	Boolean	gImagesAreTiled;
static	int		gNumTilesPerImage;

static	long		gPanoChunkOffset, gPanoSampleSize;
static	long		gVideoChunkOffset[MAX_IMAGE_OFFSETS];
int					gVideoSampleSize[MAX_IMAGE_OFFSETS];

static	GWorldPtr	tileGWorlds[MAX_TILES_PER_FACE];
static	Ptr			tileBuffers[MAX_TILES_PER_FACE];
static	long		gTileSize[MAX_TILES_PER_FACE];

#ifdef TARGET_OS_MAC
short				gMovfRefNum;
#else
static	FILE		*gFile;								// FILE ref used for fopen().  Not used on Mac since mac uses FSSpec
#endif


/**************** PARSE QUICKTIME MOVIE ***********************/
//
// Parses through the QT movie's atoms looking for our 6 cube textures.
//

OSErr ParseQTMovie(int instanceNum)
{
OSErr			iErr;
long			atomSize;
	
				/* SEE WHAT SIZE TEXTURE THE HARDWARE SUPPORTS */
				//
				// Note:  we do this here because when we get here we know that we
				//		have an OpenGL context and we know if we're doing a cube or sphere.
				//
				
	CalcMaxTextureSizeForHardware(instanceNum);


	
	gCurrentTrackMedia = 0;
	gAlreadyGotVideoMedia = false;
	gFoundJPEGs = false;
	
	
	gMyInstances[instanceNum].drawDecodingBar = true;
	gMyInstances[instanceNum].decodeStep = 0;
	

			/*****************/
			/* OPEN THE FILE */
			/*****************/
	
#ifdef TARGET_OS_MAC
	iErr = FSpOpenDF(&gMyInstances[instanceNum].dataFileSpec,fsRdPerm, &gMovfRefNum);
	if (iErr)
	{
		printf("Error opening QT movie's data fork.  %d\n", iErr);
		return(1);
	}
#else

	---- READ THIS ----
	
	/* Note for PC and Linux programmers:  The pathname specified below comes from the Browser.  It's probably a full path, and I don't
	   know how fopen() responds to full paths by default.  You should test this and be sure that fopen is successful.  If not, 
	   then you'll have to do whatever you normally do on Windows to make paths work
	*/

	gFile = fopen (gTheDataFilePath, "wb");
	if  (!gFile)           
	{
		printf("fopen() failed!\n");
		return(1);
	}

#endif		
	
	
		/*************************/
		/* RECURSE THROUGH ATOMS */
		/*************************/

	do
	{
		atomSize = ReadMovieAtom();
	}while(atomSize > 0);
	
	
	
		/***************************/
		/* SEEK AND EXTRACT IMAGES */
		/***************************/
			
	SeekAndExtractImages(instanceNum);
	
			/**************/
			/* CLOSE FILE */
			/**************/
			
#ifdef TARGET_OS_MAC
	FSClose(gMovfRefNum);						
#else
	fclose(gFile);
#endif		

	
	gMyInstances[instanceNum].drawDecodingBar = false;
	
	return(noErr);
}

/********************* READ MOVIE ATOM ************************/
//
// INPUT:  parentAtomSize = size of the enclosing atom (or -1 if root).
//						This is used to determine when we've read all the child atoms from
//						the enclosing parent that we sub-recursed.
//
// OUTPUT:	size of the atom
//

static long ReadMovieAtom(void)
{
long	atomSize, atomType, count, remainingSize;
long	filePos;
//char	*c = (char *)&atomType;
OSErr	iErr;



			/*******/
			/* MAC */
			/*******/
			
#ifdef TARGET_OS_MAC
			/* GET CURRENT FILE POS */
			//
			// We get the current file position, so that we can add the atom size
			// to skip over it later.
			//

	GetFPos(gMovfRefNum, &filePos);

				/* READ THE ATOM SIZE */

	count = 4;											// size is 4 bytes
	iErr = FSRead(gMovfRefNum, &count, &atomSize);
	if (iErr)
	{
		printf("ReadMovieAtom:  FSRead failed!  %d\n", iErr);
		return(-1);
	}	
		
				/* READ THE ATOM TYPE */

	count = 4;											// type is 4 bytes
	if (FSRead(gMovfRefNum, &count, &atomType) != noErr)
	{
		printf("ReadMovieAtom:  FSRead failed!\n");
		return(1);
	}


			/***********/
			/* WINDOWS */
			/***********/
			
#else
			/* GET CURRENT FILE POS */

	filePost = ftell(gFile);							

				/* READ THE ATOM SIZE */

	fread(&atomSize, 4, 1, gFile);		
	if (ferror(gFile))
	{
		printf("ReadMovieAtom:  fread() failed!\n");
		return(-1);
	}	

				/* READ THE ATOM TYPE */

	fread(&atomType, 4, 1, gFile);		
	if (ferror(gFile))
	{
		printf("ReadMovieAtom:  fread() failed!\n");
		return(-1);
	}	
		
		
	SwizzleLong(&atomSize);								// convert BigEndian data to LittleEndian
	SwizzleLong(&atomType);
		
#endif		
			

//	printf("Atom Size = %d\n", (int)atomSize);
//	printf("Atom Type = %c%c%c%c\n", *c, *(c + 1), *(c + 2), *(c +3));



			/* READ EXTENDED DATA IF NEEDED */
		
	if (atomSize == 1)									// if atom size == 1 then there's extended data in the header
	{
		printf("ReadMovieAtom: Extended size isn't supported yet...\n");
		return(1);	
	}



		/********************/
		/* HANDLE THIS ATOM */
		/********************/
		//
		// Any atom types not in the table below just get skipped.
		//

	switch(atomType)
	{
			/* MOOV */
			//
			// This contains more sub-atoms, so just recurse
			//
			
		case	MovieAID:										//'moov'
//				printf("  [Subrecursing 'moov' atom]\n");
				remainingSize = atomSize - 8;					// there are n bytes left in this atom to parse
				do
				{				
					remainingSize -= ReadMovieAtom();			// read atom and dec by its size					
				}
				while(remainingSize > 0);
//				printf("  [End subrecurse 'moov' atom]\n");
				break;
	
		

			/* TRAK */		
	
		case	TrackAID:										//'trak'
//				printf("  [Subrecursing 'trak' atom]\n");
				remainingSize = atomSize - 8;					// there are n bytes left in this atom to parse
				do
				{				
					remainingSize -= ReadMovieAtom();			// read atom and dec by its size					
				}
				while(remainingSize > 0);
//				printf("  [End subrecurse 'trak' atom]\n");
				break;


			/* MDIA */		
	
		case	MediaAID:										//'mdia'
//				printf("  [Subrecursing 'mdia' atom]\n");
				remainingSize = atomSize - 8;					// there are n bytes left in this atom to parse
				do
				{				
					remainingSize -= ReadMovieAtom();			// read atom and dec by its size					
				}
				while(remainingSize > 0);
//				printf("  [End subrecurse 'mdia' atom]\n");
				break;


			/* MINF */		
	
		case	MediaInfoAID:									//'minf'
//				printf("  [Subrecursing 'minf' atom]\n");
				remainingSize = atomSize - 8;					// there are n bytes left in this atom to parse
				do
				{				
					remainingSize -= ReadMovieAtom();			// read atom and dec by its size					
				}
				while(remainingSize > 0);
//				printf("  [End subrecurse 'minf' atom]\n");
				break;

			/* DINF */		
	
		case	DataInfoAID:									//'dinf'
//				printf("  [Subrecursing 'dinf' atom]\n");
				ReadMovieAtom();					
//				printf("  [End subrecurse 'dinf' atom]\n");
				break;




			/* STBL */		
	
		case	SampleTableAID:									//'stbl'
				
//				printf("  [Subrecursing 'stbl' atom]\n");
				remainingSize = atomSize - 8;					// there are n bytes left in this atom to parse
				do
				{				
					remainingSize -= ReadMovieAtom();			// read atom and dec by its size					
				}
				while(remainingSize > 0);
//				printf("  [End subrecurse 'stbl' atom]\n");
				break;

			/* STSD */		
	
//		case	STSampleDescAID:								//'stsd'
//				ReadAtom_STSD(atomSize);
//				break;


			/* STCO */		
	
		case	STChunkOffsetAID:								//'stco'
				ReadAtom_STCO(atomSize);
				break;


			/* STSZ */		
	
		case	STSampleSizeAID:								//'stsz'
				ReadAtom_STSZ(atomSize);
				break;


			/* HDLR */		
	
		case	HandlerAID:								//'hdlr'
				ReadAtom_HDLR(atomSize);
				break;
	}


	if (iErr != noErr)
		return(-1);


		/*************************/
		/* SET FPOS TO NEXT ATOM */
		/*************************/
	
	if (atomSize == 0)								// if last atom size was 0 then that was the end
	{
		printf("\n===== that should have been the end.\n");
		return(-1);
	}
	else	
	{
#ifdef TARGET_OS_MAC		
		if (SetFPos(gMovfRefNum, fsFromStart, filePos + atomSize))
			printf("ReadMovieAtom: SetFPos failed, probably EOF?\n");	
#else
		fseek(gFile, filePos + atomSize, SEEK_SET);
		if (ferror(gFile))
			printf("ReadMovieAtom: fseek() failed, probably EOF?\n");	
#endif		
	}
			

	return(atomSize);								// return the size of the atom
}




/********************** READ ATOM:  STCO ****************************/

static void ReadAtom_STCO(long size)
{
long			count;
int				i;
ChunkOffsetAtom	*atom;
int				numEntries;

			/*****************/
			/* READ THE ATOM */
			/*****************/
			
#ifdef TARGET_OS_MAC		
	SetFPos(gMovfRefNum, fsFromMark, -8);		// back up 8 bytes to the atom's start so we can read it all in
#else
	fseek(gFile, -8, SEEK_CUR);
#endif		


				/* ALLOC MEMORY FOR IT */
				//
				// This is a variable size structure, so we need to allocated based on the size of the atom that's passed in
				//
				
	atom = malloc(size);


				/* READ IT */
				
#ifdef TARGET_OS_MAC		
	count = size;							
	if (FSRead(gMovfRefNum, &count, atom) != noErr)
	{
		printf("ReadAtom_STCO:  FSRead failed!\n");
		return;
	}
#else
	fread(atom, size, 1, gFile);		
	if (ferror(gFile))
	{
		printf("ReadAtom_STCO:  fread() failed!\n");
		return;
	}	
#endif		


			/* SEE WHAT KIND OF TRACK WE'VE PARSED INTO AND GET CHUNKS BASED ON THAT */
			
	numEntries = atom->numEntries;
	SwizzleInt(&numEntries);								// convert BigEndian data to LittleEndian (if not Mac)
	
	
	switch(gCurrentTrackMedia)
	{
		case	'pano':
				gPanoChunkOffset = atom->chunkOffsetTable[0];
				SwizzleLong(&gPanoChunkOffset);									// convert BigEndian data to LittleEndian (if not Mac)
//				printf("        Chunk offset to 'pano' is : %d\n", gPanoChunkOffset);
				gCurrentTrackMedia = 0;											// reset this now!
				break;


		case	'vide':
		
				if (numEntries < 6)						// there MUST be at least 6 jpegs or this isn't a cube
				{
					printf("THERE ARE NOT 6 JPEGS IN THIS FILE!  We only support cubic QTVR's, and those have 6 or more JPEGs!\n");
					printf("This appears to only have %d\n", numEntries);
					goto bail;
				}
				
				gFoundJPEGs = true;
				
				
						/* ARE THE IMAGES TILED? */

				gNumTilesPerImage = numEntries / 6;
						
				if (gNumTilesPerImage > 1)
				{
					printf("_____ There are more than 6 entires in the 'vide' track, so this QTVR has tiled images!\n");
					gImagesAreTiled = true;				
					if (numEntries > MAX_IMAGE_OFFSETS)				// are there too many tiles
					{
						printf("THERE APPEAR TO BE TOO MANY TILE IMAGES IN THIS FILE!!!!!!!  %d\n", numEntries);
						goto bail;
					}
				}
				else
				{
					gImagesAreTiled = false;				
				}

						/* EXTRACT THE OFFSETS TO THE IMAGES */
							
				for (i = 0; i < numEntries; i++)
				{
//					printf("       # Chunk Offset entries: %d\n", numEntries);

					for (i = 0; i < numEntries; i++)
					{
						gVideoChunkOffset[i] = atom->chunkOffsetTable[i];
						SwizzleLong(&gVideoChunkOffset[i]);							// convert BigEndian data to LittleEndian (if not Mac)
//						printf("       Chunk offset #%d = %d\n", i, gVideoChunkOffset[i] );
					}
					gCurrentTrackMedia = 0;											// reset this now!
					break;
				}
	}

bail:
	free(atom);
}



/********************** READ ATOM:  STSZ ****************************/

static void ReadAtom_STSZ(long size)
{
long			count;
SampleSizeAtom	*atom;
long			numEntries, i;

			/*****************/
			/* READ THE ATOM */
			/*****************/
		
#ifdef TARGET_OS_MAC		
	SetFPos(gMovfRefNum, fsFromMark, -8);		// back up 8 bytes to the atom's start so we can read it all in
#else
	fseek(gFile, -8, SEEK_CUR);
#endif		
				

				/* ALLOC MEMORY FOR IT */
				//
				// This is a variable size structure, so we need to allocated based on the size of the atom that's passed in
				//
				
	atom = malloc(size);


				/* READ IT */
				
#ifdef TARGET_OS_MAC		
	count = size;							
	if (FSRead(gMovfRefNum, &count, atom) != noErr)
	{
		printf("ReadAtom_STSZ:  FSRead failed!\n");
		return;
	}
#else
	fread(atom, size, 1, gFile);		
	if (ferror(gFile))
	{
		printf("ReadAtom_STSZ:  fread() failed!\n");
		return;
	}	
#endif		

			/* SEE WHAT KIND OF TRACK WE'VE PARSED INTO AND GET CHUNKS BASED ON THAT */

	numEntries = atom->numEntries;
	SwizzleLong(&numEntries);											// convert BigEndian data to LittleEndian (if not Mac)

			
	switch(gCurrentTrackMedia)
	{
		case	'pano':
				gPanoSampleSize = atom->sampleSize;
				SwizzleLong(&gPanoSampleSize);							// convert BigEndian data to LittleEndian (if not Mac)
//				printf("        'pano' sample size = : %d\n", gPanoSampleSize);
				break;
				
		case	'vide':
//				printf("       # Sample Size entries: %d\n", numEntries);

				for (i = 0; i < numEntries; i++)
				{
					gVideoSampleSize[i] = atom->sampleSizeTable[i];
					SwizzleInt(&gVideoSampleSize[i]);
//					printf("       sample size %d = %d\n", i, gVideoSampleSize[i] );
				}
				break;
	}


	free(atom);
}




/********************** READ ATOM:  HDLR ****************************/

static void ReadAtom_HDLR(int size)
{
long			count;
HandlerAtom		*atom;
PublicHandlerInfo	*info;
long			componentSubType;

			/*****************/
			/* READ THE ATOM */
			/*****************/
			
#ifdef TARGET_OS_MAC		
	SetFPos(gMovfRefNum, fsFromMark, -8);		// back up 8 bytes to the atom's start so we can read it all in
#else
	fseek(gFile, -8, SEEK_CUR);
#endif		


				/* ALLOC MEMORY FOR IT */
				//
				// This is a variable size structure, so we need to allocated based on the size of the atom that's passed in
				//
				
	atom = malloc(size);


				/* READ IT */
				
#ifdef TARGET_OS_MAC		
	count = size;							
	if (FSRead(gMovfRefNum, &count, atom) != noErr)
	{
		printf("ReadAtom_HDLR:  FSRead failed!\n");
		return;
	}
#else
	fread(atom, size, 1, gFile);		
	if (ferror(gFile))
	{
		printf("ReadAtom_HDLR:  fread() failed!\n");
		return;
	}	
#endif		

			/* POINT TO HANDLER INFO */
			
	info = &atom->hInfo;
	
	componentSubType = info->componentSubType;									// get comp sub type
	SwizzleLong(&componentSubType);												// convert BigEndian data to LittleEndian (if not Mac)


	if (componentSubType == 'pano')
	{
//		printf("ReadAtom_HDLR:  We found the 'pano' media!\n");
		gCurrentTrackMedia = 'pano';
	}
	else
	if (componentSubType == 'vide')
	{
		if (!gAlreadyGotVideoMedia)					// if we already got the 'vide' then this one is just the fast-start track, so ignore it!
		{
//			printf("ReadAtom_HDLR:  We found the 'vide' media!\n");
			gCurrentTrackMedia = 'vide';
			gAlreadyGotVideoMedia = true;
		}
		else
			printf("Found an additional 'vide' media track, but we're going to ignore it since it's probably the fast-start track...\n");
	}


	free(atom);
}


#if 0

/********************** READ ATOM:  STSD ****************************/

static void ReadAtom_STSD(int size)
{
long					count;
SampleDescriptionAtom	*atom;
long					numEntries, i;
SampleDescription		*desc;

			/*****************/
			/* READ THE ATOM */
			/*****************/
			
#ifdef TARGET_OS_MAC		
	SetFPos(gMovfRefNum, fsFromMark, -8);		// back up 8 bytes to the atom's start so we can read it all in
#else
	fseek(gFile, -8, SEEK_CUR);
#endif		


				/* ALLOC MEMORY FOR IT */
				//
				// This is a variable size structure, so we need to allocated based on the size of the atom that's passed in
				//
				
	atom = malloc(size);


				/* READ IT */
				
#ifdef TARGET_OS_MAC		
	count = size;							
	if (FSRead(gMovfRefNum, &count, atom) != noErr)
	{
		printf("ReadAtom_STSD:  FSRead failed!\n");
		return;
	}
#else
	fread(atom, size, 1, gFile);		
	if (ferror(gFile))
	{
		printf("ReadAtom_STSD:  fread() failed!\n");
		return;
	}	
#endif		
	
	
	numEntries = atom->numEntries;
	SwizzleLong(&numEntries);												// convert BigEndian data to LittleEndian (if not Mac)
//	printf("# entries in STSD = %d\n", numEntries);
	
	for (i = 0; i < numEntries; i++)
	{
		desc = &atom->sampleDescTable[i];									// point to SampleDescription
		dataFormat = desc->dataFormat;										// get dataFormat
		SwizzleLong(&dataFormat);											// convert BigEndian data to LittleEndian (if not Mac)
		
		if (dataFormat == 'qtvr')
		{
			printf("Sample Description is 'qtvr'\n");
		}
		else
		if (dataFormat == 'jpeg')
		{
			printf("Sample Description is 'jpeg'\n");
		}
		
	}
	

	free(atom);
}
#endif



#pragma mark -



/********************* SEEK AND EXTRACT IMAGES ***************************/
//
// Finds the 6 cube faces JPEG's in the .mov file and then draws them into texture buffers.
//

static void SeekAndExtractImages(int instanceNum)
{
long	count;
int		i;
OSErr	iErr;
Ptr		jpegData;

				/* SEE IF WE NEED TO SPECIAL-CASE TILED IMAGES */
				
	if (gImagesAreTiled)
	{
		SeekAndExtractImages_Tiled(instanceNum);
		return;	
	}

	printf("\n\n_______SEEK & EXTRACT IMAGES_______\n\n");


	if (!gFoundJPEGs)
	{
		printf("No usable JPEG images were found, or we didn't find 6 which is needed to make a cubic pano\n");
		return;
	}

	for (i = 0; i < 6; i++)
	{	
//		printf("Processing image # %d...\n", i);

		gMyInstances[instanceNum].decodeStep = i;					// tell thermometer what to display
		OGL_DrawScene(instanceNum);
		
					/***************************/
					/* SEEK TO THE JPEG'S DATA */
					/***************************/
					
#ifdef TARGET_OS_MAC		
		iErr = SetFPos(gMovfRefNum, fsFromStart, gVideoChunkOffset[i]);
		if (iErr)
		{
			printf("SeekAndExtractImages:  SetFPos failed!  %d\n", iErr);
			continue;
		}	
#else
		fseek(gFile, gVideoChunkOffset[i], SEEK_SET);
#endif		
		
		
					/***********************/
					/* ALLOC MEMORY FOR IT */
					/***********************/
					
		jpegData = malloc(gVideoSampleSize[i]);
		if (jpegData == nil)
		{
			printf("SeekAndExtractImages: malloc failed!\n");
			continue;		
		}
		
		
						/**************/
						/* READ IT IN */
						/**************/
						
#ifdef TARGET_OS_MAC		
		count = gVideoSampleSize[i];	
		iErr = FSRead(gMovfRefNum, &count, jpegData);
		if (iErr)
		{
			printf("SeekAndExtractImages:  FSRead failed!  %d\n", iErr);
			continue;
		}	
#else
		fread(jpegData, gVideoSampleSize[i], 1, gFile);		
		if (ferror(gFile))
		{
			printf("SeekAndExtractImages:  fread() failed!  %d\n", iErr);
			continue;
		}	
#endif		


				/****************************************************/
				/* DRAW AND SCALE THE IMAGE INTO OUR TEXTURE BUFFER */
				/****************************************************/
				
#ifdef TARGET_OS_MAC
		JPEGToTexture_Mac(instanceNum, i, jpegData);
#else
		JPEGToTexture_Windows(instanceNum, i, jpegData);
#endif		

		free(jpegData);		
					
	}



	gMyInstances[instanceNum].texturesLoaded = true;

}





/********************* SEEK AND EXTRACT IMAGES:  TILED ***************************/

static void SeekAndExtractImages_Tiled(int instanceNum)
{
int		i;
int		tileDimensions, compSize;



	printf("\n\n_______SEEK & EXTRACT TILED IMAGES_______\n\n");

	tileDimensions = sqrt((float)gNumTilesPerImage);
	
	printf("tileDimensions = %d\n", tileDimensions);


	for (i = 0; i < 6; i++)										// for each Cube face...
	{	
		int		chunkNum = i * gNumTilesPerImage;
		
				/* LOAD ALL OF THE TILE IMAGES FOR THIS FACE */
					
		printf("\nLoading tiles for Face #%d\n", i);

		gMyInstances[instanceNum].decodeStep = i;					// tell thermometer what to display
		OGL_DrawScene(instanceNum);

		LoadTiles(chunkNum);


			/* CALCULATE THE DIMENSIONS OF THE TARGET IMAGE */
			//
			// Let's assume that each tile is the same size and is square
			//

		compSize = gTileSize[0] * tileDimensions;					
		printf("Face #%d has a composite size of %d\n", i, compSize);
					
				/* COMPOSITE TILES */
				
		CompositeTiles(i, tileDimensions, compSize, instanceNum);
					
							
				
				/* DISPOSE OF THE TILE DATA */
						
		FreeTiles();
					
	}


	
	gMyInstances[instanceNum].texturesLoaded = true;
}


/********************* COMPOSITE TILES *************************/
//
// This function creates a large buffer into which we'll draw all of the tiles to create the actual cube face.
// The Mac uses GWorlds as buffer wrappers that all of the Mac pixel copy function use.
//

static void CompositeTiles(int faceNum, int tileDimensions, int textureSize, int instanceNum)
{
OSErr	iErr;
int		t, h, v;
int		tileSize;
Rect	fromRect, toRect, scaledRect;

		/********************************************/
		/* CREATE BUFFER & GWORLD FOR THE COMPOSITE */
		/********************************************/

	gTempImageBuffer = malloc(textureSize * textureSize * 4);		// alloc buffer to hold image with no padding
	if (gTempImageBuffer == nil)
	{
		printf("CompositeTiles: malloc failed!\n");
		return;	
	}

	gTempImageRect.left = gTempImageRect.top = 0;
	gTempImageRect.right = gTempImageRect.bottom = textureSize;
	
#ifdef TARGET_OS_MAC
	iErr = QTNewGWorldFromPtr(&gTempImageGWorld, k32ARGBPixelFormat, &gTempImageRect, nil, nil, 0, gTempImageBuffer, textureSize * 4);
	if (iErr)
	{
		printf("CompositeTiles: NewGWorld failed.  %d\n", iErr);
		return;
	}
#else
	----- WINDOWS OR LINUX CODE GOES HERE -----
#endif		

		/*************************************/
		/* DRAW EACH TILE INTO THE COMPOSITE */
		/*************************************/

	tileSize = textureSize / tileDimensions;

	fromRect.left = fromRect.top = 0;										// set rect for tile gworld
	fromRect.right = fromRect.bottom = tileSize;


	t = 0;
	
	for (v = 0; v < tileDimensions; v++)
	{
		for (h = 0; h < tileDimensions; h++)
		{
			toRect.left		= h * tileSize;
			toRect.right	= toRect.left + tileSize;
			
			toRect.top		= v * tileSize;
			toRect.bottom	= toRect.top + tileSize;
		
#ifdef TARGET_OS_MAC
			CopyPixelsFromBufferToBuffer_Mac(tileGWorlds[t], gTempImageGWorld, &fromRect, &toRect);
#else
	----- WINDOWS OR LINUX CODE GOES HERE -----
#endif		
			
			t++;
		}
	}
	
	
				/*************************/
				/* CREATE TEXTURE GWORLD */
				/*************************/
				
	CalcAllowedTextureSize(instanceNum, &gTempImageRect, &scaledRect);			// what size shall we make the texture?
	
				
					/* ALLOCATE BUFFER & GWORLD FOR TEXTURE */
					
	gMyInstances[instanceNum].textureBuffers[faceNum] = malloc((int)scaledRect.right * (int)scaledRect.bottom * 4);		// alloc buffer to hold image with no padding
	if (gMyInstances[instanceNum].textureBuffers[faceNum] == nil)
	{
		printf("CompositeTiles: malloc failed!\n");
		return;	
	}
		
#ifdef TARGET_OS_MAC
	iErr = QTNewGWorldFromPtr(&gMyInstances[instanceNum].textureGWorlds[faceNum], k32ARGBPixelFormat, &scaledRect, nil, nil, 0,
							gMyInstances[instanceNum].textureBuffers[faceNum], (int)scaledRect.right * 4);
	if (iErr)
	{
		printf("CompositeTiles: NewGWorld failed.  %d\n", iErr);
		return;
	}
							
			/* SCALE THE LOADED TEMP IMAGE DOWN INTO THE TEXTURE GWORLD & CREATE TEXTURE */
				
	gMyInstances[instanceNum].textureNames[faceNum] = ScaleTempImageToTexture_Mac(instanceNum,
																		gMyInstances[instanceNum].textureGWorlds[faceNum],
																		gMyInstances[instanceNum].textureBuffers[faceNum],
																		&scaledRect,
																		gMyInstances[instanceNum].compressTextures); 
#else
	----- WINDOWS OR LINUX CODE GOES HERE -----
#endif		
																		
		
}



/***************** FREE TILES ****************************/

static void FreeTiles(void)
{
int		t;

	for (t = 0; t < gNumTilesPerImage; t++)
	{
		free (tileBuffers[t]);
		tileBuffers[t] = nil;
	
#ifdef TARGET_OS_MAC
		DisposeGWorld(tileGWorlds[t]);
		tileGWorlds[t] = nil;
#else
	----- WINDOWS OR LINUX CODE GOES HERE -----
#endif		
	}
	
	if (gTempImageBuffer)
	{
		free(gTempImageBuffer);
		gTempImageBuffer = nil;
	}
	
#ifdef TARGET_OS_MAC
	if (gTempImageGWorld)
	{
		DisposeGWorld(gTempImageGWorld);
		gTempImageGWorld = nil;
	}
#else
	----- WINDOWS OR LINUX CODE GOES HERE -----
#endif		
}


/*********************** LOAD TILES *******************************/

static void LoadTiles(int chunkNum)
{		
int		t, i;
OSErr	iErr;
Ptr		jpegData;
long	count;

			/****************************************/
			/* LOAD ALL OF THE TILES FOR THIS IMAGE */
			/****************************************/

	for (t = 0; t < gNumTilesPerImage; t++)
	{
		i = chunkNum + t;
	
//		printf("Processing tile #%d...\n", t);
	
					/* SEEK TO THE JPEG'S DATA */
					
#ifdef TARGET_OS_MAC
		iErr = SetFPos(gMovfRefNum, fsFromStart, gVideoChunkOffset[i]);
		if (iErr)
		{
			printf("LoadTiles:  SetFPos failed!  %d\n", iErr);
			continue;
		}	
#else
		fseek(gFile, gVideoChunkOffset[i], SEEK_SET);
		if (ferror(gFile))
		{
			printf("LoadTiles:  fseek failed!\n");
			continue;
		}
#endif		

		
					/* ALLOC MEMORY FOR IT */
					
		jpegData = malloc(gVideoSampleSize[i]);
		if (jpegData == nil)
		{
			printf("LoadTiles: malloc failed!\n");
			continue;		
		}			
		
						/* READ IT IN */
						
#ifdef TARGET_OS_MAC
		count = gVideoSampleSize[i];	
		iErr = FSRead(gMovfRefNum, &count, jpegData);
		if (iErr)
		{
			printf("LoadTiles:  FSRead failed!  %d\n", iErr);
			continue;
		}	
#else
		fread(jpegData, gVideoSampleSize[i], 1, gFile);		
		if (ferror(gFile))
		{
			printf("LoadTiles:  fread() failed!  %d\n", iErr);
			continue;
		}	
#endif		


					/* DECODE IT */
					
		tileBuffers[t] = nil;									// do this since DrawJPEGFromRAMIntoBuffer checks these for safety
		tileGWorlds[t] = nil;

#ifdef TARGET_OS_MAC
		DrawJPEGFromRAMIntoBuffer_Mac(jpegData, gVideoSampleSize[i], &tileGWorlds[t], &tileBuffers[t]);
#else
		DrawJPEGFromRAMIntoBuffer_Windows(jpegData, gVideoSampleSize[i], &tileBuffers[t]);
#endif		

		free(jpegData);	
		jpegData = nil;	


		gTileSize[t] = gTempImageRect.right - gTempImageRect.left;		// remember the size of this tile (assume height & width are same)



	}
}


#pragma mark -


/**************************** SWIZZLE LONG ***********************************/
//
// Converts a 4-byte long to reverse the Big-Little Endian order of the bytes.
//

static void SwizzleLong(long *value)
{
#ifdef TARGET_OS_MAC						// don't do this for Mac since we keep big-endian order on PowerPC

#pragma unused (value)

#else

u_char	*n, b1,b2,b3,b4;

	n = (u_char *)value;

	b1 = n[0];								// get the 4 bytes
	b2 = n[1];
	b3 = n[2];
	b4 = n[3];

	n[0] = b4;								// save in reverse order
	n[1] = b3;
	n[2] = b2;
	n[3] = b1;
#endif	
}


/**************************** SWIZZLE INT ***********************************/
//
// Converts a 4-byte int to reverse the Big-Little Endian order of the bytes.
//
// Quicktime movies are in BigEndian format, so when reading on a PC or other Little-Endian
// machine, we've got to swap the bytes around.
//

static void SwizzleInt(int *value)
{
#ifdef TARGET_OS_MAC						// don't do this for Mac since we keep big-endian order on PowerPC

#pragma unused (value)

#else

u_char	*n, b1,b2,b3,b4;

	n = (u_char *)value;

	b1 = n[0];								// get the 4 bytes
	b2 = n[1];
	b3 = n[2];
	b4 = n[3];

	n[0] = b4;								// save in reverse order
	n[1] = b3;
	n[2] = b2;
	n[3] = b1;
#endif	
}



