1187 lines
46 KiB
C++
1187 lines
46 KiB
C++
/*
|
|
** 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/>.
|
|
*/
|
|
|
|
/***********************************************************************************************
|
|
*** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S ***
|
|
***********************************************************************************************
|
|
* *
|
|
* Project Name : Max2W3D *
|
|
* *
|
|
* $Archive:: /Commando/Code/Tools/max2w3d/geometryexporttask.cpp $*
|
|
* *
|
|
* Original Author:: Greg Hjelstrom *
|
|
* *
|
|
* $Author:: Greg_h $*
|
|
* *
|
|
* $Modtime:: 3/14/02 3:27p $*
|
|
* *
|
|
* $Revision:: 8 $*
|
|
* *
|
|
*---------------------------------------------------------------------------------------------*
|
|
* Functions: *
|
|
* GeometryExportTaskClass::GeometryExportTaskClass -- Constructor *
|
|
* GeometryExportTaskClass::GeometryExportTaskClass -- Copy Constructor *
|
|
* GeometryExportTaskClass::~GeometryExportTaskClass -- Destructor *
|
|
* GeometryExportTaskClass::Get_Full_Name -- Composes the full name of this robj *
|
|
* GeometryExportTaskClass::Create_Task -- virtual constructor for export tasks *
|
|
* GeometryExportTaskClass::Optimize_Geometry -- Optimizes the export tasks *
|
|
* GeometryExportTaskClass::Generate_Unique_Name -- create a unique name for this object *
|
|
* MeshGeometryExportTaskClass::Is_Single_Material -- Tests if this mesh uses a single mater *
|
|
* MeshGeometryExportTaskClass::Get_Single_Material -- returns pointer to the material *
|
|
* MeshGeometryExportTaskClass::Cache_Single_Material -- updates the cached material pointer *
|
|
* MeshGeometryExportTaskClass::Split -- Splits into single material meshes *
|
|
* MeshGeometryExportTaskClass::Reduce_To_Single_Material -- deletes polys *
|
|
* MeshGeometryExportTaskClass::Can_Combine -- can this mesh combine with anything *
|
|
* MeshGeometryExportTaskClass::Can_Combine_With -- can this mesh be combined with the given *
|
|
* MeshGeometryExportTaskClass::Combine_Mesh -- Add the given mesh into this mesh *
|
|
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
|
|
|
#include "geometryexporttask.h"
|
|
#include "geometryexportcontext.h"
|
|
#include "util.h"
|
|
#include "w3dutil.h"
|
|
#include "w3dappdata.h"
|
|
#include "hiersave.h"
|
|
#include "maxworldinfo.h"
|
|
#include "meshsave.h"
|
|
#include "colboxsave.h"
|
|
#include "dazzlesave.h"
|
|
#include <bitarray.h>
|
|
|
|
|
|
const int OPTIMIZATION_FACECOUNT_LIMIT = 256; // TODO: what should this number be...
|
|
const float OPTIMIZATION_COMBINING_DISTANCE = 20.0f; // TODO: need a smarter method for combining...
|
|
|
|
|
|
/**
|
|
** MeshGeometryExportTaskClass
|
|
** Export task for INodes which are to generate W3D meshes
|
|
*/
|
|
class MeshGeometryExportTaskClass : public GeometryExportTaskClass
|
|
{
|
|
public:
|
|
|
|
MeshGeometryExportTaskClass(INode * node,GeometryExportContextClass & context) :
|
|
GeometryExportTaskClass(node,context),
|
|
NameDirty(false),
|
|
SingleMtl(NULL)
|
|
{
|
|
/*
|
|
** Copy the export options
|
|
*/
|
|
ExportOptions = *(W3DAppData2Struct::Get_App_Data(Node));
|
|
|
|
/*
|
|
** Copy the mesh
|
|
*/
|
|
Object * obj = Node->EvalWorldState(CurTime).obj;
|
|
TriObject * tri = (TriObject *)obj->ConvertToType(CurTime, triObjectClassID);
|
|
MeshData = tri->mesh;
|
|
|
|
/*
|
|
** Store a pointer to the material if this mesh uses only one material (even inside a Multi-Sub)
|
|
*/
|
|
Update_Cached_Data();
|
|
}
|
|
|
|
MeshGeometryExportTaskClass(const MeshGeometryExportTaskClass & that) :
|
|
GeometryExportTaskClass(that),
|
|
MeshData(that.MeshData),
|
|
ExportOptions(that.ExportOptions),
|
|
NameDirty(false)
|
|
{
|
|
}
|
|
|
|
virtual ~MeshGeometryExportTaskClass(void)
|
|
{
|
|
}
|
|
|
|
virtual void Export_Geometry(GeometryExportContextClass & context)
|
|
{
|
|
/*
|
|
** Create the mesh
|
|
*/
|
|
context.WorldInfo.Set_Current_Task(this);
|
|
context.WorldInfo.Set_Export_Transform(ExportSpace);
|
|
|
|
MeshSaveClass * mesh = new MeshSaveClass( Name,
|
|
ContainerName,
|
|
Node,
|
|
&MeshData,
|
|
ExportSpace,
|
|
ExportOptions,
|
|
context.HTree,
|
|
context.CurTime,
|
|
*context.ProgressMeter,
|
|
&context.WorldInfo );
|
|
|
|
/*
|
|
** Export It
|
|
*/
|
|
mesh->Write_To_File(context.CSave,!context.Options.DisableExportAABTrees);
|
|
delete mesh;
|
|
context.ProgressMeter->Add_Increment();
|
|
};
|
|
|
|
/*
|
|
** Naming. During the optimization phase, sometimes new meshes are created and require
|
|
** new unique names. These meshes are flagged and then new names are generated prior
|
|
** to exporting.
|
|
*/
|
|
bool Is_Name_Dirty(void) { return NameDirty; }
|
|
void Set_Name_Dirty(bool onoff) { NameDirty = onoff; }
|
|
|
|
/*
|
|
** Vertex Normal smoothing support!
|
|
*/
|
|
virtual Point3 Get_Shared_Vertex_Normal(const Point3 & pos,int smgroup);
|
|
|
|
/*
|
|
** Optimization functions
|
|
*/
|
|
bool Is_Single_Material(void);
|
|
Mtl * Get_Single_Material(void);
|
|
void Split(DynamicVectorClass<MeshGeometryExportTaskClass *> & simple_meshes);
|
|
void Reduce_To_Single_Material(int mat_id);
|
|
bool Can_Combine(void);
|
|
bool Can_Combine_With(MeshGeometryExportTaskClass * other_mesh);
|
|
void Combine_Mesh(MeshGeometryExportTaskClass * other_mesh);
|
|
|
|
protected:
|
|
|
|
virtual int Get_Geometry_Type(void) { return MESH; }
|
|
void Update_Cached_Data(void);
|
|
|
|
Mesh MeshData; // Copy of the mesh data to be exported.
|
|
W3DAppData2Struct ExportOptions; // Copy of the export options in case we want to change them during optimization
|
|
bool NameDirty;
|
|
|
|
// Cached Data about the Node/Mesh. Updated by calling Update_Cached_Data whenever the mesh changes.
|
|
Mtl * SingleMtl; // Pointer to the single material (if the mesh uses only one, even in a multi-mtl)
|
|
Point3 BoxCenter; // Center of the bounding box (in object space)
|
|
Point3 BoxExtent; // Extent of the bounding box (in object space)
|
|
Box3 WorldBounds; // World-space bounding box
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
** CollisionBoxGeometryExportTaskClass
|
|
** Export task for INodes which are to generate W3D AABoxes or OBBoxes
|
|
*/
|
|
class CollisionBoxGeometryExportTaskClass : public GeometryExportTaskClass
|
|
{
|
|
public:
|
|
CollisionBoxGeometryExportTaskClass(INode * node,GeometryExportContextClass & context) :
|
|
GeometryExportTaskClass(node,context)
|
|
{
|
|
}
|
|
|
|
virtual void Export_Geometry(GeometryExportContextClass & context)
|
|
{
|
|
/*
|
|
** Create the collision box
|
|
*/
|
|
CollisionBoxSaveClass * colbox = new CollisionBoxSaveClass( Name,
|
|
ContainerName,
|
|
Node,
|
|
ExportSpace,
|
|
context.CurTime,
|
|
*context.ProgressMeter);
|
|
|
|
/*
|
|
** Export it
|
|
*/
|
|
colbox->Write_To_File(context.CSave);
|
|
delete colbox;
|
|
context.ProgressMeter->Add_Increment();
|
|
};
|
|
|
|
protected:
|
|
|
|
virtual int Get_Geometry_Type(void) { return COLLISIONBOX; }
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
** DazzleGeometryExportTaskClass
|
|
** Export task for INodes which are to generate W3D Dazzle objects
|
|
*/
|
|
class DazzleGeometryExportTaskClass : public GeometryExportTaskClass
|
|
{
|
|
public:
|
|
DazzleGeometryExportTaskClass(INode * node,GeometryExportContextClass & context) :
|
|
GeometryExportTaskClass(node,context)
|
|
{
|
|
}
|
|
|
|
virtual void Export_Geometry(GeometryExportContextClass & context)
|
|
{
|
|
/*
|
|
** Create the dazzle object
|
|
*/
|
|
DazzleSaveClass * dazzle = new DazzleSaveClass( Name,
|
|
ContainerName,
|
|
Node,
|
|
ExportSpace,
|
|
context.CurTime,
|
|
*context.ProgressMeter);
|
|
/*
|
|
** Export it.
|
|
*/
|
|
dazzle->Write_To_File(context.CSave);
|
|
delete dazzle;
|
|
context.ProgressMeter->Add_Increment();
|
|
};
|
|
|
|
protected:
|
|
|
|
virtual int Get_Geometry_Type(void) { return DAZZLE; }
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
** NullGeometryExportTaskClass
|
|
** Export task for INodes which are to generate W3D NULL objects. Note that this
|
|
** does not do anything in the Export_Geometry call, these only create entries in
|
|
** any Hierarhcical model or collection object being exported.
|
|
*/
|
|
class NullGeometryExportTaskClass : public GeometryExportTaskClass
|
|
{
|
|
public:
|
|
NullGeometryExportTaskClass(INode * node,GeometryExportContextClass & context) :
|
|
GeometryExportTaskClass(node,context)
|
|
{
|
|
memset(ContainerName,0,sizeof(ContainerName));
|
|
memset(Name,0,sizeof(Name));
|
|
strcpy(Name,"NULL");
|
|
}
|
|
|
|
virtual void Export_Geometry(GeometryExportContextClass & context)
|
|
{
|
|
context.ProgressMeter->Add_Increment();
|
|
};
|
|
|
|
protected:
|
|
|
|
virtual int Get_Geometry_Type(void) { return NULLOBJ; }
|
|
|
|
};
|
|
|
|
|
|
/**
|
|
** AggregateGeometryExportTaskClass
|
|
** Export task for INodes which are to generate W3D Aggregates. These are nodes
|
|
** that refer to some external W3D object. This export task doesn't export any
|
|
** geometry (similer to the Null export task) and it clears its container name
|
|
** because the object to be attached is not a sub-object of the model we are
|
|
** currently exporting.
|
|
*/
|
|
class AggregateGeometryExportTaskClass : public GeometryExportTaskClass
|
|
{
|
|
public:
|
|
AggregateGeometryExportTaskClass(INode * node,GeometryExportContextClass & context) :
|
|
GeometryExportTaskClass(node,context)
|
|
{
|
|
memset(ContainerName,0,sizeof(ContainerName));
|
|
}
|
|
|
|
virtual void Export_Geometry(GeometryExportContextClass & context)
|
|
{
|
|
context.ProgressMeter->Add_Increment();
|
|
};
|
|
|
|
virtual bool Is_Aggregate(void)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
protected:
|
|
|
|
virtual int Get_Geometry_Type(void) { return AGGREGATE; }
|
|
|
|
};
|
|
|
|
|
|
/**
|
|
** ProxyExportTaskClass
|
|
** These are used by the Renegade Level Editor to cause game objects to be
|
|
** instantiated at the specified transform. Like aggregates they have to
|
|
** be handled specially and therefore have the Is_Proxy member function devoted
|
|
** solely to them :-) Hopefully we don't have any more geometry types which
|
|
** have to be handled specially or this is going to get messy again.
|
|
*/
|
|
class ProxyExportTaskClass : public GeometryExportTaskClass
|
|
{
|
|
public:
|
|
|
|
ProxyExportTaskClass(INode * node,GeometryExportContextClass & context) :
|
|
GeometryExportTaskClass(node,context)
|
|
{
|
|
/*
|
|
** clear the container name
|
|
*/
|
|
memset(ContainerName,0,sizeof(ContainerName));
|
|
|
|
/*
|
|
** strip the trailing ~
|
|
*/
|
|
char *tilda = ::strchr(Name, '~');
|
|
memset(tilda,0,sizeof(Name) - ((int)tilda - (int)Name));
|
|
|
|
}
|
|
|
|
virtual void Export_Geometry(GeometryExportContextClass & context)
|
|
{
|
|
context.ProgressMeter->Add_Increment();
|
|
};
|
|
|
|
virtual bool Is_Proxy(void) { return true; }
|
|
|
|
protected:
|
|
|
|
virtual int Get_Geometry_Type(void) { return PROXY; }
|
|
|
|
};
|
|
|
|
|
|
/***********************************************************************************************
|
|
**
|
|
** Implementations
|
|
**
|
|
***********************************************************************************************/
|
|
|
|
/***********************************************************************************************
|
|
* GeometryExportTaskClass::GeometryExportTaskClass -- Constructor *
|
|
* *
|
|
* INPUT: *
|
|
* *
|
|
* OUTPUT: *
|
|
* *
|
|
* WARNINGS: *
|
|
* *
|
|
* HISTORY: *
|
|
* 10/20/2000 gth : Created. *
|
|
*=============================================================================================*/
|
|
GeometryExportTaskClass::GeometryExportTaskClass(INode * node,GeometryExportContextClass & context) :
|
|
BoneIndex(0),
|
|
ExportSpace(1),
|
|
CurTime(context.CurTime),
|
|
Node(node)
|
|
{
|
|
/*
|
|
** Set up the names
|
|
*/
|
|
Set_W3D_Name(Name,Node->GetName());
|
|
Append_Lod_Character(Name,Get_Lod_Level(context.Origin),context.OriginList);
|
|
Set_W3D_Name(ContainerName,context.ModelName);
|
|
|
|
/*
|
|
** Set up the bone index and export coordinate system.
|
|
*/
|
|
if (context.HTree != NULL) {
|
|
if (!Is_Skin(node)) {
|
|
context.HTree->Get_Export_Coordinate_System(Node,&BoneIndex,NULL,&ExportSpace);
|
|
} else {
|
|
BoneIndex = 0;
|
|
ExportSpace = context.OriginTransform;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/***********************************************************************************************
|
|
* GeometryExportTaskClass::GeometryExportTaskClass -- Copy Constructor *
|
|
* *
|
|
* INPUT: *
|
|
* *
|
|
* OUTPUT: *
|
|
* *
|
|
* WARNINGS: *
|
|
* *
|
|
* HISTORY: *
|
|
* 10/20/2000 gth : Created. *
|
|
*=============================================================================================*/
|
|
GeometryExportTaskClass::GeometryExportTaskClass(const GeometryExportTaskClass & that) :
|
|
BoneIndex(that.BoneIndex),
|
|
ExportSpace(that.ExportSpace),
|
|
CurTime(that.CurTime),
|
|
Node(that.Node)
|
|
{
|
|
Set_W3D_Name(Name,that.Name);
|
|
Set_W3D_Name(ContainerName,that.ContainerName);
|
|
}
|
|
|
|
|
|
/***********************************************************************************************
|
|
* GeometryExportTaskClass::~GeometryExportTaskClass -- Destructor *
|
|
* *
|
|
* INPUT: *
|
|
* *
|
|
* OUTPUT: *
|
|
* *
|
|
* WARNINGS: *
|
|
* *
|
|
* HISTORY: *
|
|
*=============================================================================================*/
|
|
GeometryExportTaskClass::~GeometryExportTaskClass(void)
|
|
{
|
|
}
|
|
|
|
|
|
/***********************************************************************************************
|
|
* GeometryExportTaskClass::Get_Full_Name -- Composes the full name of this robj *
|
|
* *
|
|
* INPUT: *
|
|
* *
|
|
* OUTPUT: *
|
|
* *
|
|
* WARNINGS: *
|
|
* *
|
|
* HISTORY: *
|
|
* 10/24/2000 gth : Created. *
|
|
*=============================================================================================*/
|
|
void GeometryExportTaskClass::Get_Full_Name(char * buffer,int size)
|
|
{
|
|
char tmp[128];
|
|
memset(tmp,0,sizeof(tmp));
|
|
if (strlen(ContainerName) > 0) {
|
|
strcat(tmp,ContainerName);
|
|
strcat(tmp,".");
|
|
}
|
|
strcat(tmp,Name);
|
|
|
|
strncpy(buffer,tmp,size);
|
|
}
|
|
|
|
|
|
/***********************************************************************************************
|
|
* GeometryExportTaskClass::Create_Task -- virtual constructor for export tasks *
|
|
* *
|
|
* Virtual constructor for geometry export tasks. Will create the proper task *
|
|
* type depending on the W3D flag settings. *
|
|
* *
|
|
* INPUT: *
|
|
* *
|
|
* OUTPUT: *
|
|
* *
|
|
* WARNINGS: *
|
|
* *
|
|
* HISTORY: *
|
|
* 10/20/2000 gth : Created. *
|
|
*=============================================================================================*/
|
|
GeometryExportTaskClass *
|
|
GeometryExportTaskClass::Create_Task(INode * node,GeometryExportContextClass & context)
|
|
{
|
|
|
|
#if 0
|
|
/*
|
|
** (gth) Light export could be done in this way. HOWEVER, I am currently not inclined
|
|
** to add this feature to the max exporter. Instead, if we have the artists use the
|
|
** proxy feature to add lights to the terrain (define a named proxy), then we will be
|
|
** able to tweak the settings of the lights in the level editor.
|
|
*/
|
|
ObjectState os = node->EvalWorldState(m_ip->GetTime());
|
|
if(os.obj->SuperClassID() == LIGHT_CLASS_ID) {
|
|
GenLight* light = (GenLight*)os.obj;
|
|
struct LightState ls;
|
|
light->EvalLightState(currtime, FOREVER, &ls);
|
|
|
|
if (! light->GetUseLight())
|
|
return; // only export lights that are on for simplicity
|
|
}
|
|
#endif
|
|
|
|
if (!::Is_Geometry(node)) {
|
|
return NULL;
|
|
}
|
|
|
|
// NOTE: we *have* to check Is_Proxy first because it is tied to a naming convention
|
|
// rather than an explicit UI setting like the rest of the types.
|
|
if (::Is_Proxy(*node)) {
|
|
return new ProxyExportTaskClass(node,context);
|
|
}
|
|
|
|
if (::Is_Normal_Mesh(node) || Is_Camera_Aligned_Mesh(node) || Is_Camera_Oriented_Mesh(node) || Is_Skin(node)) {
|
|
return new MeshGeometryExportTaskClass(node,context);
|
|
}
|
|
|
|
if (::Is_Collision_AABox(node) || Is_Collision_OBBox(node)) {
|
|
return new CollisionBoxGeometryExportTaskClass(node,context);
|
|
}
|
|
|
|
if (::Is_Null_Object(node)) {
|
|
return new NullGeometryExportTaskClass(node,context);
|
|
}
|
|
|
|
if (::Is_Dazzle(node)) {
|
|
return new DazzleGeometryExportTaskClass(node,context);
|
|
}
|
|
|
|
if (::Is_Aggregate(node)) {
|
|
return new AggregateGeometryExportTaskClass(node,context);
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
|
|
/***********************************************************************************************
|
|
* GeometryExportTaskClass::Optimize_Geometry -- Optimizes the export tasks *
|
|
* *
|
|
* This function will attempt to split meshes so that they use only a single material and *
|
|
* then try to combine small meshes that use the same material. Export tasks may be *
|
|
* removed and new ones added. *
|
|
* *
|
|
* INPUT: *
|
|
* tasks - dynamic vector of export task pointers. Some tasks may be deleted, some added *
|
|
* *
|
|
* OUTPUT: *
|
|
* *
|
|
* WARNINGS: *
|
|
* *
|
|
* HISTORY: *
|
|
* 10/20/2000 gth : Created. *
|
|
*=============================================================================================*/
|
|
void GeometryExportTaskClass::Optimize_Geometry
|
|
(
|
|
DynamicVectorClass<GeometryExportTaskClass *> & tasks,
|
|
GeometryExportContextClass & context
|
|
)
|
|
{
|
|
int j=0,i=0;
|
|
|
|
/*
|
|
** Pass 1: Extract all mesh geometry tasks from the input task array.
|
|
** NOTE: We're not optimizing Skin meshes so we leave them in the task array.
|
|
*/
|
|
DynamicVectorClass<MeshGeometryExportTaskClass *> meshes;
|
|
while (i<tasks.Count()) {
|
|
if ( (tasks[i]->Get_Geometry_Type() == GeometryExportTaskClass::MESH) &&
|
|
(!Is_Skin(tasks[i]->Get_Object_Node())) )
|
|
{
|
|
/*
|
|
** Add to the mesh array, remove from the tasks array
|
|
*/
|
|
meshes.Add((MeshGeometryExportTaskClass *)(tasks[i]));
|
|
tasks.Delete(i);
|
|
} else {
|
|
|
|
/*
|
|
** Leave in the task array and move to the next one.
|
|
*/
|
|
i++;
|
|
}
|
|
}
|
|
|
|
/*
|
|
** Pass 2: Split all meshes which use more than one material
|
|
*/
|
|
DynamicVectorClass<MeshGeometryExportTaskClass *> simple_meshes;
|
|
while (meshes.Count() > 0) {
|
|
|
|
int cur_index = meshes.Count() - 1;
|
|
MeshGeometryExportTaskClass * cur_mesh = meshes[cur_index];
|
|
|
|
/*
|
|
** If this mesh already uses only one material, just transfer it to the simple_meshes array.
|
|
** Otherwise, have it split into new tasks, add them to the simple_meshes array, and delete this task.
|
|
*/
|
|
if (cur_mesh->Is_Single_Material()) {
|
|
simple_meshes.Add(cur_mesh);
|
|
} else {
|
|
cur_mesh->Split(simple_meshes);
|
|
delete cur_mesh;
|
|
}
|
|
meshes.Delete(cur_index);
|
|
}
|
|
|
|
/*
|
|
** Pass 3: Combine meshes which satisfy the following
|
|
** - They use the same (single) material
|
|
** - They have fewer than 'x' polygons
|
|
** - They are 'close' to each other
|
|
*/
|
|
i=0;
|
|
while (i < simple_meshes.Count()) {
|
|
|
|
if (simple_meshes[i]->Can_Combine()) {
|
|
|
|
j=i+1;
|
|
while (j < simple_meshes.Count()) {
|
|
if (simple_meshes[i]->Can_Combine_With(simple_meshes[j])) {
|
|
|
|
/*
|
|
** Add mesh 'j' into mesh 'i', delete its task.
|
|
*/
|
|
simple_meshes[i]->Combine_Mesh(simple_meshes[j]);
|
|
delete simple_meshes[j];
|
|
simple_meshes.Delete(j);
|
|
|
|
/*
|
|
** If we've just exceeded the max poly count, move to the next mesh
|
|
*/
|
|
if (simple_meshes[i]->Can_Combine() == false) {
|
|
j = simple_meshes.Count();
|
|
}
|
|
|
|
} else {
|
|
|
|
/*
|
|
** Otherwise, move to the next mesh
|
|
*/
|
|
j++;
|
|
}
|
|
}
|
|
}
|
|
i++;
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
** Generate names for each of the meshes that were created
|
|
*/
|
|
for (i=0; i<simple_meshes.Count(); i++) {
|
|
// if (simple_meshes[i]->Is_Name_Dirty()) {
|
|
simple_meshes[i]->Generate_Name("MESH",i,context);
|
|
// }
|
|
}
|
|
|
|
/*
|
|
** Finally, transfer all of the optimized tasks into the big task array
|
|
*/
|
|
for (i=0; i<simple_meshes.Count(); i++) {
|
|
tasks.Add(simple_meshes[i]);
|
|
}
|
|
simple_meshes.Delete_All();
|
|
|
|
}
|
|
|
|
|
|
/***********************************************************************************************
|
|
* GeometryExportTaskClass::Generate_Name -- create a name for this object *
|
|
* *
|
|
* During optimization, we can generate new meshes which need unique names. *
|
|
* *
|
|
* INPUT: *
|
|
* index - will be used to create the name, don't re-use the same index within an LOD *
|
|
* context - geometry export context *
|
|
* *
|
|
* OUTPUT: *
|
|
* *
|
|
* WARNINGS: *
|
|
* *
|
|
* HISTORY: *
|
|
* 10/23/2000 gth : Created. *
|
|
*=============================================================================================*/
|
|
void GeometryExportTaskClass::Generate_Name(char * root,int index,GeometryExportContextClass & context)
|
|
{
|
|
/*
|
|
** Check if the original had a Renegade "building prefix" in its name. Building meshes
|
|
** are named with a 2-3 letter user specified prefix followed by either '^' or '#'. We
|
|
** have to maintain this prefix on the optimized meshes...
|
|
*/
|
|
char prefix[5];
|
|
strncpy(prefix,Name,sizeof(prefix));
|
|
prefix[4] = 0;
|
|
|
|
char * interior_prefix = strchr(prefix,'^');
|
|
char * exterior_prefix = strchr(prefix,'#');
|
|
|
|
memset(Name,0,sizeof(Name));
|
|
if (interior_prefix != NULL) {
|
|
strncpy(Name,prefix,(int)(interior_prefix - prefix) + 1);
|
|
} else if (exterior_prefix != NULL) {
|
|
strncpy(Name,prefix,(int)(exterior_prefix - prefix) + 1);
|
|
}
|
|
|
|
/*
|
|
** Append "root"{index} to the name
|
|
*/
|
|
strcat(Name,root);
|
|
strcat(Name,"{");
|
|
sprintf(Name + strlen(Name),"%04d",index);
|
|
strcat(Name,"}");
|
|
|
|
/*
|
|
** Append the LOD index so the generated name doesn't collide with meshes in the same
|
|
** object but in other LOD's
|
|
*/
|
|
sprintf(Name + strlen(Name),"%c",'a'+Get_Lod_Level(context.Origin));
|
|
}
|
|
|
|
|
|
/***********************************************************************************************
|
|
* MeshGeometryExportTaskClass::Update_Cached_Data -- updates the cached material pointer, etc *
|
|
* *
|
|
* This should be called after the meshdata changes *
|
|
* *
|
|
* INPUT: *
|
|
* *
|
|
* OUTPUT: *
|
|
* *
|
|
* WARNINGS: *
|
|
* *
|
|
* HISTORY: *
|
|
* 10/23/2000 gth : Created. *
|
|
*=============================================================================================*/
|
|
void MeshGeometryExportTaskClass::Update_Cached_Data(void)
|
|
{
|
|
SingleMtl = NULL;
|
|
Mtl * nodemtl = Node->GetMtl();
|
|
|
|
/*
|
|
** Set the SingleMtl pointer if this mesh uses only one material (again, even if its in a Multi-Sub)
|
|
*/
|
|
if (nodemtl == NULL) {
|
|
|
|
SingleMtl = NULL;
|
|
|
|
} else if (nodemtl->NumSubMtls() <= 1) {
|
|
|
|
SingleMtl = nodemtl;
|
|
|
|
} else {
|
|
|
|
int mat_index;
|
|
int face_index;
|
|
int sub_mtl_count = nodemtl->NumSubMtls();
|
|
bool * sub_mtl_flags = new bool[sub_mtl_count];
|
|
|
|
/*
|
|
** Initialize each sub-material flag to false (indicates that the material is un-used)
|
|
*/
|
|
for (mat_index=0; mat_index<sub_mtl_count; mat_index++) {
|
|
sub_mtl_flags[mat_index] = false;
|
|
}
|
|
|
|
/*
|
|
** Set a true for each material actually referenced by the mesh
|
|
*/
|
|
for (face_index=0; face_index<MeshData.getNumFaces(); face_index++) {
|
|
int max_mat_index = MeshData.faces[face_index].getMatID();
|
|
int mat_index = (max_mat_index % sub_mtl_count);
|
|
sub_mtl_flags[mat_index] = true;
|
|
}
|
|
|
|
/*
|
|
** Loop over the used materials counting how many 'true's we see
|
|
*/
|
|
int mat_count = 0;
|
|
for (mat_index=0; mat_index<sub_mtl_count; mat_index++) {
|
|
if (sub_mtl_flags[mat_index]) {
|
|
SingleMtl = nodemtl->GetSubMtl(mat_index);
|
|
mat_count++;
|
|
}
|
|
}
|
|
|
|
if (mat_count > 1) {
|
|
SingleMtl = NULL;
|
|
}
|
|
}
|
|
|
|
/*
|
|
** Update the bounding box
|
|
*/
|
|
Point3 boxmin(0,0,0);
|
|
Point3 boxmax(0,0,0);
|
|
|
|
if (MeshData.numVerts > 0) {
|
|
boxmin = MeshData.verts[1];
|
|
boxmax = MeshData.verts[0];
|
|
for (int i=0; i<MeshData.numVerts; i++) {
|
|
boxmin.x = MIN(MeshData.verts[i].x,boxmin.x);
|
|
boxmin.y = MIN(MeshData.verts[i].y,boxmin.y);
|
|
boxmin.z = MIN(MeshData.verts[i].z,boxmin.z);
|
|
|
|
boxmax.x = MAX(MeshData.verts[i].x,boxmax.x);
|
|
boxmax.y = MAX(MeshData.verts[i].y,boxmax.y);
|
|
boxmax.z = MAX(MeshData.verts[i].z,boxmax.z);
|
|
}
|
|
}
|
|
|
|
boxmax += Point3(0.1f,0.1f,0.1f);
|
|
boxmin -= Point3(0.1f,0.1f,0.1f);
|
|
|
|
BoxCenter = (boxmax + boxmin) * 0.5f;
|
|
BoxExtent = (boxmax - boxmin) * 0.5f;
|
|
|
|
WorldBounds = Box3(boxmin,boxmax) * Node->GetObjectTM(CurTime);
|
|
}
|
|
|
|
|
|
/***********************************************************************************************
|
|
* MeshGeometryExportTaskClass::Is_Single_Material -- Tests if this mesh uses a single materia *
|
|
* *
|
|
* INPUT: *
|
|
* *
|
|
* OUTPUT: *
|
|
* *
|
|
* WARNINGS: *
|
|
* *
|
|
* HISTORY: *
|
|
* 10/20/2000 gth : Created. *
|
|
*=============================================================================================*/
|
|
bool MeshGeometryExportTaskClass::Is_Single_Material(void)
|
|
{
|
|
return ((SingleMtl != NULL) || (Node->GetMtl() == NULL));
|
|
}
|
|
|
|
|
|
/***********************************************************************************************
|
|
* MeshGeometryExportTaskClass::Get_Single_Material -- returns pointer to the material *
|
|
* *
|
|
* INPUT: *
|
|
* *
|
|
* OUTPUT: *
|
|
* *
|
|
* WARNINGS: *
|
|
* *
|
|
* HISTORY: *
|
|
* 10/23/2000 gth : Created. *
|
|
*=============================================================================================*/
|
|
Mtl* MeshGeometryExportTaskClass::Get_Single_Material(void)
|
|
{
|
|
return SingleMtl;
|
|
}
|
|
|
|
/***********************************************************************************************
|
|
* MeshGeometryExportTaskClass::Split -- Splits into single material meshes *
|
|
* *
|
|
* This function will create new export tasks and add them to the supplied array. Each of *
|
|
* these will be single-material meshes. *
|
|
* *
|
|
* INPUT: *
|
|
* *
|
|
* OUTPUT: *
|
|
* *
|
|
* WARNINGS: *
|
|
* *
|
|
* HISTORY: *
|
|
* 10/20/2000 gth : Created. *
|
|
*=============================================================================================*/
|
|
void MeshGeometryExportTaskClass::Split(DynamicVectorClass<MeshGeometryExportTaskClass *> & simple_meshes)
|
|
{
|
|
assert(!Is_Single_Material());
|
|
|
|
Mtl * nodemtl = Node->GetMtl();
|
|
|
|
int mat_index;
|
|
int face_index;
|
|
int sub_mtl_count = nodemtl->NumSubMtls();
|
|
bool * sub_mtl_flags = new bool[sub_mtl_count];
|
|
|
|
/*
|
|
** Initialize each sub-material flag to false (indicates that the material is un-used)
|
|
*/
|
|
for (mat_index=0; mat_index<sub_mtl_count; mat_index++) {
|
|
sub_mtl_flags[mat_index] = false;
|
|
}
|
|
|
|
/*
|
|
** Set a true for each material actually referenced by the mesh
|
|
*/
|
|
for (face_index=0; face_index<MeshData.getNumFaces(); face_index++) {
|
|
int max_mat_index = MeshData.faces[face_index].getMatID();
|
|
int mat_index = (max_mat_index % sub_mtl_count);
|
|
sub_mtl_flags[mat_index] = true;
|
|
}
|
|
|
|
/*
|
|
** Loop over the used materials, creating a new MeshGeometryExportTaskClass for each one
|
|
*/
|
|
for (mat_index=0; mat_index<sub_mtl_count; mat_index++) {
|
|
if (sub_mtl_flags[mat_index]) {
|
|
|
|
MeshGeometryExportTaskClass * new_task = new MeshGeometryExportTaskClass(*this);
|
|
new_task->Reduce_To_Single_Material(mat_index);
|
|
new_task->Set_Name_Dirty(true);
|
|
simple_meshes.Add(new_task);
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/***********************************************************************************************
|
|
* MeshGeometryExportTaskClass::Reduce_To_Single_Material -- deletes polys *
|
|
* *
|
|
* This function deletes all polys (and subsequent un-used vertices) except for the ones *
|
|
* that use the specified material. *
|
|
* *
|
|
* INPUT: *
|
|
* mat_id - only faces using this material id will remain in the mesh. *
|
|
* *
|
|
* OUTPUT: *
|
|
* *
|
|
* WARNINGS: *
|
|
* *
|
|
* HISTORY: *
|
|
* 10/20/2000 gth : Created. *
|
|
*=============================================================================================*/
|
|
void MeshGeometryExportTaskClass::Reduce_To_Single_Material(int mat_id)
|
|
{
|
|
int sub_mtl_count = Node->GetMtl()->NumSubMtls();
|
|
|
|
BitArray faces_to_delete(MeshData.getNumFaces());
|
|
BitArray verts_to_delete(MeshData.getNumVerts());
|
|
|
|
faces_to_delete.ClearAll();
|
|
verts_to_delete.ClearAll();
|
|
|
|
for (int i=0; i<MeshData.getNumFaces(); i++) {
|
|
if ((MeshData.faces[i].getMatID() % sub_mtl_count) != mat_id) {
|
|
faces_to_delete.Set(i,true);
|
|
}
|
|
}
|
|
|
|
MeshData.DeleteFaceSet(faces_to_delete,&verts_to_delete);
|
|
MeshData.DeleteVertSet(verts_to_delete);
|
|
|
|
Update_Cached_Data();
|
|
}
|
|
|
|
|
|
/***********************************************************************************************
|
|
* MeshGeometryExportTaskClass::Can_Combine -- can this mesh combine with anything *
|
|
* *
|
|
* This will return false if the mesh has multiple materials, too many polys, etc. *
|
|
* *
|
|
* INPUT: *
|
|
* *
|
|
* OUTPUT: *
|
|
* *
|
|
* WARNINGS: *
|
|
* *
|
|
* HISTORY: *
|
|
* 10/24/2000 gth : Created. *
|
|
*=============================================================================================*/
|
|
bool MeshGeometryExportTaskClass::Can_Combine(void)
|
|
{
|
|
/*
|
|
** This mesh can't combine at all if:
|
|
** - it has multiple materials
|
|
** - its polygon count is too high
|
|
** - (Renegade) VIS is enabled (don't want to create really wierd shaped vis-sectors...)
|
|
*/
|
|
if (!Is_Single_Material()) {
|
|
return false;
|
|
}
|
|
if (MeshData.numFaces > OPTIMIZATION_FACECOUNT_LIMIT) {
|
|
return false;
|
|
}
|
|
if (ExportOptions.Is_Vis_Collision_Enabled()) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
/***********************************************************************************************
|
|
* MeshGeometryExportTaskClass::Can_Combine_With -- can this mesh be combined with the given m *
|
|
* *
|
|
* INPUT: *
|
|
* *
|
|
* OUTPUT: *
|
|
* *
|
|
* WARNINGS: *
|
|
* *
|
|
* HISTORY: *
|
|
* 10/24/2000 gth : Created. *
|
|
*=============================================================================================*/
|
|
bool MeshGeometryExportTaskClass::Can_Combine_With(MeshGeometryExportTaskClass * other_mesh)
|
|
{
|
|
/*
|
|
** Does the mesh attach to the same W3D bone that we do?
|
|
*/
|
|
if (other_mesh->BoneIndex != BoneIndex) {
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
** Does the mesh use the same (single) material that we do?
|
|
*/
|
|
Mtl * other_mtl = other_mesh->Get_Single_Material();
|
|
if (other_mtl == NULL) {
|
|
return false;
|
|
}
|
|
|
|
Mtl * my_mtl = Get_Single_Material();
|
|
if (my_mtl != other_mtl) {
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
** Are its relevant W3D options the same as ours?
|
|
*/
|
|
if (ExportOptions.Geometry_Options_Match(other_mesh->ExportOptions)) {
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
** Would our combined polygon count be reasonable
|
|
*/
|
|
if (MeshData.numFaces + other_mesh->MeshData.numFaces > OPTIMIZATION_FACECOUNT_LIMIT) {
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
** Is the other mesh near me?
|
|
*/
|
|
Point3 my_center = Node->GetObjectTM(CurTime) * BoxCenter;
|
|
Point3 other_center = other_mesh->Node->GetObjectTM(CurTime) * BoxCenter;
|
|
float dist = ::FLength(my_center - other_center);
|
|
if (dist > OPTIMIZATION_COMBINING_DISTANCE) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
/***********************************************************************************************
|
|
* MeshGeometryExportTaskClass::Combine_Mesh -- Add the given mesh into this mesh *
|
|
* *
|
|
* INPUT: *
|
|
* *
|
|
* OUTPUT: *
|
|
* *
|
|
* WARNINGS: *
|
|
* *
|
|
* HISTORY: *
|
|
* 10/24/2000 gth : Created. *
|
|
*=============================================================================================*/
|
|
void MeshGeometryExportTaskClass::Combine_Mesh(MeshGeometryExportTaskClass * other_mesh)
|
|
{
|
|
/*
|
|
** Compute the transform from other_mesh's coordinate system to ours so that
|
|
** its polygons can be combined with ours (by calling CombineMeshes)
|
|
*/
|
|
Matrix3 our_tm = Node->GetObjectTM(CurTime);
|
|
Matrix3 his_tm = other_mesh->Node->GetObjectTM(CurTime);
|
|
Matrix3 tm = Inverse(our_tm) * his_tm;
|
|
|
|
/*
|
|
** Store our current material index
|
|
*/
|
|
int matid = MeshData.faces[0].getMatID();
|
|
if (Node->GetMtl()->NumSubMtls() > 1) {
|
|
matid = matid % Node->GetMtl()->NumSubMtls();
|
|
}
|
|
|
|
/*
|
|
** Combine the meshes
|
|
*/
|
|
Mesh new_mesh;
|
|
::CombineMeshes(new_mesh,MeshData,other_mesh->MeshData,&our_tm,&his_tm,0);
|
|
MeshData = new_mesh;
|
|
|
|
/*
|
|
** Set all material ID's
|
|
*/
|
|
for (int i=0; i<MeshData.numFaces; i++) {
|
|
MeshData.faces[i].setMatID(matid);
|
|
}
|
|
}
|
|
|
|
Point3 MeshGeometryExportTaskClass::Get_Shared_Vertex_Normal(const Point3 & world_pos,int smgroup)
|
|
{
|
|
const float EPSILON = 0.001f;
|
|
Point3 normal(0,0,0);
|
|
|
|
/*
|
|
** Does the bounding box for this node even contain the point
|
|
** we are looking for?
|
|
*/
|
|
if (WorldBounds.Contains(world_pos) != 0) {
|
|
|
|
/*
|
|
** Transform the query point into object space
|
|
*/
|
|
Matrix3 tm = Node->GetObjectTM(CurTime);
|
|
Point3 obj_pos = world_pos * Inverse(tm);
|
|
|
|
/*
|
|
** Loop through all the faces in this mesh and find out which ones
|
|
** share the same smoothing group as the vertex we are looking for.
|
|
*/
|
|
for (int face_index = 0; face_index < MeshData.numFaces; face_index ++) {
|
|
Face &maxface = MeshData.faces[face_index];
|
|
int face_smgroup = maxface.getSmGroup();
|
|
if ((face_smgroup & smgroup) || (face_smgroup == smgroup)) {
|
|
|
|
/*
|
|
** Find out if any of the verticies of this face share the
|
|
** same space as the vertex we are looking for.
|
|
*/
|
|
bool found = false;
|
|
for (int vert_index = 0; (vert_index < 3) && !found; vert_index ++) {
|
|
int max_vert_index = maxface.v[vert_index];
|
|
Point3 delta = obj_pos - MeshData.verts[max_vert_index];
|
|
if ((fabs (delta.x) < EPSILON) &&
|
|
(fabs (delta.y) < EPSILON) &&
|
|
(fabs (delta.z) < EPSILON))
|
|
{
|
|
|
|
/*
|
|
** Compute the normal for this face
|
|
*/
|
|
Point3 v0 = MeshData.verts[maxface.v[0]];
|
|
Point3 v1 = MeshData.verts[maxface.v[1]];
|
|
Point3 v2 = MeshData.verts[maxface.v[2]];
|
|
Point3 face_normal = (v1-v0)^(v2-v1);
|
|
face_normal = ::Normalize(face_normal);
|
|
|
|
/*
|
|
** Add this face normal to the sum
|
|
*/
|
|
normal.x += face_normal.x;
|
|
normal.y += face_normal.y;
|
|
normal.z += face_normal.z;
|
|
|
|
/*
|
|
** Done with this face, look for more
|
|
*/
|
|
found = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
** Transform the "normal" to world space. Note that this vector isn't
|
|
** normalized because we are basically summing the contributions of each
|
|
** face in each mesh which shares this normal. The final normal
|
|
** will be normalized in the MeshBuilderClass.
|
|
*/
|
|
tm.NoTrans();
|
|
normal = tm.PointTransform(normal);
|
|
}
|
|
|
|
return normal;
|
|
}
|