/*
**	Command & Conquer Renegade(tm)
**	Copyright 2025 Electronic Arts 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/>.
*/

/////////////////////////////////////////////////////////////////////////////
//
//  Utils.CPP
//
//  Module containing usefull misc. utility functions
//


#include "StdAfx.H"
#include "W3DViewDoc.H"
#include "MainFrm.H"
#include "DataTreeView.H"
#include "Utils.H"
#include "Texture.H"
#include "AssetMgr.H"
#include "Agg_Def.H"
#include "HLod.H"
#include <VFW.H>
#include "RCFile.H"


////////////////////////////////////////////////////////////////////////////
//
//  GetCurrentDocument
//
////////////////////////////////////////////////////////////////////////////
CW3DViewDoc *
GetCurrentDocument (void)
{
    // Assume failure
    CW3DViewDoc *pCDoc = NULL;

    // Get a pointer to the main window
    CMainFrame *pCMainWnd = (CMainFrame *)::AfxGetMainWnd ();
    
    ASSERT (pCMainWnd);
    if (pCMainWnd)
    {
        // Use the main window pointer to get a pointer
        // to the current doc.
        pCDoc = (CW3DViewDoc *)pCMainWnd->GetActiveDocument ();
        ASSERT (pCDoc);
    }    
    
    // Return the doc pointer
    return pCDoc;
}

/////////////////////////////////////////////////////////////
//
//  CenterDialogAroundTreeView
//
void
CenterDialogAroundTreeView (HWND hDlg)
{
    // Params OK?
    if (::IsWindow (hDlg))
    {
        // Get a pointer to the main window
        CMainFrame *pCMainWnd = (CMainFrame *)::AfxGetMainWnd ();
    
        ASSERT (pCMainWnd);
        if (pCMainWnd)
        {
            // Get the tree view pane so we can get its rectangle
            CDataTreeView *pCDataTreeView = (CDataTreeView *)pCMainWnd->GetPane (0, 0);
            
            ASSERT (pCDataTreeView);
            if (pCDataTreeView)
            {
                // Get the bounding rectangle of the data tree view
                RECT rect;
                pCDataTreeView->GetWindowRect (&rect);

                // Get the bounding rectangle of the dialog
                RECT dialogRect;
                ::GetWindowRect (hDlg, &dialogRect);

                // Move the dialog so its centered in the data tree view
                ::SetWindowPos (hDlg,
                                NULL,
                                rect.left + ((rect.right-rect.left) >> 1) - ((dialogRect.right-dialogRect.left) >> 1),
                                rect.top + ((rect.bottom-rect.top) >> 1) - ((dialogRect.bottom-dialogRect.top) >> 1),
                                0,
                                0,
                                SWP_NOSIZE | SWP_NOZORDER);
            }
        }
    }

    return ;
}

/////////////////////////////////////////////////////////////
//
//  Paint_Gradient
//
void
Paint_Gradient
(
	HWND hWnd,
	BYTE baseRed,
	BYTE baseGreen,
	BYTE baseBlue
)
{
    // Get the bounding rectangle so we know how much to paint
    RECT rect;
    ::GetClientRect (hWnd, &rect);

    // Determine the width, height, and width per each shade
    int iWidth = rect.right-rect.left;
    int iHeight = rect.bottom-rect.top;
    float widthPerShade = ((float)iWidth) / 256.00F;

    // Pull a hack to get the CDC for the window
    HDC hDC = ::GetDC (hWnd);
    CDC cDC;
    cDC.Attach(hDC);

    // Loop through each shade and paint its sliver
    float posX = 0.00F;
    for (int iShade = 0; iShade < 256; iShade ++)
    {
        // Paint this sliver
        cDC.FillSolidRect ((int)posX,
                           0,
                           (widthPerShade >= 1.00F) ? ((int)widthPerShade)+1 : 1,
                           iHeight,
                           RGB (iShade*baseRed, iShade*baseGreen, iShade*baseBlue));

        // Increment the current position
        posX += widthPerShade;
    }
    
    // Release the DC
    cDC.Detach ();    
    ::ReleaseDC (hWnd, hDC);

    // Validate the contents of the window so the control won't paint itself
    ::ValidateRect (hWnd, NULL);
    return ;
}


////////////////////////////////////////////////////////////////////////////
//
//  SetDlgItemFloat
//
void
SetDlgItemFloat
(
	HWND hdlg,
	UINT child_id,
	float value
)
{
	// Convert the float to a string
	CString text;
	text.Format ("%.2f", value);

	// Pass the string onto the dialog control
	::SetDlgItemText (hdlg, child_id, text);
	return ;
}


////////////////////////////////////////////////////////////////////////////
//
//  GetDlgItemFloat
//
float
GetDlgItemFloat
(
	HWND hdlg,
	UINT child_id
)
{
	// Get the string from the window
	TCHAR string_value[20];
	::GetDlgItemText (hdlg, child_id, string_value, sizeof (string_value));

	// Convert the string to a float and return the value
	return ::atof (string_value);
}


////////////////////////////////////////////////////////////////////////////
//
//  Initialize_Spinner
//
void
Initialize_Spinner
(
	CSpinButtonCtrl &ctrl,
	float pos,
	float min,
	float max
)
{
	//
	//	Convert the floats to ints and pass the settings onto the controls
	//	
	ctrl.SetRange32 (int(min * 100), int(max * 100));
	ctrl.SetPos (int(pos * 100));

	//
	//	Set the buddy's text accordingly
	//
	CWnd *buddy = ctrl.GetBuddy ();
	if (buddy != NULL) {
		::SetWindowFloat (*buddy, pos);
	}

	return ;
}


////////////////////////////////////////////////////////////////////////////
//
//  Update_Spinner_Buddy
//
void
Update_Spinner_Buddy (CSpinButtonCtrl &ctrl, int delta)
{
	//
	//	Only perform this service if the spinner isn't an auto buddy
	//
	if ((::GetWindowLong (ctrl, GWL_STYLE) & UDS_SETBUDDYINT) == 0) {
		CWnd *buddy = ctrl.GetBuddy ();
		if (buddy != NULL) {

			// Get the current value, increment it, and put it back into the control
			float value = ::GetWindowFloat (*buddy);
			value += (((float)(delta)) / 100.0F);

			//
			//	Validate the new position
			//
			int int_min = 0;
			int int_max = 0;
			ctrl.GetRange32 (int_min, int_max);
			float float_min = ((float)int_min) / 100;
			float float_max = ((float)int_max) / 100;
			value = max (float_min, value);
			value = min (float_max, value);
			
			// Pass the value onto the buddy window
			::SetWindowFloat (*buddy, value);
		}
	}

	return ;
}


////////////////////////////////////////////////////////////////////////////
//
//  Update_Spinner_Buddy
//
void
Update_Spinner_Buddy (HWND hspinner, int delta)
{
	//
	//	Only perform this service if the spinner isn't an auto buddy
	//
	if ((::GetWindowLong (hspinner, GWL_STYLE) & UDS_SETBUDDYINT) == 0) {
		HWND hbuddy_wnd = (HWND)SendMessage (hspinner, UDM_GETBUDDY, 0, 0L);
		if (::IsWindow (hbuddy_wnd)) {

			// Get the current value, increment it, and put it back into the control
			float value = ::GetWindowFloat (hbuddy_wnd);
			value += (((float)(delta)) / 100.0F);

			//
			//	Validate the new position
			//
			int int_min = 0;
			int int_max = 0;
			SendMessage (hspinner, UDM_GETRANGE32, (WPARAM)&int_min, (LPARAM)&int_max);
			float float_min = ((float)int_min) / 100;
			float float_max = ((float)int_max) / 100;
			value = max (float_min, value);			
			value = min (float_max, value);

			// Pass the value onto the buddy window
			::SetWindowFloat (hbuddy_wnd, value);
		}
	}

	return ;
}


////////////////////////////////////////////////////////////////////////////
//
//  Enable_Dialog_Controls
//
void
Enable_Dialog_Controls (HWND dlg,bool onoff)
{
	//
	// Loop over all sub-windows enable/disabling everything except for
	// the static text controls
	//
	for (HWND child = ::GetWindow(dlg,GW_CHILD) ; child != NULL ; child = ::GetWindow(child,GW_HWNDNEXT)) {
		char buf[64];
		::GetClassName(child,buf,sizeof(buf));
		if (stricmp(buf,"STATIC") != 0) {
			::EnableWindow(child,onoff);
		}
	}
	return ;
}


////////////////////////////////////////////////////////////////////////////
//
//  SetWindowFloat
//
void
SetWindowFloat
(
	HWND hwnd,
	float value
)
{
	// Convert the float to a string
	CString text;
	text.Format ("%.3f", value);

	// Pass the string onto the window
	::SetWindowText (hwnd, text);
	return ;
}


////////////////////////////////////////////////////////////////////////////
//
//  GetWindowFloat
//
float
GetWindowFloat (HWND hwnd)
{
	// Get the string from the window
	TCHAR string_value[20];
	::GetWindowText (hwnd, string_value, sizeof (string_value));

	// Convert the string to a float and return the value
	return ::atof (string_value);
}


////////////////////////////////////////////////////////////////////////////
//
//  Asset_Name_From_Filename
//
CString
Asset_Name_From_Filename (LPCTSTR filename)
{
	// Get the filename from this path
	CString asset_name = ::Get_Filename_From_Path (filename);
	
	// Find the index of the extension (if exists)
	int extension = asset_name.ReverseFind ('.');	
	
	// Strip off the extension
	if (extension != -1) {
		asset_name = asset_name.Left (extension);
	}

	// Return the name of the asset
	return asset_name;
}


////////////////////////////////////////////////////////////////////////////
//
//  Filename_From_Asset_Name
//
CString
Filename_From_Asset_Name (LPCTSTR asset_name)
{
	// The filename is simply the asset name plus the .w3d extension
	CString filename = asset_name + CString (".w3d");

	// Return the filename
	return filename;
}


////////////////////////////////////////////////////////////////////////////
//
//  Get_Filename_From_Path
//
CString
Get_Filename_From_Path (LPCTSTR path)
{
	// Find the last occurance of the directory deliminator
	LPCTSTR filename = ::strrchr (path, '\\');
	if (filename != NULL) {
		// Increment past the directory deliminator
		filename ++;
	} else {
		filename = path;
	}

	// Return the filename part of the path
	return CString (filename);
}


////////////////////////////////////////////////////////////////////////////
//
//  Strip_Filename_From_Path
//
CString
Strip_Filename_From_Path (LPCTSTR path)
{
	// Copy the path to a buffer we can modify
	TCHAR temp_path[MAX_PATH];
	::lstrcpy (temp_path, path);

	// Find the last occurance of the directory deliminator
	LPTSTR filename = ::strrchr (temp_path, '\\');
	if (filename != NULL) {
		// Strip off the filename
		filename[0] = 0;
	}

	// Return the path only
	return CString (temp_path);
}


////////////////////////////////////////////////////////////////////////////
//
//  Create_DIB_Section
//
HBITMAP
Create_DIB_Section
(
	UCHAR **pbits,
	int width,
	int height
)
{
	// Set-up the fields of the BITMAPINFOHEADER
	BITMAPINFOHEADER bitmap_info;
	bitmap_info.biSize = sizeof (BITMAPINFOHEADER);
	bitmap_info.biWidth = width;
	bitmap_info.biHeight = -height; // Top-down DIB uses negative height
	bitmap_info.biPlanes = 1;
	bitmap_info.biBitCount = 24;
	bitmap_info.biCompression = BI_RGB;
	bitmap_info.biSizeImage = ((width * height) * 3);
	bitmap_info.biXPelsPerMeter = 0;
	bitmap_info.biYPelsPerMeter = 0;
	bitmap_info.biClrUsed = 0;
	bitmap_info.biClrImportant = 0;

	// Get a temporary screen DC
	HDC hscreen_dc = ::GetDC (NULL);

	// Create a bitmap that we can access the bits directly of
	HBITMAP hbitmap = ::CreateDIBSection (hscreen_dc,
													  (const BITMAPINFO *)&bitmap_info,
													  DIB_RGB_COLORS,
													  (void **)pbits,
													  NULL,
													  0L);

	// Release our temporary screen DC
	::ReleaseDC (NULL, hscreen_dc);
	return hbitmap;
}


////////////////////////////////////////////////////////////////////////////
//
//  Make_Bitmap_From_Texture
//
HBITMAP
Make_Bitmap_From_Texture (TextureClass &texture, int width, int height)
{
	HBITMAP hbitmap = NULL;
#ifdef WW3D_DX8
	srColorSurfaceIFace	*surface = NULL;

	// What type of texture is this?
	switch (texture.getClassID ())
	{
		case srClass::ID_TEXTURE_FILE:
		{
			// Hopefully get the image data
			srTextureIFace::MultiRequest info = { 0 };
			info.levels[0] = new srColorSurface (srColorSurface::ARGB0444, width, height);
			texture.getMipmapData (info);
			surface = info.levels[0];
		}
		break;

		case ID_MANUAL_ANIM_TEXTURE_INSTANCE_CLASS:
		case ID_TIME_ANIM_TEXTURE_INSTANCE_CLASS:
		case ID_RESIZEABLE_TEXTURE_INSTANCE_CLASS:
		{
			VariableTextureClass *psource = ((ResizeableTextureInstanceClass &)texture).Peek_Source();
			if (psource != NULL) {
				
				// Hopefully get the image data
				srTextureIFace::MultiRequest info = { 0 };
				info.levels[0] = new srColorSurface (srColorSurface::ARGB0444, width, height);
				psource->Get_Mipmap_Data (0, info);
				surface = info.levels[0];
			}
		}
		break;

		case ID_INDIRECT_TEXTURE_CLASS:
		{
			srTextureIFace *preal_texture = ((IndirectTextureClass &)texture).Get_Texture ();
			hbitmap = ::Make_Bitmap_From_Texture (*preal_texture, width, height);
			SR_RELEASE (preal_texture);
		}
		break;

		// Unknown texture type
		default:
			ASSERT (0);
			break;
	}

	
	if (surface != NULL) {
	
		int src_width = surface->getWidth ();
		int src_height = surface->getHeight ();

		// Create a DIB section for fast 'blitting'
		UCHAR *pbits = NULL;
		hbitmap = ::Create_DIB_Section (&pbits, width, height);
		
		ASSERT (hbitmap != NULL);
		ASSERT (pbits != NULL);
		if (pbits != NULL) {
			
			float src_bits_per_pixel = (float)src_width / (float)width;
			float src_bits_per_scanline = (float)src_height / (float)height;
			float curr_src_pixel = 0;
			float curr_src_row = 0;
			
			// Window's bitmaps are DWORD aligned, so make sure
			// we take that into account.
			int alignment_offset = (width * 3) % 4;
			alignment_offset = (alignment_offset != 0) ? (4 - alignment_offset) : 0;

			// Copy the bits into the windows DIB section
			int index = 0;
			for (int y = 0; y < height; y ++) {
				for (int x = 0; x < width; x ++) {
					
					// Grab the pixel from the source buffer and stuff it into the dest
					srARGB pixel = surface->getPixel (curr_src_pixel, curr_src_row);
					pbits[index++] = pixel[srARGB::B];
					pbits[index++] = pixel[srARGB::G];
					pbits[index++] = pixel[srARGB::R];
					
					// Increment our source counter (the src size and dest don't have to match)
					curr_src_pixel += src_bits_per_pixel;
				}

				// Reset our src-to-dest conversion data
				curr_src_pixel = 0;
				curr_src_row += src_bits_per_scanline;

				// Skip past the padded bytes
				index += alignment_offset;
			}
		}

		surface->release ();
	}
#endif
	// Return a handle to the bitmap
	return hbitmap;
}


////////////////////////////////////////////////////////////////////////////
//
//  Get_Texture_Name
//
CString
Get_Texture_Name (TextureClass &texture)
{
	CString name;

	// What type of texture is this?
#ifdef WW3D_DX8
	switch (texture.getClassID ())
	{
		case srClass::ID_TEXTURE_FILE:
			name = texture.getName ();
			break;

		case ID_MANUAL_ANIM_TEXTURE_INSTANCE_CLASS:
		case ID_TIME_ANIM_TEXTURE_INSTANCE_CLASS:
		case ID_RESIZEABLE_TEXTURE_INSTANCE_CLASS:
		{
			VariableTextureClass *psource = ((ResizeableTextureInstanceClass &)texture).Peek_Source();
			if (psource != NULL) {
				name = psource->getName ();
			}
		}
		break;

		case ID_INDIRECT_TEXTURE_CLASS:
		{
			srTextureIFace *preal_texture = ((IndirectTextureClass &)texture).Get_Texture ();
			if (preal_texture != NULL) {
				name = ::Get_Texture_Name (*preal_texture);
				SR_RELEASE (preal_texture);
			}
		}
		break;

		// Unknown texture type
		default:
			ASSERT (0);
			break;
	}
#else 
	name = texture.Get_Texture_Name();
#endif

	// Return the texture's name
	return name;
}


////////////////////////////////////////////////////////////////////////////
//
//  Build_Emitter_List
//
void
Build_Emitter_List
(
	RenderObjClass &render_obj,
	DynamicVectorClass<CString> &list
)
{
	// Loop through all this render obj's sub-obj's
	for (int index = 0; index < render_obj.Get_Num_Sub_Objects (); index ++) {
		RenderObjClass *psub_obj = render_obj.Get_Sub_Object (index);
		if (psub_obj != NULL) {

			// Is this sub-obj an emitter?
			if (psub_obj->Class_ID () == RenderObjClass::CLASSID_PARTICLEEMITTER) {
				
				// Is this emitter already in the list?
				bool found = false;
				for (int list_index = 0; (list_index < list.Count ()) && !found; list_index++) {
					if (::lstrcmpi (list[list_index], psub_obj->Get_Name ()) == 0) {
						found = true;
					}
				}

				// Add this emitter to the list if necessary
				if (!found) {
					list.Add (psub_obj->Get_Name ());
				}
			}

			// Recursivly add emitters to the list
			Build_Emitter_List (*psub_obj, list);
			MEMBER_RELEASE (psub_obj);
		}		
	}

	return ;
}


////////////////////////////////////////////////////////////////////////////
//
//  Is_Aggregate
//
bool
Is_Aggregate (const char *asset_name)
{
	// Assume that the asset isn't an aggregate
	bool retval = false;

	// Check to see if this object is an aggregate
	RenderObjClass *prender_obj = WW3DAssetManager::Get_Instance()->Create_Render_Obj (asset_name);
	if ((prender_obj != NULL) &&
		 (prender_obj->Get_Base_Model_Name () != NULL))
	{
		retval = true;
	}

	// Free our hold on the temporary render object
	MEMBER_RELEASE (prender_obj);
	
	// Return the true/false result code
	return retval;
}


////////////////////////////////////////////////////////////////////////////
//
//  Rename_Aggregate_Prototype
//
void
Rename_Aggregate_Prototype
(
	const char *old_name,
	const char *new_name
)
{
	// Params valid?
	if ((old_name != NULL) &&
		 (new_name != NULL) &&
		 (::lstrcmpi (old_name, new_name) != 0)) {

		// Get the prototype from the asset manager
		AggregatePrototypeClass *proto = NULL;
		proto = (AggregatePrototypeClass *)WW3DAssetManager::Get_Instance ()->Find_Prototype (old_name);
		if (proto != NULL) {

			// Copy the definition from the prototype and remove the prototype
			AggregateDefClass *pdefinition = proto->Get_Definition ();
			AggregateDefClass *pnew_definition = pdefinition->Clone ();
			WW3DAssetManager::Get_Instance ()->Remove_Prototype (old_name);

			// Rename the definition, create a new prototype, and add it to the asset manager
			pnew_definition->Set_Name (new_name);
			proto = new AggregatePrototypeClass (pnew_definition);
			WW3DAssetManager::Get_Instance ()->Add_Prototype (proto);
		}		
	}

	return ;
}


////////////////////////////////////////////////////////////////////////////
//
//  Is_Real_LOD
//
bool
Is_Real_LOD (const char *asset_name)
{
	// Assume that the asset isn't a true LOD (HLOD w/ more than one 
	bool retval = false;

	// Check to see if this object is an aggregate
	RenderObjClass *prender_obj = WW3DAssetManager::Get_Instance()->Create_Render_Obj (asset_name);
	if ((prender_obj != NULL) &&
		 (prender_obj->Class_ID () == RenderObjClass::CLASSID_HLOD) &&
		 (((HLodClass *)prender_obj)->Get_LOD_Count () > 1)) {
		retval = true;
	}

	// Free our hold on the temporary render object
	MEMBER_RELEASE (prender_obj);
	
	// Return the true/false result code
	return retval;
}


////////////////////////////////////////////////////////////////////////////
//
//  Get_File_Time
//
bool
Get_File_Time
(
	LPCTSTR path,
	LPFILETIME pcreation_time,
	LPFILETIME paccess_time,
	LPFILETIME pwrite_time
)
{
	// Assume failure
	bool retval = false;

	// Attempt to open the file
	HANDLE hfile = ::CreateFile (path,
										  0,
										  0,
										  NULL,
										  OPEN_EXISTING,
										  0L,
										  NULL);
	
	ASSERT (hfile != INVALID_HANDLE_VALUE);
	if (hfile != INVALID_HANDLE_VALUE) {

		// Get the mod times for this file
		retval = (::GetFileTime (hfile, pcreation_time, paccess_time, pwrite_time) == TRUE);

		// Close the file
		SAFE_CLOSE (hfile);
	}		
	
	// Return the true/false result code
	return retval;
}


////////////////////////////////////////////////////////////////////////////
//
//  Are_Glide_Drivers_Acceptable
//
bool
Are_Glide_Drivers_Acceptable (void)
{
	// Assume success
	bool retval = true;

	// Is this windows NT?
	OSVERSIONINFO version = { sizeof (OSVERSIONINFO), 0 };
	if (::GetVersionEx (&version) && (version.dwPlatformId == VER_PLATFORM_WIN32_NT)) {
		
		// Now assume failure
		retval = false;

		// Get a path to the system directory
		TCHAR path[MAX_PATH];
		::GetSystemDirectory (path, sizeof (path));
		::Delimit_Path (path);

		// Build the full path of the 2 main drivers
		CString glide2x = CString (path) + "glide2x.dll";
		CString glide3x = CString (path) + "glide3x.dll";

		// Get the creation time of the glide2x driver
		FILETIME file_time = { 0 };
		if (::Get_File_Time (glide2x, NULL, NULL, &file_time)) {
			CTime time_obj (file_time);
			retval = ((time_obj.GetYear () == 1998) && (time_obj.GetMonth () == 12)) || (time_obj.GetYear () > 1998);
		}

		// Get the creation time of the glide3x driver
		if (::Get_File_Time (glide3x, NULL, NULL, &file_time)) {
			CTime time_obj (file_time);
			retval = ((time_obj.GetYear () == 1998) && (time_obj.GetMonth () == 12)) || (time_obj.GetYear () > 1998);
		}
	}
	
	// Return the true/false result code
	return retval;
}


////////////////////////////////////////////////////////////////////////////
//
//  Load_RC_Texture
//
TextureClass *
Load_RC_Texture (LPCTSTR resource_name)
{
	TextureClass *texture = NULL;

	//
	//	Load the cursor file image from this binaries resources
	//
	ResourceFileClass resource_file (::AfxGetResourceHandle (), resource_name);
	unsigned char *res_data = resource_file.Peek_Data ();
	unsigned int data_size = resource_file.Size ();

	//
	//	Create a texture from the raw image data
	//
#ifdef WW3D_DX8
	srBinIMStream stream (res_data, data_size);
	srSurfaceIOManager::SurfaceImporter *importer = srCore.getSurfaceIOManager()->getImporter (".tga");
	if (importer != NULL) {
		srColorSurfaceIFace *surface = importer->importSurface (stream, srSurfaceIOManager::ImportInfo());
		if (surface != NULL) {
			texture = new srTextureMap (surface);
		}
	}	
#endif

	// Reutrn a pointer to the new texture
	return texture;
}


////////////////////////////////////////////////////////////////////////////
//
//  Resolve_Path
//
////////////////////////////////////////////////////////////////////////////
void
Resolve_Path (CString &filename)
{
	if (filename.Find ('\\') == -1) {
		char path[MAX_PATH];
		::GetCurrentDirectory (MAX_PATH, path);
		::Delimit_Path (path);
		filename = CString (path) + filename;
	}

	return ;
}


////////////////////////////////////////////////////////////////////////////
//
//  Find_Missing_Textures
//
////////////////////////////////////////////////////////////////////////////
void
Find_Missing_Textures
(
	DynamicVectorClass<CString> &	list,
	LPCTSTR								name,
	int									frame_count
)
{
	//
	//	If this file doesn't exist, then add it to our list
	//
	if (::GetFileAttributes (name) == 0xFFFFFFFF) {
		CString full_path = name;
		Resolve_Path (full_path);
		list.Add (full_path);
	}

	return ;
}


////////////////////////////////////////////////////////////////////////////
//
//  Copy_File
//
////////////////////////////////////////////////////////////////////////////
bool
Copy_File
(
	LPCTSTR	existing_filename,
	LPCTSTR	new_filename,
	bool		force_copy
)
{
	SANITY_CHECK ((existing_filename != NULL && new_filename != NULL)) {
		return false;
	}

	// Assume failure
	bool retval = false;

	// Make sure we aren't copying over ourselves
	bool allow_copy = (::lstrcmpi (existing_filename, new_filename) != 0);
	
	// Strip the readonly bit off if necessary
	DWORD attributes = ::GetFileAttributes (new_filename);
	if (allow_copy &&
		 (attributes != 0xFFFFFFFF) &&
		 ((attributes & FILE_ATTRIBUTE_READONLY) == FILE_ATTRIBUTE_READONLY))
	{
		if (force_copy) {
			::SetFileAttributes (new_filename, attributes & (~FILE_ATTRIBUTE_READONLY));
		} else {
			allow_copy = false;
		}
	}

	// Perform the copy operation!
	if (allow_copy) {
		retval = (::CopyFile (existing_filename, new_filename, FALSE) == TRUE);
	}

	// Return the true/false result code
	return retval;
}


////////////////////////////////////////////////////////////////////////////
//
//  Get_Graphic_View
//
////////////////////////////////////////////////////////////////////////////
CGraphicView *
Get_Graphic_View (void)
{
	CGraphicView *view = NULL;

	//
	//	Get the view from the current document
	//
	CW3DViewDoc *doc = GetCurrentDocument ();
	if (doc != NULL) {
		view = doc->GetGraphicView ();
	}

	return view;
}
