1293 lines
49 KiB
C++
1293 lines
49 KiB
C++
/*
|
|
** Command & Conquer Generals Zero Hour(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/>.
|
|
*/
|
|
|
|
/* $Header: /Commando/Code/Tools/max2w3d/w3dexp.cpp 78 1/03/01 11:06a Greg_h $ */
|
|
/***********************************************************************************************
|
|
*** Confidential - Westwood Studios ***
|
|
***********************************************************************************************
|
|
* *
|
|
* Project Name : Commando Tools - W3D export *
|
|
* *
|
|
* $Archive:: /Commando/Code/Tools/max2w3d/w3dexp.cpp $*
|
|
* *
|
|
* $Author:: Greg_h $*
|
|
* *
|
|
* $Modtime:: 1/03/01 11:03a $*
|
|
* *
|
|
* $Revision:: 78 $*
|
|
* *
|
|
*---------------------------------------------------------------------------------------------*
|
|
* Functions: *
|
|
* W3dExportClass::W3dExportClass -- constructor *
|
|
* W3dExportClass::~W3dExportClass -- destructor *
|
|
* W3dExportClass::Export_Hierarchy -- Export the hierarchy tree *
|
|
* W3dExportClass::Export_Animation -- Export animation data *
|
|
* W3dExportClass::Export_Damage_Animations -- Exports damage animations for the model *
|
|
* W3dExportClass::Export_Geometry -- Export the geometry data *
|
|
* W3dExportClass::get_hierarchy_tree -- get a pointer to the hierarchy tree *
|
|
* W3dExportClass::get_export_options -- get the export options *
|
|
* W3dExportClass::Start_Progress_Bar -- start the MAX progress meter *
|
|
* W3dExportClass::End_Progress_Bar -- end the progress meter *
|
|
* W3dExportClass::get_damage_root_list -- gets the list of damage root nodes *
|
|
* W3dExportClass::Export_HLod -- Export an HLOD description *
|
|
* W3dExportClass::Export_Collection -- exports a collection chunk *
|
|
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
|
|
|
|
|
#include "rawfile.h"
|
|
#include "chunkio.h"
|
|
#include "w3dexp.h"
|
|
#include "w3dutil.h"
|
|
#include "nodelist.h"
|
|
#include "meshsave.h"
|
|
#include "hiersave.h"
|
|
#include "hlodsave.h"
|
|
#include "meshcon.h"
|
|
#include "SnapPoints.h"
|
|
#include "w3ddlg.h"
|
|
#include "progress.h"
|
|
#include "errclass.h"
|
|
#include "motion.h"
|
|
#include "util.h"
|
|
#include "w3ddesc.h"
|
|
#include "colboxsave.h"
|
|
#include "nullsave.h"
|
|
#include "dazzlesave.h"
|
|
#include "maxworldinfo.h"
|
|
#include "exportlog.h"
|
|
|
|
#include "geometryexporttask.h"
|
|
#include "geometryexportcontext.h"
|
|
|
|
#include <direct.h>
|
|
#include "targa.h"
|
|
|
|
// Used to communicate from the exporter to the dialog.
|
|
char W3dExportClass::CurrentExportPath[_MAX_DRIVE + _MAX_DIR + 1] = { '\000' };
|
|
|
|
|
|
/* local functions */
|
|
static DWORD WINAPI progress_callback( LPVOID arg);
|
|
static HierarchySaveClass * load_hierarchy_file(char * filename);
|
|
static bool dupe_check(const INodeListClass & list);
|
|
static bool check_lod_extensions (INodeListClass &list, INode *origin);
|
|
|
|
/*
|
|
** Struct for export info (AppDataChunk hung off the scene pointer)
|
|
** This includes the export info struct and some padding.
|
|
** NOTE: to avoid file versioning issues, new data should be added after
|
|
** existing data in this struct, the padding array should be made smaller so
|
|
** the total size remains the same, and the new data should give reasonable
|
|
** results with a default content of zeros (which is what it will contain if
|
|
** an older file is loaded).
|
|
*/
|
|
struct ExportInfoAppDataChunkStruct {
|
|
W3dExportOptionsStruct ExportOptions;
|
|
unsigned char Padding[89];
|
|
};
|
|
|
|
|
|
|
|
/************************************************************************************************
|
|
**
|
|
** GeometryFilterClass - filters out nodes which are not marked for W3D geometry export
|
|
**
|
|
************************************************************************************************/
|
|
class GeometryFilterClass : public INodeFilterClass
|
|
{
|
|
public:
|
|
virtual BOOL Accept_Node(INode * node, TimeValue time)
|
|
{
|
|
Object * obj = node->EvalWorldState(time).obj;
|
|
|
|
if
|
|
(
|
|
obj
|
|
// && !Is_Proxy (*node)
|
|
&& !Is_Origin(node)
|
|
&& !node->IsHidden()
|
|
&& Is_Geometry(node)
|
|
)
|
|
{
|
|
return TRUE;
|
|
} else {
|
|
return FALSE;
|
|
}
|
|
}
|
|
};
|
|
|
|
|
|
|
|
|
|
/************************************************************************************************
|
|
**
|
|
** OriginFilterClass - Filters out nodes which are not "origin" objects. Origins are MAX dummy
|
|
** objects whose parents are the scene root, and are named "origin.*" (ie. first 7 characters
|
|
** in the name are "origin.". These origin objects will be (0,0,0) for all of its descendants.
|
|
** This allows an artist to create multiple models within one MAX scene but still have each
|
|
** mesh's coordinates equal without needing to stack all the models on the world origin.
|
|
** We iterate through the origin objects when exporting a scene containing multiple LODs of
|
|
** one model.
|
|
**
|
|
************************************************************************************************/
|
|
class OriginFilterClass : public INodeFilterClass
|
|
{
|
|
public:
|
|
|
|
virtual BOOL Accept_Node(INode * node, TimeValue time) { return Is_Origin(node); }
|
|
};
|
|
|
|
|
|
/************************************************************************************************
|
|
**
|
|
** DamageRootFilterClass - Filters out all nodes which are not "damage root" objects. These nodes
|
|
** are MAX dummy objects whose parents are the scene root, and are named "damage.*" (ie. first 7
|
|
** characters in the name are "damage.". These damage roots mean that all of its children
|
|
** represent a damaged model.
|
|
**
|
|
************************************************************************************************/
|
|
class DamageRootFilterClass : public INodeFilterClass
|
|
{
|
|
public:
|
|
|
|
virtual BOOL Accept_Node(INode * node, TimeValue time) { return Is_Damage_Root(node); }
|
|
};
|
|
|
|
|
|
/************************************************************************************************
|
|
**
|
|
** DamageRegionFilterClass - Filters out all node that are not a bone which is part of a certain
|
|
** deformation region. Pass the region ID to the constructor.
|
|
**
|
|
************************************************************************************************/
|
|
class DamageRegionFilterClass : public INodeFilterClass
|
|
{
|
|
public:
|
|
DamageRegionFilterClass(int region_id) { RegionId = region_id; }
|
|
|
|
virtual BOOL Accept_Node(INode * node, TimeValue time)
|
|
{
|
|
if (!Is_Bone(node)) return FALSE;
|
|
|
|
// Check it's damage region ID (if it has one).
|
|
AppDataChunk * appdata = node->GetAppDataChunk(W3DUtilityClassID,UTILITY_CLASS_ID,1);
|
|
if (!appdata) return FALSE;
|
|
W3DAppData1Struct *wdata = (W3DAppData1Struct*)(appdata->data);
|
|
return wdata->DamageRegion == RegionId;
|
|
}
|
|
|
|
protected:
|
|
|
|
int RegionId;
|
|
};
|
|
|
|
|
|
/***********************************************************************************************
|
|
* W3dExportClass::DoExport -- This method is called for the plug-in to perform it's file expo *
|
|
* *
|
|
* INPUT: *
|
|
* name - filename to use *
|
|
* export - A pointer the plug-in may use to call methods to enumerate the scene *
|
|
* max - An interface pointer the plug-in may use to call methods of MAX. *
|
|
* *
|
|
* OUTPUT: *
|
|
* Nonzero on successful export; otherwise 0. *
|
|
* *
|
|
* WARNINGS: *
|
|
* *
|
|
* HISTORY: *
|
|
* 06/09/1997 GH : Created. *
|
|
* 10/17/2000 gth : Removed the old export code-path, everything goes through an origin now *
|
|
*=============================================================================================*/
|
|
int W3dExportClass::DoExport
|
|
(
|
|
const TCHAR *filename,
|
|
ExpInterface *export,
|
|
Interface *max,
|
|
BOOL suppressPrompts,
|
|
DWORD options
|
|
)
|
|
{
|
|
ExportInterface = export;
|
|
MaxInterface = max;
|
|
RootNode = NULL;
|
|
OriginList = NULL;
|
|
DamageRootList = NULL;
|
|
HierarchyTree = NULL;
|
|
|
|
try {
|
|
|
|
CurTime = MaxInterface->GetTime();
|
|
FrameRate = GetFrameRate();
|
|
FixupType = HierarchySaveClass::MATRIX_FIXUP_TRANS_ROT;
|
|
|
|
/*
|
|
** The Animation and the Hierarchy will be named with the root portion of the W3D filename
|
|
** and the path is used by the options dialog
|
|
*/
|
|
char rootname[_MAX_FNAME + 1];
|
|
char drivename[_MAX_DRIVE + 1];
|
|
char dirname[_MAX_DIR + 1];
|
|
_splitpath(filename, drivename, dirname, rootname, NULL);
|
|
sprintf(CurrentExportPath, "%s%s", drivename, dirname);
|
|
|
|
/*
|
|
** The batch export process (suppressPrompt == TRUE) needs to know the directory of the
|
|
** MAX file being exported. This is so that it can use the old relative pathname of the
|
|
** W3D file containing the hierarchy.
|
|
*/
|
|
_splitpath(max->GetCurFilePath(), drivename, dirname, NULL, NULL);
|
|
sprintf(CurrentScenePath, "%s%s", drivename, dirname);
|
|
|
|
/*
|
|
** Get export options
|
|
*/
|
|
if (!get_export_options(suppressPrompts)) {
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
** If no data is going to be exported just bail
|
|
*/
|
|
if ((!ExportOptions.ExportHierarchy) && (!ExportOptions.ExportAnimation) && (!ExportOptions.ExportGeometry)) {
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
** Initialize the logging system
|
|
*/
|
|
ExportLog::Init(NULL);
|
|
|
|
/*
|
|
** Create a chunk saver to write the w3d file with
|
|
*/
|
|
RawFileClass stream(filename);
|
|
|
|
if (!stream.Open(FileClass::WRITE)) {
|
|
MessageBox(NULL,"Unable to open file.","Error",MB_OK | MB_SETFOREGROUND);
|
|
return 1;
|
|
}
|
|
|
|
ChunkSaveClass csave(&stream);
|
|
|
|
/*
|
|
** Export data from the scene.
|
|
**
|
|
** Are we doing an old export (one model/LOD per scene) or a new export (multiple LODs
|
|
** for one model in a scene)?
|
|
*/
|
|
if (get_origin_list())
|
|
{
|
|
DoOriginBasedExport(rootname, csave);
|
|
}
|
|
|
|
/*
|
|
** Done!
|
|
*/
|
|
stream.Close();
|
|
|
|
if (HierarchyTree != NULL) {
|
|
delete HierarchyTree;
|
|
HierarchyTree = NULL;
|
|
}
|
|
|
|
if (OriginList != NULL) {
|
|
delete OriginList;
|
|
OriginList = NULL;
|
|
}
|
|
|
|
if (DamageRootList != NULL) {
|
|
delete DamageRootList;
|
|
DamageRootList = NULL;
|
|
}
|
|
|
|
} catch (ErrorClass error) {
|
|
|
|
MessageBox(NULL,error.error_message,"Error",MB_OK | MB_SETFOREGROUND);
|
|
}
|
|
|
|
ExportLog::Shutdown(ExportOptions.ReviewLog);
|
|
MaxInterface->RedrawViews(MaxInterface->GetTime());
|
|
return 1;
|
|
}
|
|
|
|
|
|
|
|
/***********************************************************************************************
|
|
* W3dExportClass::DoOriginBasedExport -- New export codepath. Exports any objects linked to *
|
|
* an origin object. Assumes origins named "origin.01" and greater represent LODs of the *
|
|
* original object ("origin.00"). Also assumes "damage.01" and greater represent damaged *
|
|
* versions of the original object. *
|
|
* *
|
|
* INPUT: *
|
|
* *
|
|
* OUTPUT: *
|
|
* *
|
|
* WARNINGS: *
|
|
* *
|
|
* HISTORY: *
|
|
* 9/13/1999 AJA : Created. *
|
|
* 9/21/1999 AJA : Added support for the new animation exporting process (damage-related). *
|
|
*=============================================================================================*/
|
|
void W3dExportClass::DoOriginBasedExport(char *rootname,ChunkSaveClass &csave)
|
|
{
|
|
/*
|
|
** Build the damage root list.
|
|
*/
|
|
INodeListClass *damage_list = get_damage_root_list();
|
|
assert(damage_list != NULL);
|
|
|
|
/*
|
|
** Start the progress meter
|
|
*/
|
|
Start_Progress_Bar();
|
|
Progress_Meter_Class meter(MaxInterface,0.0f,100.0f);
|
|
|
|
int steps = 0;
|
|
steps++; // Base Pose
|
|
steps+= OriginList->Num_Nodes(); // n Origins
|
|
steps++; // Basic Anim OR Damage Anims
|
|
steps++; // HLOD OR Collection
|
|
|
|
meter.Finish_In_Steps(steps);
|
|
|
|
/*
|
|
** Find the base object's origin.
|
|
*/
|
|
bool is_base_object = false;
|
|
INodeListClass *origin_list = get_origin_list();
|
|
unsigned int i, count = origin_list->Num_Nodes();
|
|
INode *base_origin = NULL;
|
|
|
|
for (i = 0; i < count; i++)
|
|
{
|
|
INode *node = (*origin_list)[i];
|
|
if (Is_Base_Origin(node))
|
|
{
|
|
base_origin = node;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*
|
|
** Write the Hierarchy Tree (if needed)
|
|
*/
|
|
Progress_Meter_Class treemeter(meter, meter.Increment);
|
|
if (!Export_Hierarchy(rootname, csave, treemeter, base_origin))
|
|
{
|
|
MessageBox(NULL,"Hierarchy Export Failure!","Error",MB_OK | MB_SETFOREGROUND);
|
|
End_Progress_Bar();
|
|
return;
|
|
}
|
|
meter.Add_Increment();
|
|
|
|
if (damage_list->Num_Nodes() <= 0)
|
|
{
|
|
/*
|
|
** Write the Base Animation (if needed)
|
|
*/
|
|
Progress_Meter_Class animmeter(meter, meter.Increment);
|
|
if (!Export_Animation(rootname, csave, animmeter, base_origin))
|
|
{
|
|
MessageBox(NULL,"Animation Export Failure!","Error",MB_OK | MB_SETFOREGROUND);
|
|
End_Progress_Bar();
|
|
return;
|
|
}
|
|
meter.Add_Increment();
|
|
}
|
|
else
|
|
{
|
|
/*
|
|
** Write the damage animations.
|
|
*/
|
|
Progress_Meter_Class damagemeter(meter, meter.Increment);
|
|
for (i = 0; i < damage_list->Num_Nodes(); i++)
|
|
{
|
|
if (!Export_Damage_Animations(rootname, csave, damagemeter, (*damage_list)[i]))
|
|
{
|
|
MessageBox(NULL, "Damage Animation Export Failure!", "Error", MB_OK | MB_SETFOREGROUND);
|
|
End_Progress_Bar();
|
|
return;
|
|
}
|
|
}
|
|
meter.Add_Increment();
|
|
}
|
|
|
|
/*
|
|
** Create an array of pointers to MeshConnectionsClass objects. These objects
|
|
** will be created below, and will be used to generate the HLOD with the
|
|
** geometry of all models in the scene.
|
|
*/
|
|
MeshConnectionsClass **connections = new MeshConnectionsClass*[count];
|
|
if (!connections)
|
|
{
|
|
MessageBox(NULL, "Memory allocation failure!", "Error", MB_OK | MB_SETFOREGROUND);
|
|
End_Progress_Bar();
|
|
return;
|
|
}
|
|
memset(connections, 0, sizeof(MeshConnectionsClass*) * count);
|
|
|
|
/*
|
|
** For each model in the scene, write its animation and geometry (if needed).
|
|
** All models share the above hierarchy tree.
|
|
*/
|
|
int idx = strlen(rootname);
|
|
rootname[idx+1] = '\0';
|
|
|
|
/*
|
|
** If we're not exporting a hierarchical model, only export the "origin.00"
|
|
*/
|
|
if (!ExportOptions.LoadHierarchy && !ExportOptions.ExportHierarchy) {
|
|
count = 1;
|
|
}
|
|
|
|
for (i = 0; i < count; i++)
|
|
{
|
|
/*
|
|
** Get the current origin.
|
|
*/
|
|
INode *origin = (*origin_list)[i];
|
|
|
|
/*
|
|
** Write each mesh (if needed)
|
|
*/
|
|
MeshConnectionsClass *meshcon = NULL;
|
|
Progress_Meter_Class meshmeter(meter, meter.Increment);
|
|
if (!Export_Geometry(rootname, csave, meshmeter, origin, &meshcon))
|
|
{
|
|
MessageBox(NULL, "Geometry Export Failure!", "Error", MB_OK | MB_SETFOREGROUND);
|
|
End_Progress_Bar();
|
|
return;
|
|
}
|
|
meter.Add_Increment();
|
|
|
|
/*
|
|
** Put the MeshConnectionsClass object for this model into
|
|
** the array in order of LOD (top-level last).
|
|
*/
|
|
int lod_level = Get_Lod_Level(origin);
|
|
if (lod_level >= count || connections[count - lod_level - 1] != NULL)
|
|
{
|
|
char text[256];
|
|
sprintf(text, "Origin Naming Error! There are %d models defined in this "
|
|
"scene, therefore your origin names should be\n\"Origin.00\" through "
|
|
"\"Origin.%02d\", 00 being the high-poly model and %02d being the "
|
|
"lowest detail LOD.", count, count-1, count-1);
|
|
MessageBox(NULL, text, "Error", MB_OK | MB_SETFOREGROUND);
|
|
End_Progress_Bar();
|
|
return;
|
|
}
|
|
connections[count - lod_level - 1] = meshcon;
|
|
}
|
|
|
|
/*
|
|
** Generate the HLOD based on all the mesh connections.
|
|
*/
|
|
if (ExportOptions.LoadHierarchy || ExportOptions.ExportHierarchy) {
|
|
rootname[idx] = '\0'; // remove the trailing character (signifies which lod level)
|
|
HierarchySaveClass *htree = get_hierarchy_tree();
|
|
if (htree)
|
|
{
|
|
Progress_Meter_Class hlod_meter(meter, meter.Increment);
|
|
if (!Export_HLod(rootname, htree->Get_Name(), csave, hlod_meter, connections, count))
|
|
{
|
|
MessageBox(NULL, "HLOD Generation Failure!", "Error", MB_OK | MB_SETFOREGROUND);
|
|
End_Progress_Bar();
|
|
return;
|
|
}
|
|
meter.Add_Increment();
|
|
}
|
|
}
|
|
|
|
/*
|
|
** Deallocate the array of mesh connections.
|
|
*/
|
|
for (i = 0; i < count; i++)
|
|
{
|
|
if (connections[i] != NULL)
|
|
delete connections[i];
|
|
}
|
|
delete []connections;
|
|
|
|
End_Progress_Bar();
|
|
}
|
|
|
|
|
|
/***********************************************************************************************
|
|
* W3dExportClass::Export_Hierarchy -- Export the hierarchy tree *
|
|
* *
|
|
* INPUT: *
|
|
* *
|
|
* OUTPUT: *
|
|
* *
|
|
* WARNINGS: *
|
|
* *
|
|
* HISTORY: *
|
|
* 10/16/1997 GH : Created. *
|
|
* 13/9/1999 AJA : Split into two calls, one that takes a node list and one that takes a *
|
|
* single root node. *
|
|
* 10/17/2000 gth : Removed the old code-path, we always use an origin now *
|
|
*=============================================================================================*/
|
|
bool W3dExportClass::Export_Hierarchy(char *name,ChunkSaveClass & csave,Progress_Meter_Class & meter,
|
|
INode *root)
|
|
{
|
|
if (!ExportOptions.ExportHierarchy) return true;
|
|
HierarchySaveClass::Enable_Terrain_Optimization(ExportOptions.EnableTerrainMode);
|
|
|
|
if (root == NULL) return false;
|
|
|
|
try {
|
|
HierarchyTree = new HierarchySaveClass(root,CurTime,meter,name,FixupType);
|
|
} catch (ErrorClass err) {
|
|
MessageBox(NULL, err.error_message,"Error!",MB_OK | MB_SETFOREGROUND);
|
|
return false;
|
|
}
|
|
|
|
HierarchyTree->Save(csave);
|
|
|
|
return true;
|
|
}
|
|
|
|
/***********************************************************************************************
|
|
* W3dExportClass::Export_Animation -- Export animation data *
|
|
* *
|
|
* INPUT: *
|
|
* *
|
|
* OUTPUT: *
|
|
* *
|
|
* WARNINGS: *
|
|
* *
|
|
* HISTORY: *
|
|
* 10/16/1997 GH : Created. *
|
|
* 13/9/1999 AJA : Split into two calls, one that takes a node list and one that takes a *
|
|
* single root node. *
|
|
* 10/17/2000 gth : Removed the old code-path, we always use an origin now *
|
|
*=============================================================================================*/
|
|
bool W3dExportClass::Export_Animation(char * name,ChunkSaveClass & csave,Progress_Meter_Class & meter,
|
|
INode *root)
|
|
{
|
|
if (!ExportOptions.ExportAnimation) return true;
|
|
HierarchySaveClass * htree = get_hierarchy_tree();
|
|
|
|
if ((root == NULL) || (htree == NULL)) {
|
|
return false;
|
|
}
|
|
|
|
MotionClass * motion = NULL;
|
|
|
|
try {
|
|
motion = new MotionClass( ExportInterface->theScene,
|
|
root,
|
|
htree,
|
|
ExportOptions,
|
|
FrameRate,
|
|
&meter,
|
|
MaxInterface->GetMAXHWnd(),
|
|
name);
|
|
} catch (ErrorClass err) {
|
|
MessageBox(NULL,err.error_message,"Error!",MB_OK | MB_SETFOREGROUND);
|
|
return false;
|
|
}
|
|
|
|
motion->Save(csave);
|
|
|
|
delete motion;
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
/***********************************************************************************************
|
|
* W3dExportClass::Export_Damage_Animations -- Exports damage animations for the model *
|
|
* *
|
|
* INPUT: *
|
|
* *
|
|
* OUTPUT: *
|
|
* *
|
|
* WARNINGS: *
|
|
* *
|
|
* HISTORY: *
|
|
* 1999 AJA : Created. *
|
|
*=============================================================================================*/
|
|
bool W3dExportClass::Export_Damage_Animations(char *name, ChunkSaveClass &csave,
|
|
Progress_Meter_Class &meter,
|
|
INode *damage_root)
|
|
{
|
|
if (!ExportOptions.ExportAnimation) return true;
|
|
HierarchySaveClass *htree = get_hierarchy_tree();
|
|
|
|
if ((damage_root == NULL) || (htree == NULL))
|
|
return false;
|
|
|
|
int damage_state = Get_Damage_State(damage_root);
|
|
|
|
/*
|
|
** While exporting damage animations, we need the offset from our origin to the real
|
|
** scene origin.
|
|
*/
|
|
Matrix3 originoffset = Inverse(damage_root->GetNodeTM(CurTime));
|
|
|
|
/*
|
|
** For every damage region we find, export an animation.
|
|
*/
|
|
bool done = false;
|
|
int current_region = 0;
|
|
int num_damage_bones = 0; // number of bones assigned to a damage region
|
|
for (current_region = 0; current_region < MAX_DAMAGE_REGIONS; current_region++)
|
|
{
|
|
DamageRegionFilterClass region_filter(current_region);
|
|
INodeListClass bone_list(damage_root, CurTime, ®ion_filter);
|
|
|
|
num_damage_bones += bone_list.Num_Nodes();
|
|
|
|
// Move to the next region if there aren't any bones in this one.
|
|
if (bone_list.Num_Nodes() <= 0)
|
|
continue;
|
|
|
|
// Put together an animation name for this damage region.
|
|
char anim_name[W3D_NAME_LEN];
|
|
sprintf(anim_name, "damage%d-%d", current_region, damage_state);
|
|
|
|
// Export an animation for this damage region.
|
|
MotionClass *motion = NULL;
|
|
try
|
|
{
|
|
motion = new MotionClass( ExportInterface->theScene,
|
|
&bone_list,
|
|
htree,
|
|
ExportOptions,
|
|
FrameRate,
|
|
&meter,
|
|
MaxInterface->GetMAXHWnd(),
|
|
anim_name,
|
|
originoffset);
|
|
}
|
|
catch (ErrorClass err)
|
|
{
|
|
MessageBox(NULL, err.error_message, "Error!", MB_OK | MB_SETFOREGROUND);
|
|
return false;
|
|
}
|
|
|
|
assert(motion != NULL);
|
|
motion->Save(csave);
|
|
|
|
delete motion;
|
|
}
|
|
|
|
if (num_damage_bones <= 0)
|
|
{
|
|
MessageBox(NULL, "Warning: Your damage bones need to be given damage region numbers. "
|
|
"You can do this in the W3D Tools panel.", name, MB_OK | MB_ICONINFORMATION | MB_SETFOREGROUND);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
#define TEAM_COLOR_PALETTE_SIZE 16
|
|
unsigned int houseColorScale[TEAM_COLOR_PALETTE_SIZE]=
|
|
{255,239,223,211,195,174,167,151,135,123,107,91,79,63,47,35};
|
|
|
|
/***********************************************************************************************
|
|
* W3dExportClass::Export_Geometry -- Export the geometry data *
|
|
* *
|
|
* INPUT: *
|
|
* *
|
|
* OUTPUT: *
|
|
* *
|
|
* WARNINGS: *
|
|
* *
|
|
* HISTORY: *
|
|
* 10/16/1997 GH : Created. *
|
|
* 13/9/1999 AJA : Added an optional "root" parameter to export geometry of the node's *
|
|
* descendants. *
|
|
* 10/17/2000 gth : Made the "root" parameter a requirement, just pass in the scene root *
|
|
* if you want to export all geometry in the scene. *
|
|
* 10/30/2000 gth : If exporting only geometry, only export the first mesh *
|
|
*=============================================================================================*/
|
|
bool W3dExportClass::Export_Geometry(char * name,ChunkSaveClass & csave,Progress_Meter_Class & meter,
|
|
INode *root,MeshConnectionsClass **out_connection)
|
|
{
|
|
unsigned int i;
|
|
|
|
assert(root != NULL);
|
|
if (!ExportOptions.ExportGeometry) return true;
|
|
|
|
/*
|
|
** If we're attaching the meshes to a hierarchy, get the tree
|
|
*/
|
|
HierarchySaveClass * htree = NULL;
|
|
if (ExportOptions.LoadHierarchy || ExportOptions.ExportHierarchy) {
|
|
htree = get_hierarchy_tree();
|
|
if (htree == NULL) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
DynamicVectorClass<GeometryExportTaskClass *> export_tasks;
|
|
INodeListClass *geometry_list = NULL;
|
|
|
|
/*
|
|
** Create the lists of nodes that we're going to work with
|
|
*/
|
|
GeometryFilterClass geometryfilter;
|
|
geometry_list = new INodeListClass(root,CurTime,&geometryfilter);
|
|
if (dupe_check(*geometry_list)) {
|
|
return false;
|
|
}
|
|
|
|
MaxWorldInfoClass world_info(export_tasks);
|
|
world_info.Allow_Mesh_Smoothing (ExportOptions.SmoothBetweenMeshes);
|
|
unsigned int materialColors[16*16]; ///@todo: MW: Fix this to remove solid colors.
|
|
char materialColorFilename[_MAX_FNAME + 1];
|
|
memset(materialColors,0,sizeof(materialColors));
|
|
for (i=0; i<TEAM_COLOR_PALETTE_SIZE; i++)
|
|
{ //preset the first 16 colors to predefined set of house colors
|
|
materialColors[i]=houseColorScale[i] << 16;
|
|
}
|
|
|
|
/*
|
|
** Initialize the context object for exporting geometry
|
|
*/
|
|
GeometryExportContextClass context( name,
|
|
csave,
|
|
world_info,
|
|
ExportOptions,
|
|
htree,
|
|
root,
|
|
get_origin_list(),
|
|
CurTime,
|
|
materialColors);
|
|
|
|
if (ExportOptions.EnableMaterialColorToTextureConversion)
|
|
context.materialColorTexture=materialColorFilename;
|
|
|
|
sprintf(materialColorFilename,"%sZMCD_%s.tga",CurrentExportPath,name);
|
|
|
|
/*
|
|
** Initialize a list of geometry export tasks containing all nodes marked for geometry export.
|
|
** If we're only exporting geometry, only export the first mesh. (no more collections)
|
|
*/
|
|
int geometry_count = geometry_list->Num_Nodes();
|
|
if ((htree == NULL) && (geometry_list->Num_Nodes() > 1)) {
|
|
geometry_count = MIN(geometry_count,1);
|
|
ExportLog::printf("\nDiscarding extra meshes since we are not exporting a hierarchical model.\n");
|
|
}
|
|
|
|
for (i=0; i<geometry_count; i++) {
|
|
GeometryExportTaskClass * export_task = GeometryExportTaskClass::Create_Task((*geometry_list)[i],context);
|
|
if (export_task != NULL) {
|
|
export_tasks.Add(export_task);
|
|
}
|
|
}
|
|
meter.Finish_In_Steps(export_tasks.Count());
|
|
|
|
/*
|
|
** Optimize the mesh data if the user desired, modifying the list of geometry export tasks.
|
|
*/
|
|
if (ExportOptions.EnableOptimizeMeshData) {
|
|
GeometryExportTaskClass::Optimize_Geometry(export_tasks,context);
|
|
}
|
|
|
|
/*
|
|
** If there is only one piece of geometry to export and no place-holders, and we're not
|
|
** exporting a hierarchical model, then we force the name to match the filename
|
|
*/
|
|
if ((export_tasks.Count() == 1) && (htree == NULL))
|
|
{
|
|
export_tasks[0]->Set_Name(name);
|
|
export_tasks[0]->Set_Container_Name("");
|
|
}
|
|
|
|
/*
|
|
** Generate the mesh-connections object to return to the caller
|
|
*/
|
|
MeshConnectionsClass * meshcon = NULL;
|
|
if (htree != NULL) {
|
|
Progress_Meter_Class mcmeter(meter,meter.Increment);
|
|
try {
|
|
meshcon = new MeshConnectionsClass(export_tasks,context);
|
|
} catch (ErrorClass err) {
|
|
MessageBox(NULL,err.error_message,"Error!",MB_OK | MB_SETFOREGROUND);
|
|
return false;
|
|
}
|
|
*out_connection = meshcon;
|
|
meter.Add_Increment();
|
|
}
|
|
|
|
/*
|
|
** Export each piece of geometry
|
|
*/
|
|
for (i=0; i<export_tasks.Count(); i++) {
|
|
Progress_Meter_Class meshmeter(meter,meter.Increment);
|
|
context.ProgressMeter = &meshmeter;
|
|
|
|
try {
|
|
export_tasks[i]->Export_Geometry(context);
|
|
} catch (ErrorClass err) {
|
|
MessageBox(MaxInterface->GetMAXHWnd(),err.error_message,"Error!",MB_OK | MB_SETFOREGROUND);
|
|
continue;
|
|
}
|
|
|
|
meter.Add_Increment();
|
|
}
|
|
|
|
//Check if any textures need to be generated
|
|
if (context.numMaterialColors || context.numHouseColors)
|
|
{
|
|
Targa targ;
|
|
char imageBuffer[16*16*3];
|
|
int px,py,buf_index;
|
|
unsigned int Diffuse;
|
|
|
|
//clear to black
|
|
memset(imageBuffer,0,sizeof(imageBuffer));
|
|
|
|
for (i=0; i<(16+context.numMaterialColors); i++)
|
|
{
|
|
//get coordinates of this material within texture page
|
|
px=(i%16); ///@todo: MW: Remove hard-coded texture size
|
|
py=(i/16);
|
|
|
|
Diffuse=context.materialColors[i];
|
|
buf_index=(px+py*16)*3;
|
|
|
|
imageBuffer[buf_index]=(Diffuse>>16)&0xff;
|
|
imageBuffer[buf_index+1]=(Diffuse>>8)&0xff;
|
|
imageBuffer[buf_index+2]=(Diffuse)&0xff;
|
|
}
|
|
|
|
memset(&targ.Header,0,sizeof(targ.Header));
|
|
targ.Header.Width=16;
|
|
targ.Header.Height=16;
|
|
targ.Header.PixelDepth=24;
|
|
targ.Header.ImageType=TGA_TRUECOLOR;
|
|
targ.SetImage(imageBuffer);
|
|
targ.YFlip();
|
|
|
|
if (context.numHouseColors)
|
|
sprintf(materialColorFilename,"%sZHCD_%s.tga",CurrentExportPath,name);
|
|
|
|
targ.Save(materialColorFilename,TGAF_IMAGE,false);
|
|
}
|
|
|
|
/*
|
|
** Cleanup
|
|
*/
|
|
for (i=0; i<export_tasks.Count(); i++) {
|
|
delete export_tasks[i];
|
|
}
|
|
|
|
export_tasks.Delete_All();
|
|
delete geometry_list;
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
/***********************************************************************************************
|
|
* W3dExportClass::Export_HLod -- Export an HLOD description *
|
|
* *
|
|
* INPUT: *
|
|
* *
|
|
* OUTPUT: *
|
|
* *
|
|
* WARNINGS: *
|
|
* *
|
|
* HISTORY: *
|
|
* 10/17/2000 gth : Created. *
|
|
*=============================================================================================*/
|
|
bool W3dExportClass::Export_HLod( char *name, const char *htree_name, ChunkSaveClass &csave,
|
|
Progress_Meter_Class &meter, MeshConnectionsClass **connections,
|
|
int lod_count)
|
|
{
|
|
if (!ExportOptions.ExportGeometry) return true;
|
|
|
|
HLodSaveClass hlod_save(connections, lod_count, CurTime, name, htree_name, meter, get_origin_list());
|
|
if (!hlod_save.Save(csave))
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
|
|
/***********************************************************************************************
|
|
* W3dExportClass::get_hierarchy_tree -- get a pointer to the hierarchy tree *
|
|
* *
|
|
* INPUT: *
|
|
* *
|
|
* OUTPUT: *
|
|
* *
|
|
* WARNINGS: *
|
|
* *
|
|
* HISTORY: *
|
|
* 10/16/1997 GH : Created. *
|
|
*=============================================================================================*/
|
|
HierarchySaveClass * W3dExportClass::get_hierarchy_tree(void)
|
|
{
|
|
/*
|
|
** If the hierarchy tree pointer has been initialized, just return it
|
|
*/
|
|
if (HierarchyTree != NULL) return HierarchyTree;
|
|
|
|
/*
|
|
** If we are supposed to be loading a hierarchy from disk, then
|
|
** load it
|
|
*/
|
|
if (!ExportOptions.ExportHierarchy) {
|
|
HierarchyTree = load_hierarchy_file(HierarchyFilename);
|
|
if (HierarchyTree) {
|
|
return HierarchyTree;
|
|
} else {
|
|
char buf[256];
|
|
sprintf(buf,"Unable to load hierarchy file: %s\nIf this Max file has been moved, please re-select the hierarchy file.",HierarchyFilename);
|
|
MessageBox(MaxInterface->GetMAXHWnd(),buf,"Error",MB_OK | MB_SETFOREGROUND);
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
/*
|
|
** Should never fall through to here...
|
|
** This would only happen if ExportHierarchy was true and the Export_Hierarchy
|
|
** function failed to create a hierarchy tree for us.
|
|
*/
|
|
assert(0);
|
|
return NULL;
|
|
}
|
|
|
|
|
|
/***********************************************************************************************
|
|
* W3dExportClass::get_damage_root_list -- gets the list of damage root nodes *
|
|
* *
|
|
* INPUT: *
|
|
* *
|
|
* OUTPUT: *
|
|
* *
|
|
* WARNINGS: *
|
|
* *
|
|
* HISTORY: *
|
|
* 10/17/2000 gth : Created. *
|
|
*=============================================================================================*/
|
|
INodeListClass * W3dExportClass::get_damage_root_list(void)
|
|
{
|
|
if (DamageRootList != NULL) return DamageRootList;
|
|
|
|
/*
|
|
** Create a list of all damage root objects in the scene.
|
|
*/
|
|
DamageRootFilterClass nodefilter;
|
|
DamageRootList = new INodeListClass(ExportInterface->theScene, CurTime, &nodefilter);
|
|
return DamageRootList;
|
|
}
|
|
|
|
|
|
/***********************************************************************************************
|
|
* get_origin_list -- get the list of origin nodes *
|
|
* *
|
|
* INPUT: *
|
|
* *
|
|
* OUTPUT: *
|
|
* *
|
|
* WARNINGS: *
|
|
* *
|
|
* HISTORY: *
|
|
* 9/13/1999 AJA : Created. *
|
|
*=============================================================================================*/
|
|
INodeListClass * W3dExportClass::get_origin_list(void)
|
|
{
|
|
if (OriginList != NULL) return OriginList;
|
|
|
|
/*
|
|
** Create a list of all origins in the scene.
|
|
*/
|
|
static OriginFilterClass originfilter;
|
|
OriginList = new INodeListClass (ExportInterface->theScene, CurTime, &originfilter);
|
|
|
|
/*
|
|
** If we didn't find any origins, add the scene root as an origin.
|
|
** NOTE: it would also be a problem if the origin list contained both the scene root
|
|
** and the user placed origins. Thats not happening now because the OriginList
|
|
** does not collect the scene root... were that to change we'd have to update this
|
|
** code as well.
|
|
*/
|
|
if (OriginList->Num_Nodes() == 0) {
|
|
OriginList->Insert(MaxInterface->GetRootNode());
|
|
}
|
|
|
|
return OriginList;
|
|
}
|
|
|
|
/***********************************************************************************************
|
|
* W3dExportClass::get_export_options -- get the export options *
|
|
* *
|
|
* INPUT: *
|
|
* *
|
|
* OUTPUT: *
|
|
* *
|
|
* WARNINGS: *
|
|
* *
|
|
* HISTORY: *
|
|
* 10/16/1997 GH : Created. *
|
|
* 9/30/1999 AJA : Added support for the MAX suppress_prompts flag. *
|
|
*=============================================================================================*/
|
|
bool W3dExportClass::get_export_options(BOOL suppress_prompts)
|
|
{
|
|
int ticksperframe = GetTicksPerFrame();
|
|
|
|
// Get the last export settings from the AppDataChunk attached to the
|
|
// scene pointer. If there is no such AppDataChunk create one and set it
|
|
// to default values.
|
|
|
|
W3dExportOptionsStruct *options = NULL;
|
|
|
|
AppDataChunk * appdata = MaxInterface->GetScenePointer()->GetAppDataChunk(W3D_EXPORTER_CLASS_ID,SCENE_EXPORT_CLASS_ID,0);
|
|
|
|
if (appdata) {
|
|
options = &(((ExportInfoAppDataChunkStruct *)(appdata->data))->ExportOptions);
|
|
} else {
|
|
|
|
ExportInfoAppDataChunkStruct *appdata_struct =
|
|
(ExportInfoAppDataChunkStruct *)malloc(sizeof(ExportInfoAppDataChunkStruct));
|
|
|
|
options = &(appdata_struct->ExportOptions);
|
|
|
|
options->ExportHierarchy = true;
|
|
options->LoadHierarchy = false;
|
|
options->ExportAnimation = true;
|
|
options->EnableTerrainMode = false;
|
|
|
|
options->ReduceAnimation = false;
|
|
options->ReduceAnimationPercent = 50;
|
|
|
|
options->CompressAnimation = false;
|
|
options->CompressAnimationFlavor = ANIM_FLAVOR_TIMECODED;
|
|
options->CompressAnimationTranslationError = 0.001f; //DEFAULT_LOSSY_ERROR_TOLERANCE;
|
|
options->CompressAnimationRotationError = 0.050f; //DEFAULT_LOSSY_ERROR_TOLERANCE;
|
|
options->ReviewLog = false;
|
|
|
|
options->ExportGeometry = true;
|
|
options->TranslationOnly = false;
|
|
options->SmoothBetweenMeshes = true;
|
|
|
|
strcpy(options->HierarchyFilename,"");
|
|
strcpy(options->RelativeHierarchyFilename,"");
|
|
options->StartFrame = MaxInterface->GetAnimRange().Start() / ticksperframe;
|
|
options->EndFrame = MaxInterface->GetAnimRange().End() / ticksperframe;
|
|
options->UseVoxelizer = false;
|
|
options->DisableExportAABTrees = true;
|
|
options->EnableOptimizeMeshData = false;
|
|
options->EnableMaterialColorToTextureConversion = false;
|
|
|
|
memset(&(appdata_struct->Padding), 0, sizeof(appdata_struct->Padding));
|
|
|
|
MaxInterface->GetScenePointer()->AddAppDataChunk(W3D_EXPORTER_CLASS_ID,
|
|
SCENE_EXPORT_CLASS_ID, 0, sizeof(ExportInfoAppDataChunkStruct),
|
|
appdata_struct);
|
|
|
|
}
|
|
|
|
// (gth) disabling the 'optimize mesh data' feature due to problems with external tools
|
|
options->EnableOptimizeMeshData = false;
|
|
|
|
bool retval = true;
|
|
if (suppress_prompts == FALSE)
|
|
{
|
|
W3dOptionsDialogClass dialog(MaxInterface,ExportInterface);
|
|
retval = dialog.Get_Export_Options(options);
|
|
}
|
|
|
|
if (suppress_prompts || retval) {
|
|
|
|
ExportOptions = *options;
|
|
|
|
if ( (suppress_prompts == TRUE) && (options->RelativeHierarchyFilename[0] != 0) )
|
|
{
|
|
// Use the relative pathname WRT the max scene's directory to
|
|
// figure out the absolute directory where the hierarchy file
|
|
// is stored.
|
|
char curdir[_MAX_DRIVE + _MAX_DIR + 1];
|
|
assert(_getcwd(curdir, sizeof(curdir)));
|
|
assert(_chdir(CurrentScenePath) != -1);
|
|
assert(_fullpath(HierarchyFilename, options->RelativeHierarchyFilename,
|
|
sizeof(HierarchyFilename)));
|
|
assert(_chdir(curdir) != -1);
|
|
}
|
|
else
|
|
strcpy(HierarchyFilename,options->HierarchyFilename);
|
|
|
|
if (ExportOptions.TranslationOnly) {
|
|
FixupType = HierarchySaveClass::MATRIX_FIXUP_TRANS;
|
|
} else {
|
|
FixupType = HierarchySaveClass::MATRIX_FIXUP_TRANS_ROT;
|
|
}
|
|
}
|
|
|
|
return retval;
|
|
}
|
|
|
|
|
|
|
|
|
|
/***********************************************************************************************
|
|
* W3dExportClass::Start_Progress_Bar -- start the MAX progress meter *
|
|
* *
|
|
* INPUT: *
|
|
* *
|
|
* OUTPUT: *
|
|
* *
|
|
* WARNINGS: *
|
|
* *
|
|
* HISTORY: *
|
|
* 10/16/1997 GH : Created. *
|
|
*=============================================================================================*/
|
|
void W3dExportClass::Start_Progress_Bar(void)
|
|
{
|
|
MaxInterface->ProgressStart(
|
|
"Processing Triangle Mesh",
|
|
TRUE,
|
|
progress_callback,
|
|
NULL);
|
|
}
|
|
|
|
/***********************************************************************************************
|
|
* W3dExportClass::End_Progress_Bar -- end the progress meter *
|
|
* *
|
|
* INPUT: *
|
|
* *
|
|
* OUTPUT: *
|
|
* *
|
|
* WARNINGS: *
|
|
* *
|
|
* HISTORY: *
|
|
* 10/16/1997 GH : Created. *
|
|
*=============================================================================================*/
|
|
void W3dExportClass::End_Progress_Bar(void)
|
|
{
|
|
MaxInterface->ProgressUpdate( 100);
|
|
MaxInterface->ProgressEnd();
|
|
}
|
|
|
|
static bool dupe_check(const INodeListClass & list)
|
|
{
|
|
for (unsigned i=0; i<list.Num_Nodes(); i++) {
|
|
|
|
/*
|
|
** Don't check aggregate objects, they are allowed to have the same name
|
|
*/
|
|
if (!Is_Aggregate(list[i])) {
|
|
for (unsigned j = i+1; j<list.Num_Nodes(); j++) {
|
|
if (stricmp(list[i]->GetName(),list[j]->GetName()) == 0) {
|
|
char buf[256];
|
|
sprintf(buf,"Geometry Nodes with duplicated names found!\nDuplicated Name: %s\n",list[i]->GetName());
|
|
MessageBox(NULL,buf,"Error",MB_OK | MB_SETFOREGROUND);
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static bool check_lod_extensions (INodeListClass &list, INode *origin)
|
|
{
|
|
/*
|
|
** Assumptions:
|
|
** - If origin == NULL, then we're just exporting a single model and don't need to
|
|
** worry about lod extensions at all.
|
|
** - If origin is the root of the scene, then we're just exporting a single model as well.
|
|
** - Otherwise origin actually points to an Origin and not just any INode.
|
|
*/
|
|
if (origin == NULL) return true;
|
|
if (origin->IsRootNode()) return true;
|
|
|
|
char *extension = strrchr(origin->GetName(), '.');
|
|
int ext_len = strlen(extension);
|
|
for (unsigned i = 0; i < list.Num_Nodes(); i++)
|
|
{
|
|
char *this_ext = strrchr(list[i]->GetName(), '.');
|
|
|
|
// Check for the existance of an extension in this node.
|
|
if (this_ext == NULL)
|
|
return false;
|
|
|
|
// Check that the extensions are the same.
|
|
if (strcmp(this_ext, extension) != 0)
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
bool W3dExportClass::get_base_object_tm (Matrix3 &tm)
|
|
{
|
|
INodeListClass *origin_list = get_origin_list();
|
|
if (!origin_list)
|
|
return false;
|
|
|
|
unsigned int i, count = origin_list->Num_Nodes();
|
|
INode *base_origin = NULL;
|
|
for (i = 0; i < count; i++)
|
|
{
|
|
INode *node = (*origin_list)[i];
|
|
if (Is_Base_Origin(node))
|
|
{
|
|
// we found origin.00, fall through
|
|
base_origin = node;
|
|
break;
|
|
}
|
|
}
|
|
if (!base_origin)
|
|
return false;
|
|
|
|
tm = base_origin->GetNodeTM(CurTime);
|
|
return true;
|
|
}
|
|
|
|
static DWORD WINAPI progress_callback( LPVOID arg )
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
|
|
static HierarchySaveClass * load_hierarchy_file(char * filename)
|
|
{
|
|
HierarchySaveClass * hier = NULL;
|
|
|
|
RawFileClass file(filename);
|
|
|
|
if (!file.Open()) {
|
|
return NULL;
|
|
}
|
|
ChunkLoadClass cload(&file);
|
|
|
|
cload.Open_Chunk();
|
|
if (cload.Cur_Chunk_ID() == W3D_CHUNK_HIERARCHY) {
|
|
hier = new HierarchySaveClass();
|
|
hier->Load(cload);
|
|
} else {
|
|
hier = NULL;
|
|
file.Close();
|
|
return NULL;
|
|
}
|
|
|
|
cload.Close_Chunk();
|
|
file.Close();
|
|
|
|
return hier;
|
|
}
|
|
|