geometryexporttask.cpp 46 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187
  1. /*
  2. ** Command & Conquer Renegade(tm)
  3. ** Copyright 2025 Electronic Arts Inc.
  4. **
  5. ** This program is free software: you can redistribute it and/or modify
  6. ** it under the terms of the GNU General Public License as published by
  7. ** the Free Software Foundation, either version 3 of the License, or
  8. ** (at your option) any later version.
  9. **
  10. ** This program is distributed in the hope that it will be useful,
  11. ** but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. ** GNU General Public License for more details.
  14. **
  15. ** You should have received a copy of the GNU General Public License
  16. ** along with this program. If not, see <http://www.gnu.org/licenses/>.
  17. */
  18. /***********************************************************************************************
  19. *** 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 ***
  20. ***********************************************************************************************
  21. * *
  22. * Project Name : Max2W3D *
  23. * *
  24. * $Archive:: /Commando/Code/Tools/max2w3d/geometryexporttask.cpp $*
  25. * *
  26. * Original Author:: Greg Hjelstrom *
  27. * *
  28. * $Author:: Greg_h $*
  29. * *
  30. * $Modtime:: 3/14/02 3:27p $*
  31. * *
  32. * $Revision:: 8 $*
  33. * *
  34. *---------------------------------------------------------------------------------------------*
  35. * Functions: *
  36. * GeometryExportTaskClass::GeometryExportTaskClass -- Constructor *
  37. * GeometryExportTaskClass::GeometryExportTaskClass -- Copy Constructor *
  38. * GeometryExportTaskClass::~GeometryExportTaskClass -- Destructor *
  39. * GeometryExportTaskClass::Get_Full_Name -- Composes the full name of this robj *
  40. * GeometryExportTaskClass::Create_Task -- virtual constructor for export tasks *
  41. * GeometryExportTaskClass::Optimize_Geometry -- Optimizes the export tasks *
  42. * GeometryExportTaskClass::Generate_Unique_Name -- create a unique name for this object *
  43. * MeshGeometryExportTaskClass::Is_Single_Material -- Tests if this mesh uses a single mater *
  44. * MeshGeometryExportTaskClass::Get_Single_Material -- returns pointer to the material *
  45. * MeshGeometryExportTaskClass::Cache_Single_Material -- updates the cached material pointer *
  46. * MeshGeometryExportTaskClass::Split -- Splits into single material meshes *
  47. * MeshGeometryExportTaskClass::Reduce_To_Single_Material -- deletes polys *
  48. * MeshGeometryExportTaskClass::Can_Combine -- can this mesh combine with anything *
  49. * MeshGeometryExportTaskClass::Can_Combine_With -- can this mesh be combined with the given *
  50. * MeshGeometryExportTaskClass::Combine_Mesh -- Add the given mesh into this mesh *
  51. * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
  52. #include "geometryexporttask.h"
  53. #include "geometryexportcontext.h"
  54. #include "util.h"
  55. #include "w3dutil.h"
  56. #include "w3dappdata.h"
  57. #include "hiersave.h"
  58. #include "maxworldinfo.h"
  59. #include "meshsave.h"
  60. #include "colboxsave.h"
  61. #include "dazzlesave.h"
  62. #include <bitarray.h>
  63. const int OPTIMIZATION_FACECOUNT_LIMIT = 256; // TODO: what should this number be...
  64. const float OPTIMIZATION_COMBINING_DISTANCE = 20.0f; // TODO: need a smarter method for combining...
  65. /**
  66. ** MeshGeometryExportTaskClass
  67. ** Export task for INodes which are to generate W3D meshes
  68. */
  69. class MeshGeometryExportTaskClass : public GeometryExportTaskClass
  70. {
  71. public:
  72. MeshGeometryExportTaskClass(INode * node,GeometryExportContextClass & context) :
  73. GeometryExportTaskClass(node,context),
  74. NameDirty(false),
  75. SingleMtl(NULL)
  76. {
  77. /*
  78. ** Copy the export options
  79. */
  80. ExportOptions = *(W3DAppData2Struct::Get_App_Data(Node));
  81. /*
  82. ** Copy the mesh
  83. */
  84. Object * obj = Node->EvalWorldState(CurTime).obj;
  85. TriObject * tri = (TriObject *)obj->ConvertToType(CurTime, triObjectClassID);
  86. MeshData = tri->mesh;
  87. /*
  88. ** Store a pointer to the material if this mesh uses only one material (even inside a Multi-Sub)
  89. */
  90. Update_Cached_Data();
  91. }
  92. MeshGeometryExportTaskClass(const MeshGeometryExportTaskClass & that) :
  93. GeometryExportTaskClass(that),
  94. MeshData(that.MeshData),
  95. ExportOptions(that.ExportOptions),
  96. NameDirty(false)
  97. {
  98. }
  99. virtual ~MeshGeometryExportTaskClass(void)
  100. {
  101. }
  102. virtual void Export_Geometry(GeometryExportContextClass & context)
  103. {
  104. /*
  105. ** Create the mesh
  106. */
  107. context.WorldInfo.Set_Current_Task(this);
  108. context.WorldInfo.Set_Export_Transform(ExportSpace);
  109. MeshSaveClass * mesh = new MeshSaveClass( Name,
  110. ContainerName,
  111. Node,
  112. &MeshData,
  113. ExportSpace,
  114. ExportOptions,
  115. context.HTree,
  116. context.CurTime,
  117. *context.ProgressMeter,
  118. &context.WorldInfo );
  119. /*
  120. ** Export It
  121. */
  122. mesh->Write_To_File(context.CSave,!context.Options.DisableExportAABTrees);
  123. delete mesh;
  124. context.ProgressMeter->Add_Increment();
  125. };
  126. /*
  127. ** Naming. During the optimization phase, sometimes new meshes are created and require
  128. ** new unique names. These meshes are flagged and then new names are generated prior
  129. ** to exporting.
  130. */
  131. bool Is_Name_Dirty(void) { return NameDirty; }
  132. void Set_Name_Dirty(bool onoff) { NameDirty = onoff; }
  133. /*
  134. ** Vertex Normal smoothing support!
  135. */
  136. virtual Point3 Get_Shared_Vertex_Normal(const Point3 & pos,int smgroup);
  137. /*
  138. ** Optimization functions
  139. */
  140. bool Is_Single_Material(void);
  141. Mtl * Get_Single_Material(void);
  142. void Split(DynamicVectorClass<MeshGeometryExportTaskClass *> & simple_meshes);
  143. void Reduce_To_Single_Material(int mat_id);
  144. bool Can_Combine(void);
  145. bool Can_Combine_With(MeshGeometryExportTaskClass * other_mesh);
  146. void Combine_Mesh(MeshGeometryExportTaskClass * other_mesh);
  147. protected:
  148. virtual int Get_Geometry_Type(void) { return MESH; }
  149. void Update_Cached_Data(void);
  150. Mesh MeshData; // Copy of the mesh data to be exported.
  151. W3DAppData2Struct ExportOptions; // Copy of the export options in case we want to change them during optimization
  152. bool NameDirty;
  153. // Cached Data about the Node/Mesh. Updated by calling Update_Cached_Data whenever the mesh changes.
  154. Mtl * SingleMtl; // Pointer to the single material (if the mesh uses only one, even in a multi-mtl)
  155. Point3 BoxCenter; // Center of the bounding box (in object space)
  156. Point3 BoxExtent; // Extent of the bounding box (in object space)
  157. Box3 WorldBounds; // World-space bounding box
  158. };
  159. /**
  160. ** CollisionBoxGeometryExportTaskClass
  161. ** Export task for INodes which are to generate W3D AABoxes or OBBoxes
  162. */
  163. class CollisionBoxGeometryExportTaskClass : public GeometryExportTaskClass
  164. {
  165. public:
  166. CollisionBoxGeometryExportTaskClass(INode * node,GeometryExportContextClass & context) :
  167. GeometryExportTaskClass(node,context)
  168. {
  169. }
  170. virtual void Export_Geometry(GeometryExportContextClass & context)
  171. {
  172. /*
  173. ** Create the collision box
  174. */
  175. CollisionBoxSaveClass * colbox = new CollisionBoxSaveClass( Name,
  176. ContainerName,
  177. Node,
  178. ExportSpace,
  179. context.CurTime,
  180. *context.ProgressMeter);
  181. /*
  182. ** Export it
  183. */
  184. colbox->Write_To_File(context.CSave);
  185. delete colbox;
  186. context.ProgressMeter->Add_Increment();
  187. };
  188. protected:
  189. virtual int Get_Geometry_Type(void) { return COLLISIONBOX; }
  190. };
  191. /**
  192. ** DazzleGeometryExportTaskClass
  193. ** Export task for INodes which are to generate W3D Dazzle objects
  194. */
  195. class DazzleGeometryExportTaskClass : public GeometryExportTaskClass
  196. {
  197. public:
  198. DazzleGeometryExportTaskClass(INode * node,GeometryExportContextClass & context) :
  199. GeometryExportTaskClass(node,context)
  200. {
  201. }
  202. virtual void Export_Geometry(GeometryExportContextClass & context)
  203. {
  204. /*
  205. ** Create the dazzle object
  206. */
  207. DazzleSaveClass * dazzle = new DazzleSaveClass( Name,
  208. ContainerName,
  209. Node,
  210. ExportSpace,
  211. context.CurTime,
  212. *context.ProgressMeter);
  213. /*
  214. ** Export it.
  215. */
  216. dazzle->Write_To_File(context.CSave);
  217. delete dazzle;
  218. context.ProgressMeter->Add_Increment();
  219. };
  220. protected:
  221. virtual int Get_Geometry_Type(void) { return DAZZLE; }
  222. };
  223. /**
  224. ** NullGeometryExportTaskClass
  225. ** Export task for INodes which are to generate W3D NULL objects. Note that this
  226. ** does not do anything in the Export_Geometry call, these only create entries in
  227. ** any Hierarhcical model or collection object being exported.
  228. */
  229. class NullGeometryExportTaskClass : public GeometryExportTaskClass
  230. {
  231. public:
  232. NullGeometryExportTaskClass(INode * node,GeometryExportContextClass & context) :
  233. GeometryExportTaskClass(node,context)
  234. {
  235. memset(ContainerName,0,sizeof(ContainerName));
  236. memset(Name,0,sizeof(Name));
  237. strcpy(Name,"NULL");
  238. }
  239. virtual void Export_Geometry(GeometryExportContextClass & context)
  240. {
  241. context.ProgressMeter->Add_Increment();
  242. };
  243. protected:
  244. virtual int Get_Geometry_Type(void) { return NULLOBJ; }
  245. };
  246. /**
  247. ** AggregateGeometryExportTaskClass
  248. ** Export task for INodes which are to generate W3D Aggregates. These are nodes
  249. ** that refer to some external W3D object. This export task doesn't export any
  250. ** geometry (similer to the Null export task) and it clears its container name
  251. ** because the object to be attached is not a sub-object of the model we are
  252. ** currently exporting.
  253. */
  254. class AggregateGeometryExportTaskClass : public GeometryExportTaskClass
  255. {
  256. public:
  257. AggregateGeometryExportTaskClass(INode * node,GeometryExportContextClass & context) :
  258. GeometryExportTaskClass(node,context)
  259. {
  260. memset(ContainerName,0,sizeof(ContainerName));
  261. }
  262. virtual void Export_Geometry(GeometryExportContextClass & context)
  263. {
  264. context.ProgressMeter->Add_Increment();
  265. };
  266. virtual bool Is_Aggregate(void)
  267. {
  268. return true;
  269. }
  270. protected:
  271. virtual int Get_Geometry_Type(void) { return AGGREGATE; }
  272. };
  273. /**
  274. ** ProxyExportTaskClass
  275. ** These are used by the Renegade Level Editor to cause game objects to be
  276. ** instantiated at the specified transform. Like aggregates they have to
  277. ** be handled specially and therefore have the Is_Proxy member function devoted
  278. ** solely to them :-) Hopefully we don't have any more geometry types which
  279. ** have to be handled specially or this is going to get messy again.
  280. */
  281. class ProxyExportTaskClass : public GeometryExportTaskClass
  282. {
  283. public:
  284. ProxyExportTaskClass(INode * node,GeometryExportContextClass & context) :
  285. GeometryExportTaskClass(node,context)
  286. {
  287. /*
  288. ** clear the container name
  289. */
  290. memset(ContainerName,0,sizeof(ContainerName));
  291. /*
  292. ** strip the trailing ~
  293. */
  294. char *tilda = ::strchr(Name, '~');
  295. memset(tilda,0,sizeof(Name) - ((int)tilda - (int)Name));
  296. }
  297. virtual void Export_Geometry(GeometryExportContextClass & context)
  298. {
  299. context.ProgressMeter->Add_Increment();
  300. };
  301. virtual bool Is_Proxy(void) { return true; }
  302. protected:
  303. virtual int Get_Geometry_Type(void) { return PROXY; }
  304. };
  305. /***********************************************************************************************
  306. **
  307. ** Implementations
  308. **
  309. ***********************************************************************************************/
  310. /***********************************************************************************************
  311. * GeometryExportTaskClass::GeometryExportTaskClass -- Constructor *
  312. * *
  313. * INPUT: *
  314. * *
  315. * OUTPUT: *
  316. * *
  317. * WARNINGS: *
  318. * *
  319. * HISTORY: *
  320. * 10/20/2000 gth : Created. *
  321. *=============================================================================================*/
  322. GeometryExportTaskClass::GeometryExportTaskClass(INode * node,GeometryExportContextClass & context) :
  323. BoneIndex(0),
  324. ExportSpace(1),
  325. CurTime(context.CurTime),
  326. Node(node)
  327. {
  328. /*
  329. ** Set up the names
  330. */
  331. Set_W3D_Name(Name,Node->GetName());
  332. Append_Lod_Character(Name,Get_Lod_Level(context.Origin),context.OriginList);
  333. Set_W3D_Name(ContainerName,context.ModelName);
  334. /*
  335. ** Set up the bone index and export coordinate system.
  336. */
  337. if (context.HTree != NULL) {
  338. if (!Is_Skin(node)) {
  339. context.HTree->Get_Export_Coordinate_System(Node,&BoneIndex,NULL,&ExportSpace);
  340. } else {
  341. BoneIndex = 0;
  342. ExportSpace = context.OriginTransform;
  343. }
  344. }
  345. }
  346. /***********************************************************************************************
  347. * GeometryExportTaskClass::GeometryExportTaskClass -- Copy Constructor *
  348. * *
  349. * INPUT: *
  350. * *
  351. * OUTPUT: *
  352. * *
  353. * WARNINGS: *
  354. * *
  355. * HISTORY: *
  356. * 10/20/2000 gth : Created. *
  357. *=============================================================================================*/
  358. GeometryExportTaskClass::GeometryExportTaskClass(const GeometryExportTaskClass & that) :
  359. BoneIndex(that.BoneIndex),
  360. ExportSpace(that.ExportSpace),
  361. CurTime(that.CurTime),
  362. Node(that.Node)
  363. {
  364. Set_W3D_Name(Name,that.Name);
  365. Set_W3D_Name(ContainerName,that.ContainerName);
  366. }
  367. /***********************************************************************************************
  368. * GeometryExportTaskClass::~GeometryExportTaskClass -- Destructor *
  369. * *
  370. * INPUT: *
  371. * *
  372. * OUTPUT: *
  373. * *
  374. * WARNINGS: *
  375. * *
  376. * HISTORY: *
  377. *=============================================================================================*/
  378. GeometryExportTaskClass::~GeometryExportTaskClass(void)
  379. {
  380. }
  381. /***********************************************************************************************
  382. * GeometryExportTaskClass::Get_Full_Name -- Composes the full name of this robj *
  383. * *
  384. * INPUT: *
  385. * *
  386. * OUTPUT: *
  387. * *
  388. * WARNINGS: *
  389. * *
  390. * HISTORY: *
  391. * 10/24/2000 gth : Created. *
  392. *=============================================================================================*/
  393. void GeometryExportTaskClass::Get_Full_Name(char * buffer,int size)
  394. {
  395. char tmp[128];
  396. memset(tmp,0,sizeof(tmp));
  397. if (strlen(ContainerName) > 0) {
  398. strcat(tmp,ContainerName);
  399. strcat(tmp,".");
  400. }
  401. strcat(tmp,Name);
  402. strncpy(buffer,tmp,size);
  403. }
  404. /***********************************************************************************************
  405. * GeometryExportTaskClass::Create_Task -- virtual constructor for export tasks *
  406. * *
  407. * Virtual constructor for geometry export tasks. Will create the proper task *
  408. * type depending on the W3D flag settings. *
  409. * *
  410. * INPUT: *
  411. * *
  412. * OUTPUT: *
  413. * *
  414. * WARNINGS: *
  415. * *
  416. * HISTORY: *
  417. * 10/20/2000 gth : Created. *
  418. *=============================================================================================*/
  419. GeometryExportTaskClass *
  420. GeometryExportTaskClass::Create_Task(INode * node,GeometryExportContextClass & context)
  421. {
  422. #if 0
  423. /*
  424. ** (gth) Light export could be done in this way. HOWEVER, I am currently not inclined
  425. ** to add this feature to the max exporter. Instead, if we have the artists use the
  426. ** proxy feature to add lights to the terrain (define a named proxy), then we will be
  427. ** able to tweak the settings of the lights in the level editor.
  428. */
  429. ObjectState os = node->EvalWorldState(m_ip->GetTime());
  430. if(os.obj->SuperClassID() == LIGHT_CLASS_ID) {
  431. GenLight* light = (GenLight*)os.obj;
  432. struct LightState ls;
  433. light->EvalLightState(currtime, FOREVER, &ls);
  434. if (! light->GetUseLight())
  435. return; // only export lights that are on for simplicity
  436. }
  437. #endif
  438. if (!::Is_Geometry(node)) {
  439. return NULL;
  440. }
  441. // NOTE: we *have* to check Is_Proxy first because it is tied to a naming convention
  442. // rather than an explicit UI setting like the rest of the types.
  443. if (::Is_Proxy(*node)) {
  444. return new ProxyExportTaskClass(node,context);
  445. }
  446. if (::Is_Normal_Mesh(node) || Is_Camera_Aligned_Mesh(node) || Is_Camera_Oriented_Mesh(node) || Is_Skin(node)) {
  447. return new MeshGeometryExportTaskClass(node,context);
  448. }
  449. if (::Is_Collision_AABox(node) || Is_Collision_OBBox(node)) {
  450. return new CollisionBoxGeometryExportTaskClass(node,context);
  451. }
  452. if (::Is_Null_Object(node)) {
  453. return new NullGeometryExportTaskClass(node,context);
  454. }
  455. if (::Is_Dazzle(node)) {
  456. return new DazzleGeometryExportTaskClass(node,context);
  457. }
  458. if (::Is_Aggregate(node)) {
  459. return new AggregateGeometryExportTaskClass(node,context);
  460. }
  461. return NULL;
  462. }
  463. /***********************************************************************************************
  464. * GeometryExportTaskClass::Optimize_Geometry -- Optimizes the export tasks *
  465. * *
  466. * This function will attempt to split meshes so that they use only a single material and *
  467. * then try to combine small meshes that use the same material. Export tasks may be *
  468. * removed and new ones added. *
  469. * *
  470. * INPUT: *
  471. * tasks - dynamic vector of export task pointers. Some tasks may be deleted, some added *
  472. * *
  473. * OUTPUT: *
  474. * *
  475. * WARNINGS: *
  476. * *
  477. * HISTORY: *
  478. * 10/20/2000 gth : Created. *
  479. *=============================================================================================*/
  480. void GeometryExportTaskClass::Optimize_Geometry
  481. (
  482. DynamicVectorClass<GeometryExportTaskClass *> & tasks,
  483. GeometryExportContextClass & context
  484. )
  485. {
  486. int j=0,i=0;
  487. /*
  488. ** Pass 1: Extract all mesh geometry tasks from the input task array.
  489. ** NOTE: We're not optimizing Skin meshes so we leave them in the task array.
  490. */
  491. DynamicVectorClass<MeshGeometryExportTaskClass *> meshes;
  492. while (i<tasks.Count()) {
  493. if ( (tasks[i]->Get_Geometry_Type() == GeometryExportTaskClass::MESH) &&
  494. (!Is_Skin(tasks[i]->Get_Object_Node())) )
  495. {
  496. /*
  497. ** Add to the mesh array, remove from the tasks array
  498. */
  499. meshes.Add((MeshGeometryExportTaskClass *)(tasks[i]));
  500. tasks.Delete(i);
  501. } else {
  502. /*
  503. ** Leave in the task array and move to the next one.
  504. */
  505. i++;
  506. }
  507. }
  508. /*
  509. ** Pass 2: Split all meshes which use more than one material
  510. */
  511. DynamicVectorClass<MeshGeometryExportTaskClass *> simple_meshes;
  512. while (meshes.Count() > 0) {
  513. int cur_index = meshes.Count() - 1;
  514. MeshGeometryExportTaskClass * cur_mesh = meshes[cur_index];
  515. /*
  516. ** If this mesh already uses only one material, just transfer it to the simple_meshes array.
  517. ** Otherwise, have it split into new tasks, add them to the simple_meshes array, and delete this task.
  518. */
  519. if (cur_mesh->Is_Single_Material()) {
  520. simple_meshes.Add(cur_mesh);
  521. } else {
  522. cur_mesh->Split(simple_meshes);
  523. delete cur_mesh;
  524. }
  525. meshes.Delete(cur_index);
  526. }
  527. /*
  528. ** Pass 3: Combine meshes which satisfy the following
  529. ** - They use the same (single) material
  530. ** - They have fewer than 'x' polygons
  531. ** - They are 'close' to each other
  532. */
  533. i=0;
  534. while (i < simple_meshes.Count()) {
  535. if (simple_meshes[i]->Can_Combine()) {
  536. j=i+1;
  537. while (j < simple_meshes.Count()) {
  538. if (simple_meshes[i]->Can_Combine_With(simple_meshes[j])) {
  539. /*
  540. ** Add mesh 'j' into mesh 'i', delete its task.
  541. */
  542. simple_meshes[i]->Combine_Mesh(simple_meshes[j]);
  543. delete simple_meshes[j];
  544. simple_meshes.Delete(j);
  545. /*
  546. ** If we've just exceeded the max poly count, move to the next mesh
  547. */
  548. if (simple_meshes[i]->Can_Combine() == false) {
  549. j = simple_meshes.Count();
  550. }
  551. } else {
  552. /*
  553. ** Otherwise, move to the next mesh
  554. */
  555. j++;
  556. }
  557. }
  558. }
  559. i++;
  560. }
  561. /*
  562. ** Generate names for each of the meshes that were created
  563. */
  564. for (i=0; i<simple_meshes.Count(); i++) {
  565. // if (simple_meshes[i]->Is_Name_Dirty()) {
  566. simple_meshes[i]->Generate_Name("MESH",i,context);
  567. // }
  568. }
  569. /*
  570. ** Finally, transfer all of the optimized tasks into the big task array
  571. */
  572. for (i=0; i<simple_meshes.Count(); i++) {
  573. tasks.Add(simple_meshes[i]);
  574. }
  575. simple_meshes.Delete_All();
  576. }
  577. /***********************************************************************************************
  578. * GeometryExportTaskClass::Generate_Name -- create a name for this object *
  579. * *
  580. * During optimization, we can generate new meshes which need unique names. *
  581. * *
  582. * INPUT: *
  583. * index - will be used to create the name, don't re-use the same index within an LOD *
  584. * context - geometry export context *
  585. * *
  586. * OUTPUT: *
  587. * *
  588. * WARNINGS: *
  589. * *
  590. * HISTORY: *
  591. * 10/23/2000 gth : Created. *
  592. *=============================================================================================*/
  593. void GeometryExportTaskClass::Generate_Name(char * root,int index,GeometryExportContextClass & context)
  594. {
  595. /*
  596. ** Check if the original had a Renegade "building prefix" in its name. Building meshes
  597. ** are named with a 2-3 letter user specified prefix followed by either '^' or '#'. We
  598. ** have to maintain this prefix on the optimized meshes...
  599. */
  600. char prefix[5];
  601. strncpy(prefix,Name,sizeof(prefix));
  602. prefix[4] = 0;
  603. char * interior_prefix = strchr(prefix,'^');
  604. char * exterior_prefix = strchr(prefix,'#');
  605. memset(Name,0,sizeof(Name));
  606. if (interior_prefix != NULL) {
  607. strncpy(Name,prefix,(int)(interior_prefix - prefix) + 1);
  608. } else if (exterior_prefix != NULL) {
  609. strncpy(Name,prefix,(int)(exterior_prefix - prefix) + 1);
  610. }
  611. /*
  612. ** Append "root"{index} to the name
  613. */
  614. strcat(Name,root);
  615. strcat(Name,"{");
  616. sprintf(Name + strlen(Name),"%04d",index);
  617. strcat(Name,"}");
  618. /*
  619. ** Append the LOD index so the generated name doesn't collide with meshes in the same
  620. ** object but in other LOD's
  621. */
  622. sprintf(Name + strlen(Name),"%c",'a'+Get_Lod_Level(context.Origin));
  623. }
  624. /***********************************************************************************************
  625. * MeshGeometryExportTaskClass::Update_Cached_Data -- updates the cached material pointer, etc *
  626. * *
  627. * This should be called after the meshdata changes *
  628. * *
  629. * INPUT: *
  630. * *
  631. * OUTPUT: *
  632. * *
  633. * WARNINGS: *
  634. * *
  635. * HISTORY: *
  636. * 10/23/2000 gth : Created. *
  637. *=============================================================================================*/
  638. void MeshGeometryExportTaskClass::Update_Cached_Data(void)
  639. {
  640. SingleMtl = NULL;
  641. Mtl * nodemtl = Node->GetMtl();
  642. /*
  643. ** Set the SingleMtl pointer if this mesh uses only one material (again, even if its in a Multi-Sub)
  644. */
  645. if (nodemtl == NULL) {
  646. SingleMtl = NULL;
  647. } else if (nodemtl->NumSubMtls() <= 1) {
  648. SingleMtl = nodemtl;
  649. } else {
  650. int mat_index;
  651. int face_index;
  652. int sub_mtl_count = nodemtl->NumSubMtls();
  653. bool * sub_mtl_flags = new bool[sub_mtl_count];
  654. /*
  655. ** Initialize each sub-material flag to false (indicates that the material is un-used)
  656. */
  657. for (mat_index=0; mat_index<sub_mtl_count; mat_index++) {
  658. sub_mtl_flags[mat_index] = false;
  659. }
  660. /*
  661. ** Set a true for each material actually referenced by the mesh
  662. */
  663. for (face_index=0; face_index<MeshData.getNumFaces(); face_index++) {
  664. int max_mat_index = MeshData.faces[face_index].getMatID();
  665. int mat_index = (max_mat_index % sub_mtl_count);
  666. sub_mtl_flags[mat_index] = true;
  667. }
  668. /*
  669. ** Loop over the used materials counting how many 'true's we see
  670. */
  671. int mat_count = 0;
  672. for (mat_index=0; mat_index<sub_mtl_count; mat_index++) {
  673. if (sub_mtl_flags[mat_index]) {
  674. SingleMtl = nodemtl->GetSubMtl(mat_index);
  675. mat_count++;
  676. }
  677. }
  678. if (mat_count > 1) {
  679. SingleMtl = NULL;
  680. }
  681. }
  682. /*
  683. ** Update the bounding box
  684. */
  685. Point3 boxmin(0,0,0);
  686. Point3 boxmax(0,0,0);
  687. if (MeshData.numVerts > 0) {
  688. boxmin = MeshData.verts[1];
  689. boxmax = MeshData.verts[0];
  690. for (int i=0; i<MeshData.numVerts; i++) {
  691. boxmin.x = MIN(MeshData.verts[i].x,boxmin.x);
  692. boxmin.y = MIN(MeshData.verts[i].y,boxmin.y);
  693. boxmin.z = MIN(MeshData.verts[i].z,boxmin.z);
  694. boxmax.x = MAX(MeshData.verts[i].x,boxmax.x);
  695. boxmax.y = MAX(MeshData.verts[i].y,boxmax.y);
  696. boxmax.z = MAX(MeshData.verts[i].z,boxmax.z);
  697. }
  698. }
  699. boxmax += Point3(0.1f,0.1f,0.1f);
  700. boxmin -= Point3(0.1f,0.1f,0.1f);
  701. BoxCenter = (boxmax + boxmin) * 0.5f;
  702. BoxExtent = (boxmax - boxmin) * 0.5f;
  703. WorldBounds = Box3(boxmin,boxmax) * Node->GetObjectTM(CurTime);
  704. }
  705. /***********************************************************************************************
  706. * MeshGeometryExportTaskClass::Is_Single_Material -- Tests if this mesh uses a single materia *
  707. * *
  708. * INPUT: *
  709. * *
  710. * OUTPUT: *
  711. * *
  712. * WARNINGS: *
  713. * *
  714. * HISTORY: *
  715. * 10/20/2000 gth : Created. *
  716. *=============================================================================================*/
  717. bool MeshGeometryExportTaskClass::Is_Single_Material(void)
  718. {
  719. return ((SingleMtl != NULL) || (Node->GetMtl() == NULL));
  720. }
  721. /***********************************************************************************************
  722. * MeshGeometryExportTaskClass::Get_Single_Material -- returns pointer to the material *
  723. * *
  724. * INPUT: *
  725. * *
  726. * OUTPUT: *
  727. * *
  728. * WARNINGS: *
  729. * *
  730. * HISTORY: *
  731. * 10/23/2000 gth : Created. *
  732. *=============================================================================================*/
  733. Mtl* MeshGeometryExportTaskClass::Get_Single_Material(void)
  734. {
  735. return SingleMtl;
  736. }
  737. /***********************************************************************************************
  738. * MeshGeometryExportTaskClass::Split -- Splits into single material meshes *
  739. * *
  740. * This function will create new export tasks and add them to the supplied array. Each of *
  741. * these will be single-material meshes. *
  742. * *
  743. * INPUT: *
  744. * *
  745. * OUTPUT: *
  746. * *
  747. * WARNINGS: *
  748. * *
  749. * HISTORY: *
  750. * 10/20/2000 gth : Created. *
  751. *=============================================================================================*/
  752. void MeshGeometryExportTaskClass::Split(DynamicVectorClass<MeshGeometryExportTaskClass *> & simple_meshes)
  753. {
  754. assert(!Is_Single_Material());
  755. Mtl * nodemtl = Node->GetMtl();
  756. int mat_index;
  757. int face_index;
  758. int sub_mtl_count = nodemtl->NumSubMtls();
  759. bool * sub_mtl_flags = new bool[sub_mtl_count];
  760. /*
  761. ** Initialize each sub-material flag to false (indicates that the material is un-used)
  762. */
  763. for (mat_index=0; mat_index<sub_mtl_count; mat_index++) {
  764. sub_mtl_flags[mat_index] = false;
  765. }
  766. /*
  767. ** Set a true for each material actually referenced by the mesh
  768. */
  769. for (face_index=0; face_index<MeshData.getNumFaces(); face_index++) {
  770. int max_mat_index = MeshData.faces[face_index].getMatID();
  771. int mat_index = (max_mat_index % sub_mtl_count);
  772. sub_mtl_flags[mat_index] = true;
  773. }
  774. /*
  775. ** Loop over the used materials, creating a new MeshGeometryExportTaskClass for each one
  776. */
  777. for (mat_index=0; mat_index<sub_mtl_count; mat_index++) {
  778. if (sub_mtl_flags[mat_index]) {
  779. MeshGeometryExportTaskClass * new_task = new MeshGeometryExportTaskClass(*this);
  780. new_task->Reduce_To_Single_Material(mat_index);
  781. new_task->Set_Name_Dirty(true);
  782. simple_meshes.Add(new_task);
  783. }
  784. }
  785. }
  786. /***********************************************************************************************
  787. * MeshGeometryExportTaskClass::Reduce_To_Single_Material -- deletes polys *
  788. * *
  789. * This function deletes all polys (and subsequent un-used vertices) except for the ones *
  790. * that use the specified material. *
  791. * *
  792. * INPUT: *
  793. * mat_id - only faces using this material id will remain in the mesh. *
  794. * *
  795. * OUTPUT: *
  796. * *
  797. * WARNINGS: *
  798. * *
  799. * HISTORY: *
  800. * 10/20/2000 gth : Created. *
  801. *=============================================================================================*/
  802. void MeshGeometryExportTaskClass::Reduce_To_Single_Material(int mat_id)
  803. {
  804. int sub_mtl_count = Node->GetMtl()->NumSubMtls();
  805. BitArray faces_to_delete(MeshData.getNumFaces());
  806. BitArray verts_to_delete(MeshData.getNumVerts());
  807. faces_to_delete.ClearAll();
  808. verts_to_delete.ClearAll();
  809. for (int i=0; i<MeshData.getNumFaces(); i++) {
  810. if ((MeshData.faces[i].getMatID() % sub_mtl_count) != mat_id) {
  811. faces_to_delete.Set(i,true);
  812. }
  813. }
  814. MeshData.DeleteFaceSet(faces_to_delete,&verts_to_delete);
  815. MeshData.DeleteVertSet(verts_to_delete);
  816. Update_Cached_Data();
  817. }
  818. /***********************************************************************************************
  819. * MeshGeometryExportTaskClass::Can_Combine -- can this mesh combine with anything *
  820. * *
  821. * This will return false if the mesh has multiple materials, too many polys, etc. *
  822. * *
  823. * INPUT: *
  824. * *
  825. * OUTPUT: *
  826. * *
  827. * WARNINGS: *
  828. * *
  829. * HISTORY: *
  830. * 10/24/2000 gth : Created. *
  831. *=============================================================================================*/
  832. bool MeshGeometryExportTaskClass::Can_Combine(void)
  833. {
  834. /*
  835. ** This mesh can't combine at all if:
  836. ** - it has multiple materials
  837. ** - its polygon count is too high
  838. ** - (Renegade) VIS is enabled (don't want to create really wierd shaped vis-sectors...)
  839. */
  840. if (!Is_Single_Material()) {
  841. return false;
  842. }
  843. if (MeshData.numFaces > OPTIMIZATION_FACECOUNT_LIMIT) {
  844. return false;
  845. }
  846. if (ExportOptions.Is_Vis_Collision_Enabled()) {
  847. return false;
  848. }
  849. return true;
  850. }
  851. /***********************************************************************************************
  852. * MeshGeometryExportTaskClass::Can_Combine_With -- can this mesh be combined with the given m *
  853. * *
  854. * INPUT: *
  855. * *
  856. * OUTPUT: *
  857. * *
  858. * WARNINGS: *
  859. * *
  860. * HISTORY: *
  861. * 10/24/2000 gth : Created. *
  862. *=============================================================================================*/
  863. bool MeshGeometryExportTaskClass::Can_Combine_With(MeshGeometryExportTaskClass * other_mesh)
  864. {
  865. /*
  866. ** Does the mesh attach to the same W3D bone that we do?
  867. */
  868. if (other_mesh->BoneIndex != BoneIndex) {
  869. return false;
  870. }
  871. /*
  872. ** Does the mesh use the same (single) material that we do?
  873. */
  874. Mtl * other_mtl = other_mesh->Get_Single_Material();
  875. if (other_mtl == NULL) {
  876. return false;
  877. }
  878. Mtl * my_mtl = Get_Single_Material();
  879. if (my_mtl != other_mtl) {
  880. return false;
  881. }
  882. /*
  883. ** Are its relevant W3D options the same as ours?
  884. */
  885. if (ExportOptions.Geometry_Options_Match(other_mesh->ExportOptions)) {
  886. return false;
  887. }
  888. /*
  889. ** Would our combined polygon count be reasonable
  890. */
  891. if (MeshData.numFaces + other_mesh->MeshData.numFaces > OPTIMIZATION_FACECOUNT_LIMIT) {
  892. return false;
  893. }
  894. /*
  895. ** Is the other mesh near me?
  896. */
  897. Point3 my_center = Node->GetObjectTM(CurTime) * BoxCenter;
  898. Point3 other_center = other_mesh->Node->GetObjectTM(CurTime) * BoxCenter;
  899. float dist = ::FLength(my_center - other_center);
  900. if (dist > OPTIMIZATION_COMBINING_DISTANCE) {
  901. return false;
  902. }
  903. return true;
  904. }
  905. /***********************************************************************************************
  906. * MeshGeometryExportTaskClass::Combine_Mesh -- Add the given mesh into this mesh *
  907. * *
  908. * INPUT: *
  909. * *
  910. * OUTPUT: *
  911. * *
  912. * WARNINGS: *
  913. * *
  914. * HISTORY: *
  915. * 10/24/2000 gth : Created. *
  916. *=============================================================================================*/
  917. void MeshGeometryExportTaskClass::Combine_Mesh(MeshGeometryExportTaskClass * other_mesh)
  918. {
  919. /*
  920. ** Compute the transform from other_mesh's coordinate system to ours so that
  921. ** its polygons can be combined with ours (by calling CombineMeshes)
  922. */
  923. Matrix3 our_tm = Node->GetObjectTM(CurTime);
  924. Matrix3 his_tm = other_mesh->Node->GetObjectTM(CurTime);
  925. Matrix3 tm = Inverse(our_tm) * his_tm;
  926. /*
  927. ** Store our current material index
  928. */
  929. int matid = MeshData.faces[0].getMatID();
  930. if (Node->GetMtl()->NumSubMtls() > 1) {
  931. matid = matid % Node->GetMtl()->NumSubMtls();
  932. }
  933. /*
  934. ** Combine the meshes
  935. */
  936. Mesh new_mesh;
  937. ::CombineMeshes(new_mesh,MeshData,other_mesh->MeshData,&our_tm,&his_tm,0);
  938. MeshData = new_mesh;
  939. /*
  940. ** Set all material ID's
  941. */
  942. for (int i=0; i<MeshData.numFaces; i++) {
  943. MeshData.faces[i].setMatID(matid);
  944. }
  945. }
  946. Point3 MeshGeometryExportTaskClass::Get_Shared_Vertex_Normal(const Point3 & world_pos,int smgroup)
  947. {
  948. const float EPSILON = 0.001f;
  949. Point3 normal(0,0,0);
  950. /*
  951. ** Does the bounding box for this node even contain the point
  952. ** we are looking for?
  953. */
  954. if (WorldBounds.Contains(world_pos) != 0) {
  955. /*
  956. ** Transform the query point into object space
  957. */
  958. Matrix3 tm = Node->GetObjectTM(CurTime);
  959. Point3 obj_pos = world_pos * Inverse(tm);
  960. /*
  961. ** Loop through all the faces in this mesh and find out which ones
  962. ** share the same smoothing group as the vertex we are looking for.
  963. */
  964. for (int face_index = 0; face_index < MeshData.numFaces; face_index ++) {
  965. Face &maxface = MeshData.faces[face_index];
  966. int face_smgroup = maxface.getSmGroup();
  967. if ((face_smgroup & smgroup) || (face_smgroup == smgroup)) {
  968. /*
  969. ** Find out if any of the verticies of this face share the
  970. ** same space as the vertex we are looking for.
  971. */
  972. bool found = false;
  973. for (int vert_index = 0; (vert_index < 3) && !found; vert_index ++) {
  974. int max_vert_index = maxface.v[vert_index];
  975. Point3 delta = obj_pos - MeshData.verts[max_vert_index];
  976. if ((fabs (delta.x) < EPSILON) &&
  977. (fabs (delta.y) < EPSILON) &&
  978. (fabs (delta.z) < EPSILON))
  979. {
  980. /*
  981. ** Compute the normal for this face
  982. */
  983. Point3 v0 = MeshData.verts[maxface.v[0]];
  984. Point3 v1 = MeshData.verts[maxface.v[1]];
  985. Point3 v2 = MeshData.verts[maxface.v[2]];
  986. Point3 face_normal = (v1-v0)^(v2-v1);
  987. face_normal = ::Normalize(face_normal);
  988. /*
  989. ** Add this face normal to the sum
  990. */
  991. normal.x += face_normal.x;
  992. normal.y += face_normal.y;
  993. normal.z += face_normal.z;
  994. /*
  995. ** Done with this face, look for more
  996. */
  997. found = true;
  998. }
  999. }
  1000. }
  1001. }
  1002. /*
  1003. ** Transform the "normal" to world space. Note that this vector isn't
  1004. ** normalized because we are basically summing the contributions of each
  1005. ** face in each mesh which shares this normal. The final normal
  1006. ** will be normalized in the MeshBuilderClass.
  1007. */
  1008. tm.NoTrans();
  1009. normal = tm.PointTransform(normal);
  1010. }
  1011. return normal;
  1012. }