/* ** 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 . */ /* $Header: /Commando/Code/Tools/max2w3d/meshsave.cpp 107 8/21/01 10:28a Greg_h $ */ /*********************************************************************************************** *** Confidential - Westwood Studios *** *********************************************************************************************** * * * Project Name : Commando / G * * * * File Name : MESHSAVE.CPP * * * * Programmer : Greg Hjelstrom * * * * Start Date : 06/10/97 * * * * Last Update : 10/20/1999997 [GH] * * * *---------------------------------------------------------------------------------------------* * Functions: * * MeshSaveClass::MeshSaveClass -- constructor, processes a Max mesh * * MeshSaveClass::~MeshSaveClass -- destructor, frees all allocated memory * * MeshSaveClass::write_verts -- write the vertex chunk into a wtm file * * MeshSaveClass::write_header -- write a mesh header chunk into a wtm file * * MeshSaveClass::Write_To_File -- Append the mesh to an open wtm file * * MeshSaveClass::write_normals -- writes the vertex normals chunk into a wtm file * * MeshSaveClass::write_vert_normals -- Writes the surrender normal chunk into a wtm file * * MeshSaveClass::write_triangles -- Write the triangles chunk into a wtm file. * * MeshSaveClass::write_sr_triangles -- writes the triangles in surrender friendly format * * MeshSaveClass::write_triangles -- write the triangles chunk * * MeshSaveClass::compute_surrender_vertex -- Compute the surrender vertex normals * * MeshSaveClass::setup_material -- Gets the texture names and base colors for a material * * MeshSaveClass::compute_bounding_volumes -- computes a bounding box and bounding sphere for* * MeshSaveClass::set_transform -- set the default transformation matrix for the mesh * * MeshSaveClass::compute_physical_properties -- computes the volume and moment of inertia * * MeshSaveClass::prep_mesh -- pre-transform the MAX mesh by a specified matrix * * MeshSaveClass::write_user_text -- write the user text chunk * * MeshSaveClass::get_htree_bone_index_for_inode -- searches the htree for the given INode * * MeshSaveClass::get_skin_modifier_objects -- Searches for the WWSkin modifier for this mes * * MeshSaveClass::inv_deform_mesh -- preprocess the mesh for skinning * * MeshSaveClass::create_materials -- create the materials for this mesh * * MeshSaveClass::write_ps2_shaders -- Write shaders specific to the PS2 in their own chunk. * * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ #include "meshsave.h" #include #include #include #include "gamemtl.h" #include "errclass.h" #include "vxl.h" #include "vxldbg.h" #include "nodelist.h" #include "hiersave.h" #include "util.h" #include "w3dappdata.h" #include "skin.h" #include "skindata.h" #include "meshbuild.h" #include "alphamodifier.h" #include "aabtreebuilder.h" #include "exportlog.h" static char _string1[512]; const int VOXEL_RESOLUTION = 64; // resolution to use when computing I, V and CM #define DEBUG_VOXELS 0 #define MIN_AABTREE_POLYGONS 8 #define DIFFUSE_HOUSECOLOR_TEXTURE_PREFIX 0x4443485A //'ZHCD' prefix put on all code generated textures #define DIFFUSE_COLOR_TEXTURE_PREFIX 0x44434d5A //'ZMCD' prefix put on all code generated textures #define DIFFUSE_COLOR_TEXTURE_MASK 0x4443005A /************************************************************************************ ** ** Compute the determinant of the 3x3 portion of the given matrix ** ************************************************************************************/ float Compute_3x3_Determinant(const Matrix3 & tm) { float det = tm[0][0] * (tm[1][1]*tm[2][2] - tm[1][2]*tm[2][1]); det -= tm[0][1] * (tm[1][0]*tm[2][2] - tm[1][2]*tm[2][0]); det += tm[0][2] * (tm[1][0]*tm[2][1] - tm[1][1]*tm[2][0]); return det; } /************************************************************************************ ** ** check if this is a mesh which should use a simple rendering method. I don't ** compute vertex normals or store u-v's in that case (prevents vertex splitting) ** ************************************************************************************/ bool use_simple_rendering(int geo_type) { geo_type &= W3D_MESH_FLAG_GEOMETRY_TYPE_MASK; if ( (geo_type == OBSOLETE_W3D_MESH_FLAG_GEOMETRY_TYPE_SHADOW) || (geo_type == W3D_MESH_FLAG_GEOMETRY_TYPE_AABOX) || (geo_type == W3D_MESH_FLAG_GEOMETRY_TYPE_OBBOX) ) { return true; } else { return false; } } /************************************************************************************ ** ** build the bitfield of W3D mesh attributes for the given node ** ************************************************************************************/ uint32 setup_mesh_attributes(INode * node) { uint32 attributes = W3D_MESH_FLAG_NONE; /* ** Mesh will be one of: ** W3D_MESH_FLAG_NONE, ** W3D_MESH_FLAG_COLLISION_BOX, ** W3D_MESH_FLAG_SKIN, ** W3D_MESH_FLAG_ALIGNED ** W3D_MESH_FLAG_ORIENTED */ if (Is_Collision_AABox(node)) { attributes = W3D_MESH_FLAG_GEOMETRY_TYPE_AABOX; } else if (Is_Collision_OBBox(node)) { attributes = W3D_MESH_FLAG_GEOMETRY_TYPE_OBBOX; } else if (Is_Skin(node)) { attributes = W3D_MESH_FLAG_GEOMETRY_TYPE_SKIN; } else if (Is_Camera_Aligned_Mesh(node)) { attributes = W3D_MESH_FLAG_GEOMETRY_TYPE_CAMERA_ALIGNED; } else if (Is_Camera_Oriented_Mesh(node)) { attributes = W3D_MESH_FLAG_GEOMETRY_TYPE_CAMERA_ORIENTED; } /* ** And, a mesh may have one or more types of collision detection enabled. ** W3D_MESH_FLAG_COLLISION_TYPE_PHYSICAL ** W3D_MESH_FLAG_COLLISION_TYPE_PROJECTILE ** However, if the mesh is SKIN, SHADOW, ALIGNED, ORIENTED or NULL, don't let ** the collision bits get set... */ if ( attributes != W3D_MESH_FLAG_GEOMETRY_TYPE_SKIN && attributes != W3D_MESH_FLAG_GEOMETRY_TYPE_CAMERA_ALIGNED && attributes != W3D_MESH_FLAG_GEOMETRY_TYPE_CAMERA_ORIENTED ) { if (Is_Physical_Collision(node)) { attributes |= W3D_MESH_FLAG_COLLISION_TYPE_PHYSICAL; } if (Is_Projectile_Collision(node)) { attributes |= W3D_MESH_FLAG_COLLISION_TYPE_PROJECTILE; } if (Is_Vis_Collision(node)) { attributes |= W3D_MESH_FLAG_COLLISION_TYPE_VIS; } if (Is_Camera_Collision(node)) { attributes |= W3D_MESH_FLAG_COLLISION_TYPE_CAMERA; } if (Is_Vehicle_Collision(node)) { attributes |= W3D_MESH_FLAG_COLLISION_TYPE_VEHICLE; } } /* ** A mesh may have one of the following bits set as well */ if (Is_Hidden(node)) { attributes |= W3D_MESH_FLAG_HIDDEN; } if (Is_Two_Sided(node)) { attributes |= W3D_MESH_FLAG_TWO_SIDED; } if (Is_Shadow(node)) { attributes |= W3D_MESH_FLAG_CAST_SHADOW; } if (Is_Shatterable(node)) { attributes |= W3D_MESH_FLAG_SHATTERABLE; } if (Is_NPatchable(node)) { attributes |= W3D_MESH_FLAG_NPATCHABLE; } return attributes; } /*********************************************************************************************** * MeshSaveClass::MeshSaveClass -- constructor, processes a Max mesh * * * * This class takes a MAX mesh and computes the information for a W3D mesh or skin. * * * * INPUT: * * * * inode - the max INode containing the mesh/skin to export * * exportspace - matrix defining the desired coordinate system for the mesh * * htree - hierarchy tree that this mesh is being connected to * * curtime - current time in Max. * * meter - progress meter * * mesh_name - name to use for the mesh * * container_name - name of the container * * * * OUTPUT: * * * * WARNINGS: * * * * HISTORY: * * 06/10/1997 GH : Created. * *=============================================================================================*/ MeshSaveClass::MeshSaveClass ( char * mesh_name, char * container_name, INode * inode, const Mesh * input_mesh, Matrix3 & exportspace, W3DAppData2Struct & exportoptions, HierarchySaveClass * htree, TimeValue curtime, Progress_Meter_Class & meter, unsigned int * materialColors, int &numMaterialColors, int &numHouseColors, char *materialColorTexture, WorldInfoClass * world_info ) : MaxINode(inode), ExportOptions(exportoptions), CurTime(curtime), ExportSpace(exportspace), HTree(htree), UserText(NULL), VertInfluences(NULL), MaterialRemapTable(NULL) { Mesh mesh = *input_mesh; // copy the mesh so we can modify it Mtl * nodemtl = inode->GetMtl(); DWORD wirecolor = inode->GetWireColor(); PS2Material = FALSE; // Check to see if the mesh uses PS2 game materials. If so, set a flag so // that write_shaders will know to make a PS2 shader chunk. if (nodemtl) { if (nodemtl->ClassID() == PS2GameMaterialClassID) { PS2Material = TRUE; } else if (nodemtl->IsMultiMtl()) { for (int i = 0; i < nodemtl->NumSubMtls(); i++) { Mtl *sub = nodemtl->GetSubMtl(i); if (sub->ClassID() == PS2GameMaterialClassID) { PS2Material = TRUE; } } } } ////////////////////////////////////////////////////////////////////// // Check if the mesh is being inverted by its transform. If this // is the case, then we will need to reverse the winding of all // polygons later. ////////////////////////////////////////////////////////////////////// Matrix3 objtm = MaxINode->GetObjectTM(curtime); MeshInverted = (Compute_3x3_Determinant(objtm) < 0.0f); ////////////////////////////////////////////////////////////////////// // Prep the mesh by transforming it by the delta between exportspace // and this INodes current space // (this is the delta between the bone and the mesh if one exists...) ////////////////////////////////////////////////////////////////////// MeshToExportSpace = objtm * Inverse(ExportSpace); prep_mesh(mesh,MeshToExportSpace); ////////////////////////////////////////////////////////////////////// // Prepare the mesh header. ////////////////////////////////////////////////////////////////////// assert(mesh_name != NULL); assert(container_name != NULL); memset(&Header,0,sizeof(Header)); Set_W3D_Name(Header.MeshName,mesh_name); Set_W3D_Name(Header.ContainerName,container_name); Header.Version = W3D_CURRENT_MESH_VERSION; Header.Attributes = setup_mesh_attributes(MaxINode); meter.Finish_In_Steps( 3*Header.NumTris + // normals Header.NumVertices + // surrender normals 64 // voxelization ); ExportLog::printf("\nProcessing Mesh: %s\n",Header.MeshName); ////////////////////////////////////////////////////////////////////// // Enforce that we have enough data to actually make a mesh ////////////////////////////////////////////////////////////////////// if (mesh.getNumFaces() <= 0) { throw ErrorClass("No Triangles in Mesh: %s",Header.MeshName); } if (mesh.getNumVerts() <= 0) { throw ErrorClass("No Vertices in Mesh: %s",Header.MeshName); } ////////////////////////////////////////////////////////////////////// // process the materials ////////////////////////////////////////////////////////////////////// DebugPrint("processing materials\n"); scan_used_materials(mesh,nodemtl); create_materials(nodemtl,wirecolor,materialColorTexture); ////////////////////////////////////////////////////////////////////// // what face and vertex attributes are we going to export? ////////////////////////////////////////////////////////////////////// Header.FaceChannels = W3D_FACE_CHANNEL_FACE; Header.VertexChannels = W3D_VERTEX_CHANNEL_LOCATION; if (!use_simple_rendering(Header.Attributes)) { Header.VertexChannels |= W3D_VERTEX_CHANNEL_NORMAL; } if (((Header.Attributes & W3D_MESH_FLAG_GEOMETRY_TYPE_MASK) == W3D_MESH_FLAG_GEOMETRY_TYPE_SKIN) && (HTree != NULL)) { Header.VertexChannels |= W3D_VERTEX_CHANNEL_BONEID; } ////////////////////////////////////////////////////////////////////// // Process the mesh ////////////////////////////////////////////////////////////////////// Builder.Set_World_Info (world_info); Build_Mesh(mesh, nodemtl, materialColors, numMaterialColors, numHouseColors); if (materialColorTexture) { //diffuse color materials are replaced by textures //set diffuse to 255,255,255 so it has no effect. fix_diffuse_materials(numHouseColors != 0); } ////////////////////////////////////////////////////////////////////// // Create damage (deform) information for the mesh ////////////////////////////////////////////////////////////////////// Object *ref_obj = MaxINode->GetObjectRef (); DeformSave.Initialize(Builder, ref_obj, mesh, &MeshToExportSpace); ////////////////////////////////////////////////////////////////////// // Determine if the deformer should use alpha or v-color info ////////////////////////////////////////////////////////////////////// if (ExportOptions.Is_Vertex_Alpha_Enabled()) { unsigned int alpha_passes = 0; for (int pass=0; pass < MaterialDesc.Pass_Count(); pass++) { if (MaterialDesc.Pass_Uses_Vertex_Alpha(pass)) { alpha_passes |= (1 << pass); } } DeformSave.Set_Alpha_Passes(alpha_passes); } ////////////////////////////////////////////////////////////////////// // Set the counts in the mesh header ////////////////////////////////////////////////////////////////////// Header.NumTris = Builder.Get_Face_Count(); Header.NumVertices = Builder.Get_Vertex_Count(); ////////////////////////////////////////////////////////////////////// // Compute the mesh's bounding box and sphere. This must be done // before we pre-deform the mesh (if its a skin). ////////////////////////////////////////////////////////////////////// compute_bounding_volumes(); ////////////////////////////////////////////////////////////////////// // Voxelize the mesh and compute the Moment of Inertia and // Center of Mass. This must come after we compute the bounding // volumes and before we pre-deform the mesh. ////////////////////////////////////////////////////////////////////// Progress_Meter_Class voxelmeter(meter, 64.0f * meter.Increment); compute_physical_constants(MaxINode,voxelmeter,false /*usevoxelizer*/); ////////////////////////////////////////////////////////////////////// // If this is a skin, pre-deform the mesh. ////////////////////////////////////////////////////////////////////// if (((Header.Attributes & W3D_MESH_FLAG_GEOMETRY_TYPE_MASK) == W3D_MESH_FLAG_GEOMETRY_TYPE_SKIN) && (HTree != NULL)) { inv_deform_mesh(); } ////////////////////////////////////////////////////////////////////// // Get the user text from MAX's properties window. ////////////////////////////////////////////////////////////////////// TSTR usertext; MaxINode->GetUserPropBuffer(usertext); CStr usertext8 = usertext; if (usertext8.Length() > 0) { UserText = new char[usertext8.Length() + 1]; memset(UserText,0,usertext8.Length() + 1); memcpy(UserText,usertext8.data(),usertext8.Length()); } } /*********************************************************************************************** * MeshSaveClass::~MeshSaveClass -- destructor, frees all allocated memory * * * * INPUT: * * * * OUTPUT: * * * * WARNINGS: * * * * HISTORY: * * 06/10/1997 GH : Created. * *=============================================================================================*/ MeshSaveClass::~MeshSaveClass(void) { if (UserText) { delete[] UserText; UserText = NULL; } if (VertInfluences) { delete[] VertInfluences; VertInfluences = NULL; } if (MaterialRemapTable) { delete[] MaterialRemapTable; MaterialRemapTable = NULL; } } //search through previously found material colors and return index. house colors are always placed in top row. void getMaterialUV(UVVert &tvert,unsigned int diffuse, unsigned int *materialColors, int &numMaterialColors, int &numHouseColors, bool house) { int i; if (house) { //this material is a house color, place it in first row. for (i=0; i<16; i++) { if (materialColors[i]==diffuse) { tvert.x=((double)(i%16)+0.5)/16.0; ///@todo: MW: Remove hard-coded texture size tvert.y=1.0-((double)(i/16)+0.5)/16.0; numHouseColors=16; return; } } ExportLog::printf("\nUndefined House Color %d,%d,%d",(diffuse>>16)&0xff,(diffuse>>8)&0xff,diffuse&0xff); assert(0); //all house colors must be from a predefined range of reds } for (i=16; i<(16+numMaterialColors); i++) { if (materialColors[i]==diffuse) { tvert.x=((double)(i%16)+0.5)/16.0; ///@todo: MW: Remove hard-coded texture size tvert.y=1.0-((double)(i/16)+0.5)/16.0; return; } } //new color found tvert.x=((double)(i%16)+0.5)/16.0; ///@todo: MW: Remove hard-coded texture size tvert.y=1.0-((double)(i/16)+0.5)/16.0; materialColors[i]=diffuse; numMaterialColors++; } void MeshSaveClass::Build_Mesh(Mesh & mesh, Mtl *node_mtl, unsigned int *materialColors, int &numMaterialColors, int &numHouseColors) { int vert_counter; int face_index; int pass; int stage; float *vdata = NULL; int firstSolidColoredMaterial=-1; Builder.Reset(true,mesh.getNumFaces(),mesh.getNumFaces()/3); // Get a pointer to the channel that has alpha values entered by the artist. // This pointer will be NULL if they didn't use the channel. vdata = mesh.vertexFloat(ALPHA_VERTEX_CHANNEL); /* ** Get the skin info */ bool is_skin = false; SkinDataClass * skindata = NULL; SkinWSMObjectClass * skinobj = NULL; get_skin_modifier_objects(&skindata,&skinobj); if ( ((Header.Attributes & W3D_MESH_FLAG_GEOMETRY_TYPE_MASK) == W3D_MESH_FLAG_GEOMETRY_TYPE_SKIN) && (HTree != NULL) ) { is_skin = ((skindata != NULL) && (skinobj != NULL)); } /* ** Submit all of the faces */ MeshBuilderClass::FaceClass face; for (face_index = 0; face_index < mesh.getNumFaces(); face_index++) { Face maxface = mesh.faces[face_index]; int mat_index = 0; if (Header.NumMaterials > 0) { mat_index = MaterialRemapTable[(maxface.getMatID() % Header.NumMaterials)]; } assert(mat_index != -1); for (pass=0; passNumSubMtls() > 1)) { mtl_to_use = node_mtl->GetSubMtl (maxface.getMatID() % node_mtl->NumSubMtls()); } if ((mtl_to_use != NULL) && ((mtl_to_use->ClassID() == GameMaterialClassID) || (mtl_to_use->ClassID() == PS2GameMaterialClassID))) { face.SurfaceType = ((GameMtl *)mtl_to_use)->Get_Surface_Type (); } for (vert_counter = 0; vert_counter < 3; vert_counter++) { /* ** if this mesh is being inverted, we need to insert the verts in ** the opposite order. max_vert_counter will count backwards ** in this case; causing all vertex data to be entered in the ** reverse winding. */ int max_vert_counter; if (MeshInverted) { max_vert_counter = 2 - vert_counter; } else { max_vert_counter = vert_counter; } int max_vert_index = maxface.v[max_vert_counter]; /* ** Vertex Id, to prevent unwanted welding! */ face.Verts[vert_counter].Id = max_vert_index; /* ** Vertex Position */ face.Verts[vert_counter].Position.X = mesh.verts[max_vert_index].x; face.Verts[vert_counter].Position.Y = mesh.verts[max_vert_index].y; face.Verts[vert_counter].Position.Z = mesh.verts[max_vert_index].z; if (vdata) { // If an alpha channel has been created, use its value. for (int pass=0; pass < MaterialDesc.Pass_Count(); pass++) { if (MaterialDesc.Pass_Uses_Vertex_Alpha(pass)) { // Mulitiply by .01 to change from percentage. face.Verts[vert_counter].Alpha[pass] = vdata[max_vert_index] * .01; } } } /* ** Texture coordinate. Apply uv coords if the mesh has them and is not being ** instructed to ignore them. ** - check if the mesh needs them (uses_simple_rendering() == false) ** - for each pass and stage, look up what map channel this face's material is using ** - ask Max for the tfaces and tverts for that map channel ** - copy the values into each vertex */ for (pass=0; passFilename && *((unsigned int *)map3d->Filename) == DIFFUSE_COLOR_TEXTURE_PREFIX) //check for prefix { double Diffuse = vmat->Diffuse.Get_Color() >> 8; //get material color //MW: Encode the material color into the u texture coordinate tvert.x=Diffuse; tvert.y=Diffuse; //find out material color location within texture page if (strnicmp(MaterialDesc.Get_Vertex_Material_Name(mat_index,pass),"HouseColor",10)==0) getMaterialUV(tvert,vmat->Diffuse.Get_Color() >> 8, materialColors, numMaterialColors, numHouseColors, true); else getMaterialUV(tvert,vmat->Diffuse.Get_Color() >> 8, materialColors, numMaterialColors, numHouseColors, false); //Keep track of first vertex material converted, so we can remap all other non-textured //materials to use the same material. if (firstSolidColoredMaterial == -1) firstSolidColoredMaterial=MaterialDesc.Get_Vertex_Material_Index(mat_index,pass); } /* ** If the mesh needs uv coords and they are present, copy the uv into tvert */ else if (!use_simple_rendering(Header.Attributes)) { int channel = MaterialDesc.Get_Map_Channel(mat_index,pass,stage); UVVert * uvarray = mesh.mapVerts(channel); TVFace * tvfacearray = mesh.mapFaces(channel); ///@todo: MW: Forced ingoring of uv coordinates if no texture! Is this ok? W3dMapClass *map3d=MaterialDesc.Get_Texture(mat_index,pass,stage); if (map3d && (uvarray != NULL) && (tvfacearray != NULL)) { int tvert_index = tvfacearray[face_index].t[max_vert_counter]; tvert = uvarray[tvert_index]; } } /* ** Copy the texture coordinate into the vertex structure */ face.Verts[vert_counter].TexCoord[pass][stage].X = tvert.x; face.Verts[vert_counter].TexCoord[pass][stage].Y = tvert.y; } } /* ** Vertex Color */ if (mesh.vcFace) { /* ** If the mesh is being mirrored, remap the index */ int max_cvert_index = mesh.vcFace[face_index].t[max_vert_counter]; VertColor vc; vc = mesh.vertCol[max_cvert_index]; /* ** If Vertex Alpha is specified, the vertex color is converted ** to alpha. If the (obsolete) Node-flag for vertex alpha is enabled, ** the alpha is put into each pass which has alpha enabled. Otherwise, ** we check the material settings for which passes should get the alpha ** values. If neither alpha options are enabled, we put the color ** value into the first pass. */ if (ExportOptions.Is_Vertex_Alpha_Enabled()) { float alpha = (vc.x + vc.y + vc.z) / 3.0f; for (int pass=0; pass < MaterialDesc.Pass_Count(); pass++) { if (MaterialDesc.Pass_Uses_Vertex_Alpha(pass)) { face.Verts[vert_counter].Alpha[pass] = alpha; } } } else { face.Verts[vert_counter].DiffuseColor[0].X = vc.x; face.Verts[vert_counter].DiffuseColor[0].Y = vc.y; face.Verts[vert_counter].DiffuseColor[0].Z = vc.z; } face.Verts[vert_counter].MaxVertColIndex = max_cvert_index; } else { face.Verts[vert_counter].MaxVertColIndex = 0; } /* ** Vertex materials (get's index of sub-material) */ for (pass = 0; passFilename && *((unsigned int *)map3d->Filename) == DIFFUSE_COLOR_TEXTURE_PREFIX) //check for prefix face.Verts[vert_counter].VertexMaterialIndex[pass]=firstSolidColoredMaterial; else face.Verts[vert_counter].VertexMaterialIndex[pass] = MaterialDesc.Get_Vertex_Material_Index(mat_index,pass); } face.Verts[vert_counter].Attribute0 = max_vert_index; face.Verts[vert_counter].Attribute1 = face_index; /* ** Skin attachment */ face.Verts[vert_counter].BoneIndex = 0; if (is_skin) { int skin_bone_index = skindata->VertData[max_vert_index].BoneIdx[0]; // If this is a valid bone, try to find the corresponding bone index in the HTree if ( (skin_bone_index != -1) && (skin_bone_index < skinobj->Num_Bones()) && (skinobj->BoneTab[skin_bone_index] != NULL) ) { face.Verts[vert_counter].BoneIndex = get_htree_bone_index_for_inode(skinobj->BoneTab[skin_bone_index]); } } } Builder.Add_Face(face); } /* ** Process the mesh */ Builder.Build_Mesh(true); const MeshBuilderClass::MeshStatsStruct & stats = Builder.Get_Mesh_Stats(); int vcount = Builder.Get_Vertex_Count(); int pcount = mesh.numFaces; float vert_poly_ratio = (float)vcount / (float)pcount; ExportLog::printf(" triangle count: %d\n",pcount); ExportLog::printf(" final vertex count: %d\n",vcount); ExportLog::printf(" vertex/triangle ratio: %f\n",vert_poly_ratio); ExportLog::printf(" strip count: %d\n",stats.StripCount); ExportLog::printf(" average strip length: %f\n",stats.AvgStripLength); ExportLog::printf(" longest strip: %d\n",stats.MaxStripLength); } /*********************************************************************************************** * MeshSaveClass::get_skin_modifier_objects -- Searches for the WWSkin modifier for this mesh * * * * INPUT: * * * * OUTPUT: * * * * WARNINGS: * * * * HISTORY: * *=============================================================================================*/ void MeshSaveClass::get_skin_modifier_objects(SkinDataClass ** skin_data_ptr,SkinWSMObjectClass ** skin_obj_ptr) { *skin_data_ptr = NULL; *skin_obj_ptr = NULL; // loop through the references that our node has for (int i = 0; i < MaxINode->NumRefs(); i++) { ReferenceTarget *refTarg = MaxINode->GetReference(i); // if the reference is a WSM Derived Object. if (refTarg != NULL && refTarg->ClassID() == Class_ID(WSM_DERIVOB_CLASS_ID,0)) { IDerivedObject * wsm_der_obj = (IDerivedObject *)refTarg; // loop through the WSM's attached to this WSM Derived object for (int j = 0; j < wsm_der_obj->NumModifiers(); j++) { Modifier * mod = wsm_der_obj->GetModifier(j); if (mod->ClassID() == SKIN_MOD_CLASS_ID) { // This is our modifier! Get the data from it! SkinModifierClass * skinmod = (SkinModifierClass *)mod; ModContext * mc = wsm_der_obj->GetModContext(j); *skin_data_ptr = (SkinDataClass *)(mc->localData); *skin_obj_ptr = (SkinWSMObjectClass *)skinmod->GetReference(SkinModifierClass::OBJ_REF); } } } } } /*********************************************************************************************** * MeshSaveClass::get_htree_bone_index_for_inode -- searches the htree for the given INode * * * * INPUT: * * * * OUTPUT: * * * * WARNINGS: * * * * HISTORY: * * 5/1/2000 gth : Created. * *=============================================================================================*/ int MeshSaveClass::get_htree_bone_index_for_inode(INode * node) { // index of this INode in the hierarchy tree (it better be there :-) char w3dname[W3D_NAME_LEN]; Set_W3D_Name(w3dname,node->GetName()); int bindex = HTree->Find_Named_Node(w3dname); // If the desired bone isn't being exported, export the point // relative to the root. if (bindex == -1) { bindex = 0; } return bindex; } /*********************************************************************************************** * MeshSaveClass::inv_deform_mesh -- preprocess the mesh for skinning * * * * INPUT: * * * * OUTPUT: * * * * WARNINGS: * * * * HISTORY: * * 10/26/1997 GH : Created. * * 5/1/2000 gth : Created. * *=============================================================================================*/ void MeshSaveClass::inv_deform_mesh() { // Got the skinning info, now pre-deform each vertex and // create an array of vertex influence indexes. VertInfluences = new W3dVertInfStruct[Builder.Get_Vertex_Count()]; memset(VertInfluences,0,sizeof(W3dVertInfStruct) * Builder.Get_Vertex_Count()); Header.VertexChannels |= W3D_VERTEX_CHANNEL_BONEID; for (int vert_index = 0; vert_index < Builder.Get_Vertex_Count(); vert_index++) { MeshBuilderClass::VertClass & vert = Builder.Get_Vertex(vert_index); if (vert.BoneIndex == 0) { // set the influence to 0 (root node) and leave the vert and normal unchanged // (gth) 07/11/2000: Note that in this case, the mesh coordinates have already been // transformed relative to the user or scene origin and do not need to be further modified. VertInfluences[vert_index].BoneIdx = 0; } else { // here we go! get the matrix from the hierarchy tree and transform // the point into the bone's coordinate system. // (gth) 07/11/2000: The origin_offset_tm is no longer needed because // skin meshes are transformed into the origin coordinate system before // we get to this code. Matrix3 bonetm = HTree->Get_Node_Transform(vert.BoneIndex); Matrix3 invbonetm = Inverse(bonetm); Point3 pos; pos.x = vert.Position.X; pos.y = vert.Position.Y; pos.z = vert.Position.Z; pos = pos * invbonetm; vert.Position.X = pos.x; vert.Position.Y = pos.y; vert.Position.Z = pos.z; // Now, transform the normal into the bone's coordinate system. invbonetm.NoTrans(); Point3 norm; norm.x = vert.Normal.X; norm.y = vert.Normal.Y; norm.z = vert.Normal.Z; norm = norm * invbonetm; vert.Normal.X = norm.x; vert.Normal.Y = norm.y; vert.Normal.Z = norm.z; VertInfluences[vert_index].BoneIdx = vert.BoneIndex; } } } /*********************************************************************************************** * MeshSaveClass::Write_To_File -- Append the mesh to an open wtm file * * * * INPUT: * * csave - ChunkSaveClass object to handle writing the chunk-based file * * export_aabtree - should we generate an aabtree for this mesh * * * * OUTPUT: * * 0 if nothing went wrong, Non-Zero otherwise * * * * WARNINGS: * * * * * * HISTORY: * * 06/10/1997 GH : Created. * *=============================================================================================*/ int MeshSaveClass::Write_To_File(ChunkSaveClass & csave,bool export_aabtree) { if (!csave.Begin_Chunk(W3D_CHUNK_MESH)) { return 1; } if (write_header(csave) != 0) { return 1; } if (write_user_text(csave) != 0) { return 1; } if (write_verts(csave) != 0) { return 1; } if (write_vert_normals(csave) != 0) { return 1; } if (write_triangles(csave) != 0) { return 1; } if (write_vert_influences(csave) != 0) { return 1; } if (write_vert_shade_indices(csave) != 0) { return 1; } if (write_material_info(csave) != 0) { return 1; } if (write_vertex_materials(csave) != 0) { return 1; } if (PS2Material == TRUE) { // The ps2 shaders must be written out first. if (write_ps2_shaders(csave) != 0) { return 1; } } if (write_shaders(csave) != 0) { return 1; } if (write_textures(csave) != 0) { return 1; } for (int pass=0; pass 0); assert(Builder.Get_Vertex_Count() == (int)Header.NumVertices); for (int i=0; i= Header.Min.X); assert(vert.Position.Y >= Header.Min.Y); assert(vert.Position.Z >= Header.Min.Z); } W3dVectorStruct w3dvert; w3dvert.X = vert.Position.X; w3dvert.Y = vert.Position.Y; w3dvert.Z = vert.Position.Z; if (csave.Write(&(w3dvert),sizeof(W3dVectorStruct)) != sizeof(W3dVectorStruct)) { return 1; } } if (!csave.End_Chunk()) { return 1; } return 0; } /*********************************************************************************************** * MeshSaveClass::write_vert_normals -- Writes the surrender normal chunk into a wtm file * * * * INPUT: * * csave - chunk save object * * * * OUTPUT: * * 0 if nothing went wrong, Non-Zero otherwise * * * * WARNINGS: * * * * HISTORY: * * 06/10/1997 GH : Created. * *=============================================================================================*/ int MeshSaveClass::write_vert_normals(ChunkSaveClass & csave) { if (!(Header.VertexChannels & W3D_VERTEX_CHANNEL_NORMAL)) return 0; if (!csave.Begin_Chunk(W3D_CHUNK_VERTEX_NORMALS)) { return 1; } for (int i=0; i < Builder.Get_Vertex_Count(); i++) { const MeshBuilderClass::VertClass & vert = Builder.Get_Vertex(i); W3dVectorStruct norm; if (ExportOptions.Is_ZNormals_Enabled()) { norm.X = 0.0f; norm.Y = 0.0f; norm.Z = 1.0f; } else { norm.X = vert.Normal.X; norm.Y = vert.Normal.Y; norm.Z = vert.Normal.Z; } if (csave.Write(&(norm),sizeof(W3dVectorStruct)) != sizeof(W3dVectorStruct)) { return 1; } } if (!csave.End_Chunk()) { return 1; } return 0; } /*********************************************************************************************** * MeshSaveClass::write_vert_influences -- skins will have this chunk that binds verts to bones* * * * INPUT: * * csave - chunk save object * * * * OUTPUT: * * 0 if nothing went wrong, Non-Zero otherwise * * * * WARNINGS: * * * * HISTORY: * * 06/10/1997 GH : Created. * *=============================================================================================*/ int MeshSaveClass::write_vert_influences(ChunkSaveClass & csave) { if (((Header.Attributes & W3D_MESH_FLAG_GEOMETRY_TYPE_MASK) != W3D_MESH_FLAG_GEOMETRY_TYPE_SKIN) || !(Header.VertexChannels & W3D_VERTEX_CHANNEL_BONEID) || (VertInfluences == NULL)) { return 0; } if (!csave.Begin_Chunk(W3D_CHUNK_VERTEX_INFLUENCES)) { return 1; } int count = Builder.Get_Vertex_Count(); if (csave.Write(VertInfluences,count * sizeof(W3dVertInfStruct)) != count * sizeof(W3dVertInfStruct)) { return 1; } if (!csave.End_Chunk()) { return 1; } return 0; } /*********************************************************************************************** * MeshSaveClass::write_triangles -- write the triangles chunk * * * * INPUT: * * * * OUTPUT: * * * * WARNINGS: * * * * HISTORY: * * 4/7/98 GTH : Created. * *=============================================================================================*/ int MeshSaveClass::write_triangles(ChunkSaveClass & csave) { if (!csave.Begin_Chunk(W3D_CHUNK_TRIANGLES)) { return 1; } assert(Builder.Get_Face_Count() == (int)Header.NumTris); for (int i=0; i 0); if (PS2Material == TRUE) { // Make the PC shader as close to the PS2 shader as possible. // This will allow for viewing in W3D View. setup_PC_shaders_from_PS2_shaders(); } if (!csave.Begin_Chunk(W3D_CHUNK_SHADERS)) { return 1; } W3dShaderStruct * shader; for (int i=0; iFilename,strlen(map->Filename) + 1) != strlen(map->Filename) + 1) return 1; csave.End_Chunk(); // optionally write an animation info chunk if (map->AnimInfo != NULL) { csave.Begin_Chunk(W3D_CHUNK_TEXTURE_INFO); if (csave.Write(map->AnimInfo,sizeof(W3dTextureInfoStruct)) != sizeof(W3dTextureInfoStruct)) return 1; csave.End_Chunk(); } csave.End_Chunk(); } if (!csave.End_Chunk()) { return 1; } return 0; } int MeshSaveClass::write_pass(ChunkSaveClass & csave,int pass) { assert(pass >= 0); assert(pass < MaterialDesc.Pass_Count()); if (!csave.Begin_Chunk(W3D_CHUNK_MATERIAL_PASS)) { return 1; } const MeshBuilderClass::MeshStatsStruct & stats = Builder.Get_Mesh_Stats(); if (stats.HasVertexMaterial[pass]) { write_vertex_material_ids(csave,pass); } if (stats.HasShader[pass]) { write_shader_ids(csave,pass); } if (stats.HasDiffuseColor[pass] || DeformSave.Does_Deformer_Modify_DCG ()) { write_dcg(csave,pass); } for (int stage = 0; stage < MeshBuilderClass::MAX_STAGES; stage++) { write_texture_stage(csave,pass,stage); } if (!csave.End_Chunk()) { return 1; } return 0; } int MeshSaveClass::write_vertex_material_ids(ChunkSaveClass & csave,int pass) { const MeshBuilderClass::MeshStatsStruct & stats = Builder.Get_Mesh_Stats(); if (!csave.Begin_Chunk(W3D_CHUNK_VERTEX_MATERIAL_IDS)) { return 1; } uint32 matid; if (stats.HasPerVertexMaterial[pass]) { for (int i=0; i= MIN_AABTREE_POLYGONS) && ((Header.Attributes & W3D_MESH_FLAG_GEOMETRY_TYPE_MASK) == W3D_MESH_FLAG_GEOMETRY_TYPE_NORMAL) ) { /* ** Build temporary array representations of the mesh */ int vertcount = Builder.Get_Vertex_Count(); int polycount = Builder.Get_Face_Count(); Vector3 * verts = new Vector3[vertcount]; Vector3i * polys = new Vector3i[polycount]; for (int vi=0; viNumSubMtls() <= 1)) { MaterialRemapTable = new int[1]; MaterialRemapTable[0] = 0; return 1; } else { int sub_mtl_count = nodemtl->NumSubMtls(); MaterialRemapTable = new int[sub_mtl_count]; // Initialize each remap to -1 (indicates that the material is un-used) for (mat_index=0; mat_index 0); return matcount; } } //Check if material is solid, non-textured. int isTexturedMaterial(Mtl * mtl) { Texmap * tmap; tmap = mtl->GetSubTexmap(ID_DI); if (mtl->ClassID() == GameMaterialClassID) { GameMtl * gamemtl=(GameMtl *)mtl; for (int pass=0;passGet_Pass_Count(); pass++) { for (int stage=0; stage < W3dMaterialClass::MAX_STAGES; stage++) { if (gamemtl->Get_Texture_Enable(pass,stage) && gamemtl->Get_Texture(pass,stage)) return 1; //using a texture } } return 0; } else { return (tmap && tmap->ClassID() == Class_ID(BMTEX_CLASS_ID,0)); } } //count number of used solid materials (no texture) int MeshSaveClass::getNumSolidMaterials(Mtl * nodemtl) { int mat_index; int numSolid=0; if ((nodemtl == NULL) || (nodemtl->NumSubMtls() <= 1)) { //Check if diffuse texture present if (isTexturedMaterial(nodemtl)) return 00; return 1; } else { int sub_mtl_count = nodemtl->NumSubMtls(); for (mat_index=0; mat_indexGetSubMtl(mat_index))) continue; numSolid++; } } return numSolid; } } //Cancels out the material colors by setting them to white. //Also changes prefix on texture names to use DIFFUSE_HOUSECOLOR_TEXTURE_PREFIX //if house color is used. void MeshSaveClass::fix_diffuse_materials(bool isHouseColor) { uint8 color=255; char materialColorFilename[_MAX_FNAME + 1]; for (int mat_index=0; mat_indexFilename && (*((unsigned int *)map3d->Filename) & 0xffff00ff) == DIFFUSE_COLOR_TEXTURE_MASK) //check for 'TXC^' prefix { //found a material which had its material color replaced by a texture //set the material color to white so it's not being used. vmat->Diffuse.Set(color,color,color); vmat->Ambient.Set(color,color,color); if (isHouseColor && *(map3d->Filename+1) != 'H') { //our material texture contains house colors, adjust the name //by adding H prefix. sprintf(materialColorFilename,"%s%s","ZHCD",map3d->Filename+4); map3d->Set_Filename(materialColorFilename); } } } } } } /*********************************************************************************************** * MeshSaveClass::create_materials -- create the materials for this mesh * * * * INPUT: * * * * OUTPUT: * * * * WARNINGS: * * * * HISTORY: * * 10/26/1997 GH : Created. * * 2/8/99 GTH : modified to use the MaterialRemapTable * *=============================================================================================*/ void MeshSaveClass::create_materials(Mtl * nodemtl,DWORD wirecolor, char *materialColorTexture) { bool domaps = !use_simple_rendering(Header.Attributes); ////////////////////////////////////////////////////////////////////// // Create materials // Four cases: // - Creating a collision object: use a hard-coded material // - The material is null: use wire color // - The material is a simple material: create one material // - The material is a multi-material: create n materials ////////////////////////////////////////////////////////////////////// int geo_type = Header.Attributes & W3D_MESH_FLAG_GEOMETRY_TYPE_MASK; if ((geo_type == W3D_MESH_FLAG_GEOMETRY_TYPE_AABOX) || (geo_type == W3D_MESH_FLAG_GEOMETRY_TYPE_OBBOX)) { Header.NumMaterials = 1; W3dVertexMaterialStruct vmat; W3dShaderStruct shader; W3dMaterialClass material; Color diffuse; if (Header.Attributes & W3D_MESH_FLAG_COLLISION_TYPE_PHYSICAL) { diffuse.r = 0.0f; diffuse.g = 0.0f; diffuse.b = 1.0f; } else { diffuse.r = 0.4f; diffuse.g = 1.0f; diffuse.b = 0.4f; } W3d_Shader_Reset(&shader); W3d_Shader_Set_Dest_Blend_Func(&shader,W3DSHADER_DESTBLENDFUNC_ONE_MINUS_SRC_ALPHA); W3d_Shader_Set_Src_Blend_Func(&shader,W3DSHADER_SRCBLENDFUNC_SRC_ALPHA); W3d_Vertex_Material_Reset(&vmat); vmat.Opacity = 0.5f; // add material 0 vmat.Diffuse.R = (uint8)(diffuse.r * 255.0f); vmat.Diffuse.G = (uint8)(diffuse.g * 255.0f); vmat.Diffuse.B = (uint8)(diffuse.b * 255.0f); material.Set_Pass_Count(1); material.Set_Vertex_Material(vmat,0); material.Set_Shader(shader,0); MaterialDesc.Add_Material(material); assert(MaterialDesc.Material_Count() == 1); } else if (!nodemtl) { // Create a single material using the wire color Header.NumMaterials = 1; W3dVertexMaterialStruct vmat; W3dShaderStruct shader; W3dMaterialClass material; W3d_Vertex_Material_Reset(&vmat); W3d_Shader_Reset(&shader); vmat.Diffuse.R = GetRValue(wirecolor); vmat.Diffuse.G = GetGValue(wirecolor); vmat.Diffuse.B = GetBValue(wirecolor); material.Set_Pass_Count(1); material.Set_Vertex_Material(vmat,0); material.Set_Shader(shader,0); MaterialDesc.Add_Material(material,"WireColor"); assert(MaterialDesc.Material_Count() == 1); } else if (!nodemtl->IsMultiMtl()) { Header.NumMaterials = 1; W3dMaterialClass mat; if (isTexturedMaterial(nodemtl) == 0) mat.Init(nodemtl,materialColorTexture); else mat.Init(nodemtl,NULL); W3dMaterialDescClass::ErrorType err; err = MaterialDesc.Add_Material(mat,nodemtl->GetName()); if (err == W3dMaterialDescClass::MULTIPASS_TRANSPARENT) { sprintf(_string1,"Exporting Materials for Mesh: %s\nMaterial %s is multi-pass and transparent\nMulti-pass transparent materials are not allowed.\n", Header.MeshName, nodemtl->GetName()); ExportLog::printf(_string1); throw ErrorClass(_string1); } assert(MaterialDesc.Material_Count() == 1); } else { Header.NumMaterials = nodemtl->NumSubMtls(); W3dMaterialClass mat; for (unsigned mi = 0; mi < Header.NumMaterials; mi++) { // only process materials that were found to be used in the scan_used_materials call if (MaterialRemapTable[mi] != -1) { if (isTexturedMaterial(nodemtl->GetSubMtl(mi)) == 0) mat.Init(nodemtl->GetSubMtl(mi),materialColorTexture); else mat.Init(nodemtl->GetSubMtl(mi),NULL); char * name; W3dMaterialDescClass::ErrorType err; name = nodemtl->GetSubMtl(mi)->GetName(); err = MaterialDesc.Add_Material(mat,name); if (err == W3dMaterialDescClass::INCONSISTENT_PASSES) { sprintf(_string1,"Exporting Materials for Mesh: %s\nMaterial %s has %d passes.\nThe other materials have %d passes.\nAll Materials must have the same number of passes.\n", Header.MeshName, nodemtl->GetSubMtl(mi)->GetName(), mat.Get_Pass_Count(), MaterialDesc.Pass_Count()); ExportLog::printf(_string1); throw ErrorClass(_string1); } if (err == W3dMaterialDescClass::MULTIPASS_TRANSPARENT) { sprintf(_string1,"Exporting Materials for Mesh: %s\nMaterial %s is multi-pass and transparent\nMulti-pass transparent materials are not allowed.\n", Header.MeshName, nodemtl->GetSubMtl(mi)->GetName()); throw ErrorClass(_string1); } if (err == W3dMaterialDescClass::INCONSISTENT_SORT_LEVEL) { sprintf(_string1,"Exporting Materials for Mesh: %s\nMaterial %s does not have the same Static Sort Level as other materials used on the mesh.\nAll materials for a mesh must use the same Static Sort Level value.\n", Header.MeshName, nodemtl->GetSubMtl(mi)->GetName()); ExportLog::printf(_string1); throw ErrorClass(_string1); } } } } // Store the material's sort level in the mesh header. Header.SortLevel = MaterialDesc.Get_Sort_Level(); } /*********************************************************************************************** * MeshSaveClass::compute_bounding_volumes -- computes a bounding box and bounding sphere for * * * * INPUT: * * * * OUTPUT: * * * * WARNINGS: * * * * HISTORY: * * 08/01/1997 GH : Created. * *=============================================================================================*/ void MeshSaveClass::compute_bounding_volumes(void) { Vector3 min,max,center; float radius; Builder.Compute_Bounding_Box(&min,&max); Builder.Compute_Bounding_Sphere(¢er,&radius); Header.SphCenter.X = center.X; Header.SphCenter.Y = center.Y; Header.SphCenter.Z = center.Z; Header.SphRadius = radius; Header.Min.X = min.X; Header.Min.Y = min.Y; Header.Min.Z = min.Z; Header.Max.X = max.X; Header.Max.Y = max.Y; Header.Max.Z = max.Z; } /*********************************************************************************************** * MeshSaveClass::compute_physical_properties -- computes the volume and moment of inertia * * * * INPUT: * * * * OUTPUT: * * * * WARNINGS: * * * * HISTORY: * * 08/01/1997 GH : Created. * *=============================================================================================*/ void MeshSaveClass::compute_physical_constants ( INode * inode, Progress_Meter_Class & meter, bool voxelize ) { #if 0 // IF we need this again, move the data to a physics chunk (header doesn't have it anymore) if (voxelize) { // Create an INodeList object for this mesh AnyINodeFilter nodefilt; INodeListClass meshlist(CurTime,&nodefilt); meshlist.Insert(inode); // Create a Voxel object for this mesh VoxelClass * voxel = new VoxelClass ( meshlist, VOXEL_RESOLUTION, ExportSpace, CurTime, meter ); #if DEBUG_VOXELS VoxelDebugWindowClass dbgwin(voxel); dbgwin.Display_Window(); #endif double vol[1]; double cm[3]; double inertia[9]; voxel->Compute_Physical_Properties(vol,cm,inertia); Header.Volume = (float)vol[0]; Header.MassCenter.X = (float)cm[0]; Header.MassCenter.Y = (float)cm[1]; Header.MassCenter.Z = (float)cm[2]; Header.Inertia[0] = (float)inertia[0]; Header.Inertia[1] = (float)inertia[1]; Header.Inertia[2] = (float)inertia[2]; Header.Inertia[3] = (float)inertia[3]; Header.Inertia[4] = (float)inertia[4]; Header.Inertia[5] = (float)inertia[5]; Header.Inertia[6] = (float)inertia[6]; Header.Inertia[7] = (float)inertia[7]; Header.Inertia[8] = (float)inertia[8]; } else { // Set mass center to the center of the bounding box Header.MassCenter.X = (Header.Max.X + Header.Min.X) / 2.0f; Header.MassCenter.Y = (Header.Max.Y + Header.Min.Y) / 2.0f; Header.MassCenter.Z = (Header.Max.Z + Header.Min.Z) / 2.0f; // Set inertia tensor to inertia tensor of the bounding box // (gth) !!!! DO THIS !!!! Header.Inertia[0] = 1.0f; Header.Inertia[1] = 0.0f; Header.Inertia[2] = 0.0f; Header.Inertia[3] = 0.0f; Header.Inertia[4] = 1.0f; Header.Inertia[5] = 0.0f; Header.Inertia[6] = 0.0f; Header.Inertia[7] = 0.0f; Header.Inertia[8] = 1.0f; Header.Volume = (Header.Max.X - Header.Min.X) * (Header.Max.Y - Header.Min.Y) * (Header.Max.Z - Header.Min.Z); } #endif } /*********************************************************************************************** * MeshSaveClass::prep_mesh -- pre-transform the MAX mesh by a specified matrix * * * * INPUT: * * * * OUTPUT: * * * * WARNINGS: * * * * HISTORY: * * 08/01/1997 GH : Created. * *=============================================================================================*/ void MeshSaveClass::prep_mesh(Mesh & mesh,Matrix3 & objoff) { int vert_index; // Transform the mesh's vertices so that they are relative to the coordinate // system that we want to use with the mesh for (vert_index = 0; vert_index < mesh.getNumVerts (); vert_index++) { mesh.verts[vert_index] = mesh.verts[vert_index] * objoff; } // Re-Build the normals. mesh.buildNormals(); }