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, &region_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;
}