/*==LICENSE==*

CyanWorlds.com Engine - MMOG client, server and tools
Copyright (C) 2011  Cyan Worlds, Inc.

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program 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.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.

You can contact Cyan Worlds, Inc. by email legal@cyan.com
 or by snail mail at:
      Cyan Worlds, Inc.
      14617 N Newport Hwy
      Mead, WA   99021

*==LICENSE==*/
#include "hsTypes.h"
#include "max.h"
#include <commdlg.h>
#include "bmmlib.h"

#include "../../Plasma/PubUtilLib/plGImage/plMipmap.h"
#include "hsExceptionStack.h"
#include "../../Plasma/PubUtilLib/plGImage/hsCodecManager.h"

#include "plBitmapCreator.h"

#include "../MaxMain/plPluginResManager.h"
#include "../MaxExport/plErrorMsg.h"
#include "../MaxPlasmaMtls/Layers/plStaticEnvLayer.h"

#include "../plGImage/plMipmap.h"
#include "../plGImage/plDynamicTextMap.h"
#include "../plGImage/plCubicEnvironmap.h"
#include "../pnKeyedObject/plKey.h"
#include "../pnKeyedObject/plUoid.h"
#include "../plResMgr/plRegistryHelpers.h"
#include "../plResMgr/plLocalization.h"
#include "../plAgeDescription/plAgeDescription.h"

//// plCommonBitmapLib ///////////////////////////////////////////////////////
//	Derived class for our textures, since they all go in a common page
//	(namely, "Textures")

#include "../MaxMain/plCommonObjLib.h"

class plCommonBitmapLib : public plCommonObjLib
{
	public:
		virtual hsBool	IsInteresting( const plKey &objectKey )
		{
			if( objectKey->GetUoid().GetClassType() == plCubicEnvironmap::Index() ||
				objectKey->GetUoid().GetClassType() == plMipmap::Index() )
			{
				return true;
			}
			return false;
		}
};

static plCommonBitmapLib		sCommonBitmapLib;


plBitmapCreator::plBitmapCreator()
{
	fErrorMsg = nil;
}
plBitmapCreator::~plBitmapCreator()
{
}

plBitmapCreator	&plBitmapCreator::Instance()
{
	static plBitmapCreator	fInstance;
	return fInstance;
}

void	plBitmapCreator::Init( hsBool save, plErrorMsg *msg )
{
	fErrorMsg = msg;
}

void	plBitmapCreator::DeInit( void )
{
	CleanUpMaps();
}

void	plBitmapCreator::CleanUpMaps( void )
{
	sCommonBitmapLib.ClearObjectList();
}

void	plBitmapCreator::DeleteExportedBitmap( const plKey &constKey )
{
	plKey key = constKey;
	sCommonBitmapLib.RemoveObjectAndKey( key );
}

//
// Create Bitmap
//
plMipmap *plBitmapCreator::ICreateBitmap(plBitmapData *bd)
{
	hsGuardBegin("hsConverterUtils::CreateBitmap");

    // Load the bitmap
    BitmapInfo bi;
    bi.SetName(bd->fileName);

#if 0 // This isn't really an issue since the textures are packed -Colin
	const int kMaxFileNameLength = 30;
	if (strlen(bi.Filename()) > kMaxFileNameLength)
	{
		// Allow to continue, But make it painful
		char errStr[256];
		sprintf(errStr, "File name longer than %d, won't burn to CD (%s)", kMaxFileNameLength, bi.Filename());//bitmapTex->GetName());
		MessageBox(GetActiveWindow(), errStr, bd->fileName, MB_OK|MB_ICONEXCLAMATION);	
	}
#endif

	hsBool notMipped = (bd->texFlags & plMipmap::kForceOneMipLevel) != 0;
	float sigma = bd->sig;
    
    // Load the bitmap
	Bitmap *bm = TheManager->Load(&bi);
	if (!bm)
	{
		// FIXME
		/*
		if (fErrorMsg->Set(!(fWarned & kWarnedNoMoreBitmapLoadErr), 
			"Error loading bitmap", pathName).CheckAskOrCancel())
		{
			fWarned |= kWarnedNoMoreBitmapLoadErr;
		}
		*/
		return nil;
	}
	BitmapStorage *storage = bm->Storage();
	BitmapInfo *bInfo = &storage->bi;

	ICheckOutBitmap(bInfo, bm, bd->fileName);

    //
    // Create a plMipmap 
    //
    plMipmap *hBitmap = TRACKED_NEW plMipmap;
    if( (bm->Width() ^ (bm->Width() & -bm->Width()))
        ||(bm->Height() ^ (bm->Height() & -bm->Height())) )
    {
        IResampBitmap(bm, *hBitmap);
    }
    else if( ((bm->Width() >> 3) > bm->Height())||((bm->Height() >> 3) > bm->Width()) )
    {
        IResampBitmap(bm, *hBitmap);
    }
    else
    {
        ICopyBitmap(bm, *hBitmap);
    }
	bm->DeleteThis();

	if( bd->invertAlpha )
		IInvertAlpha(*hBitmap);

	// Do it
	plMipmap *hMipmap = nil;
	if (sigma > 0.f)
	{
		hMipmap = TRACKED_NEW plMipmap(hBitmap, sigma, bd->createFlags, bd->detailDropoffStart, 
								bd->detailDropoffStop, bd->detailMax, bd->detailMin);
	}
	else
	{
		hMipmap = TRACKED_NEW plMipmap(hBitmap, -1.f, bd->createFlags, bd->detailDropoffStart, 
								bd->detailDropoffStop, bd->detailMax, bd->detailMin);
	}
	delete hBitmap;

	/// Clamp the border if we're using clamping
	if( bd->clampFlags != 0 )
	{
		hMipmap->EnsureKonstantBorder( ( bd->clampFlags & plBitmapData::kClampU ) ? true : false,
									   ( bd->clampFlags & plBitmapData::kClampV ) ? true : false );
	}

	/// Cut this down to whatever size we were told to :)
	if( bd->maxDimension != 0 )
		hMipmap->ClipToMaxSize( bd->maxDimension );

	if( notMipped )
	{
		// Done AFTER ClipToMaxSize() so we still get the export size specified 
		hMipmap->RemoveMipping();
	}

	hBitmap = hMipmap;
        
	UInt32	flagsToSet = 0;

	if( bd->texFlags & plMipmap::kNoMaxSize )
		flagsToSet |= plMipmap::kNoMaxSize;
	if( bd->texFlags & plMipmap::kHalfSize )
		flagsToSet |= plMipmap::kHalfSize;
	if( bd->texFlags & plMipmap::kDontThrowAwayImage )
		flagsToSet |= plMipmap::kDontThrowAwayImage;

	hBitmap->SetFlags( hBitmap->GetFlags() | flagsToSet );
	if (bd->useJPEG)
		hBitmap->fCompressionType = plMipmap::kJPEGCompression;

	// FIXME
	if (/*fSave &&*/ !(bd->texFlags & plMipmap::kForceNonCompressed) && !bd->useJPEG)
	{
		// Are we on?  Check Plasma Util panel
//		SwitchUtil *pu = (SwitchUtil *)CreateInstance(UTILITY_CLASS_ID, PlasmaUtilClassID);
//		if (!pu || pu->TextureCompressionEnabled()) 
		{
			plMipmap *compressed = hsCodecManager::Instance().CreateCompressedMipmap(plMipmap::kDirectXCompression, hBitmap);
//				hsDXTSoftwareCodec::Instance().CreateCompressedBitmap(hBitmap);
//				hsDXTDirectXCodec::Instance().CreateCompressedBitmap(hBitmap);

			if (compressed)
			{
				delete hBitmap;
				hBitmap = compressed;
				hBitmap->SetFlags( hBitmap->GetFlags() | flagsToSet );
			}
		}
	}

	return hBitmap;
	hsGuardEnd; 
}




//
// Verify that bitmap is the correct type/size
//
void plBitmapCreator::ICheckOutBitmap(BitmapInfo* bInfo, Bitmap* bm, const char *fileName)
{
	hsGuardBegin("hsConverterUtils::ICheckOutBitmap");

	// Check out bitmap
	if (bm->Flags() & MAP_FLIPPED)
		MessageBox(GetActiveWindow(), "Bitmap is flipped horizontally", fileName, MB_OK);	
	if (bm->Flags() & MAP_INVERTED)
		MessageBox(GetActiveWindow(), "Bitmap is inverted vertically", fileName, MB_OK);	

	if (bInfo->Flags() & MAP_FLIPPED)
		MessageBox(GetActiveWindow(), "BI:Bitmap is flipped horizontally", fileName, MB_OK);	
	if (bInfo->Flags() & MAP_INVERTED)
		MessageBox(GetActiveWindow(), "BI:Bitmap is inverted vertically", fileName, MB_OK);	

	hsGuardEnd;
}

int plBitmapCreator::IResampBitmap(Bitmap *bm, plMipmap &hBitmap)
{
	hsGuardBegin("hsConverterUtils::IResampBitmap");

	BitmapStorage *storage = bm->Storage();
	BitmapInfo *bInfo = &storage->bi;

	int dbgW = bm->Width(), dbgH = bm->Height();
	int it;
	for( it = 1; it <= bm->Width(); it <<= 1 );
	it >>= 1;
	hBitmap.fWidth = it;
	for( it = 1; it <= bm->Height(); it <<= 1 );
	it >>= 1;
	hBitmap.fHeight = it;
	if( (hBitmap.fHeight >> 3) > hBitmap.fWidth )
		hBitmap.fHeight = hBitmap.fWidth << 3;
	else
	if( (hBitmap.fWidth >> 3) > hBitmap.fHeight )
		hBitmap.fWidth = hBitmap.fHeight << 3;

	hBitmap.fPixelSize	= 32;
	hBitmap.fRowBytes	= hBitmap.fWidth * hBitmap.fPixelSize >> 3;
	hBitmap.fNumLevels = 1;

	hBitmap.fImage		= HSMemory::New(hBitmap.fRowBytes * hBitmap.fHeight);

#ifdef COLOR_BLACK_WHITE
	hBitmap.fFlags |= plMipmap::kColorWhite | plMipmap::kColorBlack;
#endif // COLOR_BLACK_WHITE
	hBitmap.fFlags &= ~( plMipmap::kAlphaBitFlag | plMipmap::kAlphaChannelFlag );

	int y,x;
	float scaleY, scaleX;
	hsRGBAColor32  *dstColor;
	dstColor = (hsRGBAColor32*)hBitmap.fImage;

	scaleX = ((float)bm->Width())/(float)hBitmap.fWidth;
	scaleY = ((float)bm->Height())/(float)hBitmap.fHeight;
	for (y = 0; y < hBitmap.fHeight; y++)
	{
		for (x = 0; x < hBitmap.fWidth; x++)
		{
			BMM_Color_64 c64_00, c64_01, c64_10, c64_11;
			int ix, iy;
			float fracX, fracY;
			float t;
			t = x * scaleX;
			ix = (int)t;
			fracX = t-ix;
			t = y * scaleY;
			iy = (int)t;
			fracY = t-iy;

			int ret = storage->GetPixels(ix,iy,1,&c64_00);
// FIXME
//			fErrorMsg->Set(ret == 0, "ResampBitmap", "Failure getting pixels %dX%d", x, y).Check();
			ret = storage->GetPixels(ix+1,iy,1,&c64_10);
			ret = storage->GetPixels(ix,iy+1,1,&c64_01);
			ret = storage->GetPixels(ix+1,iy+1,1,&c64_11);

			dstColor->r = (unsigned char)(255.0 / 65535.0
				* ( c64_00.r * (1.f - fracX) * (1.f - fracY)
				  + c64_10.r * (      fracX) * (1.f - fracY)
				  + c64_01.r * (1.f - fracX) * (      fracY)
				  + c64_11.r * (      fracX) * (      fracY) ));
			dstColor->g = (unsigned char)(255.0 / 65535.0
				* ( c64_00.g * (1.f - fracX) * (1.f - fracY)
				  + c64_10.g * (      fracX) * (1.f - fracY)
				  + c64_01.g * (1.f - fracX) * (      fracY)
				  + c64_11.g * (      fracX) * (      fracY) ));
			dstColor->b =  (unsigned char)(255.0 / 65535.0
				* ( c64_00.b * (1.f - fracX) * (1.f - fracY)
				  + c64_10.b * (      fracX) * (1.f - fracY)
				  + c64_01.b * (1.f - fracX) * (      fracY)
				  + c64_11.b * (      fracX) * (      fracY) ));
			dstColor->a =  (unsigned char)(255.0 / 65535.0
				* ( c64_00.a * (1.f - fracX) * (1.f - fracY)
				  + c64_10.a * (      fracX) * (1.f - fracY)
				  + c64_01.a * (1.f - fracX) * (      fracY)
				  + c64_11.a * (      fracX) * (      fracY) ));


#ifdef COLOR_BLACK_WHITE
			if( dstColor->r | dstColor->g | dstColor->b )
				hBitmap.fFlags &= ~plMipmap::kColorBlack;
			if( ~(dstColor->r & dstColor->g & dstColor->b) )
				hBitmap.fFlags &= ~plMipmap::kColorWhite;
#endif // COLOR_BLACK_WHITE

			if( dstColor->a < 255 )
			{
				hBitmap.fFlags |= plMipmap::kAlphaBitFlag;
				if( dstColor->a > 0 )
					hBitmap.fFlags |= plMipmap::kAlphaChannelFlag;
			}
			dstColor++;
		}
	}
	if( hBitmap.fFlags & plMipmap::kAlphaChannelFlag )
		hBitmap.fFlags &= ~plMipmap::kAlphaBitFlag;

	return 0;
	hsGuardEnd; 
}

int plBitmapCreator::ICopyBitmap(Bitmap *bm, plMipmap &hBitmap)
{
	hsGuardBegin("hsConverterUtils::ICopyBitmap");

	BitmapStorage *storage = bm->Storage();
	BitmapInfo *bInfo = &storage->bi;

	hBitmap.fWidth		= bm->Width();
	hBitmap.fHeight		= bm->Height();
	hBitmap.fPixelSize	= 32;
	hBitmap.fRowBytes	= bm->Width()*hBitmap.fPixelSize/8;
	hBitmap.fNumLevels = 1;

	hBitmap.fImage		= HSMemory::New(hBitmap.fRowBytes * hBitmap.fHeight);

#ifdef COLOR_BLACK_WHITE
	hBitmap.fFlags |= plMipmap::kColorWhite | plMipmap::kColorBlack;
#endif // COLOR_BLACK_WHITE
	hBitmap.fFlags &= ~( plMipmap::kAlphaBitFlag | plMipmap::kAlphaChannelFlag );

	int y,x;
	hsRGBAColor32  *dstColor;
	dstColor = (hsRGBAColor32*)hBitmap.fImage;

	for (y = 0; y < hBitmap.fHeight; y++)
	{
		for (x = 0; x < hBitmap.fWidth; x++)
		{
			BMM_Color_64 c64;
			int ret = storage->GetPixels(x,y,1,&c64);
// FIXME
//			fErrorMsg->Set(ret == 0, "CopyBitmap", "Failure getting pixels %dX%d", x, y).Check();

			// Convert from 16 bits to 8 bits
			dstColor->r = (char)(255.0*c64.r/65535.0);
			dstColor->g = (char)(255.0*c64.g/65535.0);
			dstColor->b = (char)(255.0*c64.b/65535.0);
			dstColor->a = (char)(255.0*c64.a/65535.0);

#ifdef COLOR_BLACK_WHITE
			if( dstColor->r | dstColor->g | dstColor->b )
				hBitmap.fFlags &= ~plMipmap::kColorBlack;
			if( ~(dstColor->r & dstColor->g & dstColor->b) )
				hBitmap.fFlags &= ~plMipmap::kColorWhite;
#endif // COLOR_BLACK_WHITE

			if( dstColor->a < 255 )
			{
				hBitmap.fFlags |= plMipmap::kAlphaBitFlag;
				if( dstColor->a > 0 )
					hBitmap.fFlags |= plMipmap::kAlphaChannelFlag;
			}
			dstColor++;
		}
	}
	if( hBitmap.fFlags & plMipmap::kAlphaChannelFlag )
		hBitmap.fFlags &= ~plMipmap::kAlphaBitFlag;

	return 0;
	hsGuardEnd; 
}

int plBitmapCreator::IInvertAlpha(plMipmap& hBitmap)
{
	hsGuardBegin("hsConverterUtils::ICopyBitmap");

	hsAssert(hBitmap.fPixelSize == 32, "Only RGBA32 implemented");
	if( hBitmap.fPixelSize != 32 )
		return -1;

	hsRGBAColor32* dstColor = (hsRGBAColor32*)hBitmap.fImage;
	int n = hBitmap.GetWidth() * hBitmap.GetHeight();
	int i;
	for( i = 0; i < n; i++ )
	{
		dstColor->a = 255 - dstColor->a;
		dstColor++;
	}

	return 0;
	hsGuardEnd; 
}

plBitmap *plBitmapCreator::CreateTexture(plBitmapData *bd, const plLocation &loc, int clipID)
{
	plBitmap* bm = ICreateTexture(bd, loc, clipID);

	for (int i = 0; i < plLocalization::GetNumLocales(); i++)
	{
		char localName[MAX_PATH];
		if (plLocalization::ExportGetLocalized(bd->fileName, i, localName))
		{
			const char* oldName = bd->fileName;
			bd->fileName = localName;
			ICreateTexture(bd, loc, clipID);
			bd->fileName = oldName;
		}
	}

	return bm;
}

//// ICreateTexture ////////////////////////////////////////////////////////////
//	Plasma texture creator.  Pass it a completed bitmapdata structure and it 
//	returns a registered texture pointer, or nil if something goes wrong.
//
//	9.14.2001 mcn - clipID added to uniquely identify mipmaps that have been 
//	rescaled differently (approximately represents how many powers of 2 it was 
//	scaled down from the original source).
//
//	3.29.2002 mcn - Moved to plBitmapCreator, where it really belongs, and
//	added code to handle tracking/cleaning up all materials that are exported.

plBitmap *plBitmapCreator::ICreateTexture( plBitmapData *bd, const plLocation &loc, int clipID )
{
	hsGuardBegin( "plBitmapCreator::CreateTexture" );

	const plLocation &textureLoc = plPluginResManager::ResMgr()->GetCommonPage( loc, plAgeDescription::kTextures );

	if( !bd )
	{
		fErrorMsg->Set( true, "Bitmap Error", "No bitmap data" ).Show();
		fErrorMsg->Set();
		return nil;
	}

	if( bd->fileName == nil || bd->fileName[ 0 ] == 0 )
	{
		fErrorMsg->Set( true, "Bitmap Error", "Material texture has null bitmap name." ).Show();
		fErrorMsg->Set();	
		return nil;
	}

	// Get and mangle key name
	char name[ 256 ], temp[ 256 ];
	_splitpath(bd->fileName, NULL, NULL, temp, NULL);

	// Somehow, sometimes, we get the same file in with different cases. So we need to force the
	// case identical all the time, else the patching process for dat files will think they're
	// "different" when they're really not
	strlwr( temp );

	/// Mangle name for detail textures, so we don't end up overwriting settings elsewhere
	if( bd->createFlags & plMipmap::kCreateDetailMask )
	{	
		// Mangle of the form: name@dropStart&dropStop&max&min
		if( clipID != -1 )
			sprintf( name, "%s*%x#%d@%s&%3.2f&%3.2f&%3.2f&%3.2f", temp, bd->texFlags, clipID, 
					bd->createFlags & plMipmap::kCreateDetailAlpha ? "al" : ( bd->createFlags & plMipmap::kCreateDetailAdd ? "ad" : "mu" ),
					bd->detailDropoffStart, bd->detailDropoffStop, bd->detailMax, bd->detailMin );
		else
			sprintf( name, "%s*%x@%s&%3.2f&%3.2f&%3.2f&%3.2f", temp, bd->texFlags, 
					bd->createFlags & plMipmap::kCreateDetailAlpha ? "al" : ( bd->createFlags == plMipmap::kCreateDetailAdd ? "ad" : "mu" ),
					bd->detailDropoffStart, bd->detailDropoffStop, bd->detailMax, bd->detailMin );
	}
	else if( clipID != -1 )
		sprintf( name, "%s*%x#%d", temp, bd->texFlags, clipID );
	else
		sprintf( name, "%s*%x", temp, bd->texFlags );
	if( bd->invertAlpha )
		strcat( name, "_inva" );
	strcat( name, ".hsm" );


	// Has this texture been used before?
	plKey key;

	plBitmap *texture = plBitmap::ConvertNoRef( sCommonBitmapLib.FindObject( name, ( bd->isStaticCubicEnvMap ) ? plCubicEnvironmap::Index() : plMipmap::Index() ) );
	//hsAssert( texture == nil || texture->GetKey()->GetUoid().GetLocation() == textureLoc, "Somehow our texture objectLib has a texture not in the right page? Should be harmless tho..." );

	// Texture reuse optimization
	if( texture )
	{
		WIN32_FILE_ATTRIBUTE_DATA fileAttrib;
		GetFileAttributesEx(bd->fileName, GetFileExInfoStandard, &fileAttrib);
		FILETIME &fileTime = fileAttrib.ftLastWriteTime;

		// If this texture has been modified since the last export, delete the old version but reuse the key
		if (!texture->IsSameModifiedTime(fileTime.dwLowDateTime, fileTime.dwHighDateTime))
		{
			DeleteExportedBitmap( texture->GetKey() );
			texture = nil;
			key = nil;
		}
	}

	if( texture )
	{
		// If it's in the registry, great, use it.
		if( bd->texFlags & plMipmap::kNoMaxSize )
			texture->SetFlags( texture->GetFlags() | plMipmap::kNoMaxSize );

		if( bd->texFlags & plMipmap::kHalfSize )
			texture->SetFlags( texture->GetFlags() | plMipmap::kHalfSize );
	}
	else
	{
		// If it hasn't been used before, make a new texture
		if( bd->isStaticCubicEnvMap )
		{
			plCubicEnvironmap *cubic = TRACKED_NEW plCubicEnvironmap;
			
			plMipmap	*face;

			/// Build and set the faces
			bd->fileName = bd->faceNames[ plStaticEnvLayer::kTopFace ];
			face = ICreateBitmap( bd );
			if( face == nil ) return nil;
			cubic->CopyToFace( face, plCubicEnvironmap::kTopFace );

			bd->fileName = bd->faceNames[ plStaticEnvLayer::kBottomFace ];
			face = ICreateBitmap( bd );
			if( face == nil ) return nil;
			cubic->CopyToFace( face, plCubicEnvironmap::kBottomFace );

			bd->fileName = bd->faceNames[ plStaticEnvLayer::kLeftFace ];
			face = ICreateBitmap( bd );
			if( face == nil ) return nil;
			cubic->CopyToFace( face, plCubicEnvironmap::kLeftFace );

			bd->fileName = bd->faceNames[ plStaticEnvLayer::kRightFace ];
			face = ICreateBitmap( bd );
			if( face == nil ) return nil;
			cubic->CopyToFace( face, plCubicEnvironmap::kRightFace );

			/// NOTE: For whatever reason, MAX decided that the front and back faces should be'
			/// switched, literally. It's as if the cube for the cube map starts at the back face
			/// and then wraps around, instead of starting at the front face. Since we do things
			/// the RIGHT way (or rather, the front way :) on client-side, we need to flip the 
			/// two here. If you convert this to the real MAX UI, make sure the faces are still
			/// flipped!!!!!!!!

			bd->fileName = bd->faceNames[ plStaticEnvLayer::kBackFace ];
			face = ICreateBitmap( bd );
			if( face == nil ) return nil;
			cubic->CopyToFace( face, plCubicEnvironmap::kFrontFace );

			bd->fileName = bd->faceNames[ plStaticEnvLayer::kFrontFace ];
			face = ICreateBitmap( bd );
			if( face == nil ) return nil;
			cubic->CopyToFace( face, plCubicEnvironmap::kBackFace );


			key = hsgResMgr::ResMgr()->NewKey( name, cubic, textureLoc );

			texture = (plBitmap *)cubic;
		}
		else
		{
			plMipmap *mipmap = ICreateBitmap(bd);
			if (!mipmap)
				return nil;

			key = hsgResMgr::ResMgr()->NewKey( name, mipmap, textureLoc );

			texture = (plBitmap *)mipmap;
		}

		// Texture reuse optimization
		WIN32_FILE_ATTRIBUTE_DATA fileAttrib;
		GetFileAttributesEx(bd->fileName, GetFileExInfoStandard, &fileAttrib);
		FILETIME &fileTime = fileAttrib.ftLastWriteTime;
		texture->SetModifiedTime(fileTime.dwLowDateTime, fileTime.dwHighDateTime);

		// Add to our list of created textures and ref, since we have a hold of them
		IAddBitmap( texture );
	}

	return texture;

	hsGuardEnd;
}

//// IAddBitmap ///////////////////////////////////////////////////////////////

void	plBitmapCreator::IAddBitmap( plBitmap *bitmap, hsBool dontRef )
{
	sCommonBitmapLib.AddObject( bitmap );
}

//// CreateBlankMipmap ////////////////////////////////////////////////////////
//	Simple mipmap creator, but importantly, it also adds the mipmap to the list
//	of "converted" maps to clean up at the end of export.

plMipmap	*plBitmapCreator::CreateBlankMipmap( UInt32 width, UInt32 height, unsigned config, UInt8 numLevels, 
												 const char *keyName, const plLocation &keyLocation )
{
	hsGuardBegin( "plBitmapCreator::CreateBlankMipmap" );

	// Get our real location
	const plLocation &textureLoc = plPluginResManager::ResMgr()->GetCommonPage( keyLocation, plAgeDescription::kTextures );

	// Is it already created?
	plKey key = hsgResMgr::ResMgr()->FindKey( plUoid( textureLoc, plMipmap::Index(), keyName ) );
	if( key != nil )
		return plMipmap::ConvertNoRef( key->GetObjectPtr() );

	// Create
	plMipmap	*mip = TRACKED_NEW plMipmap( width, height, config, numLevels );

	// Assign key
	hsgResMgr::ResMgr()->NewKey( keyName, mip, textureLoc );

	// Add to our list
	IAddBitmap( mip );

	return mip;

	hsGuardEnd;
}