/* ** 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 . */ /*********************************************************************************************** *** 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 : WW3D * * * * $Archive:: /Commando/Code/Tools/max2w3d/meshbuild.cpp $* * * * Author:: Greg_h * * * * $Modtime:: 11/02/00 6:28p $* * * * $Revision:: 15 $* * * *---------------------------------------------------------------------------------------------* * Functions: * * MeshBuilderClass::VertClass::Reset -- reset this vertex * * MeshBuilderClass::FaceClass::Reset -- rest this face * * MeshBuilderClass::FaceClass::Compute_Plane -- compute the plane for this face * * MeshBuilderClass::FaceClass::Is_Degenerate -- check if a face is degenerate * * MeshBuilderClass::MeshStatsStruct::Reset -- reset the stats to all false * * MeshBuilderClass::MeshBuilderClass -- constructor * * MeshBuilderClass::~MeshBuilderClass -- destructor * * MeshBuilderClass::Free -- release all memory in use * * MeshBuilderClass::Reset -- Get the builder ready to process a mesh * * MeshBuilderClass::Add_Face -- Add a face to the mesh * * MeshBuilderClass::Build_Mesh -- process the mesh * * MeshBuilderClass::Compute_Face_Normals -- computes all of the face normals from the index * * MeshBuilderClass::Verify_Face_Normals -- checks if any faces have flipped * * MeshBuilderClass::Compute_Vertex_Normals -- Computes the vertex normals for the mesh * * MeshBuilderClass::Remove_Degenerate_Faces -- discard invalid or duplicated faces * * MeshBuilderClass::Compute_Mesh_Stats -- compute some stats about the mesh * * MeshBuilderClass::Compute_Bounding_Box -- computes an axis-aligned bounding box for the m * * MeshBuilderClass::Compute_Bounding_Sphere -- computes a bounding sphere for the mesh * * MeshBuilderClass::Optimize_Mesh -- "optimize" the mesh * * MeshBuilderClass::Strip_Optimize_Mesh -- optimize the mesh for triangle strips * * MeshBuilderClass::Grow_Face_Array -- increases the size of the face array * * MeshBuilderClass::Sort_Vertices -- Sorts vertices according to the given key array * * MeshBuilderClass::Sort_Vertices_By_Bone_Index -- sorts verts by bone index * * MeshBuilderClass::Sort_Vertices_By_Vertex_Material -- sorts verts by vertex mtl in pass0 * * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ #include "meshbuild.h" #include "uarray.h" #include #include #include const float EPSILON = 0.0001f; /* ** qsort compare functions */ static int face_material_compare(const void *elem1, const void *elem2); static int pass0_stage0_compare(const void *elem1, const void *elem2); static int pass0_stage1_compare(const void *elem1, const void *elem2); static int pass1_stage0_compare(const void *elem1, const void *elem2); static int pass1_stage1_compare(const void *elem1, const void *elem2); static int pass2_stage0_compare(const void *elem1, const void *elem2); static int pass2_stage1_compare(const void *elem1, const void *elem2); static int pass3_stage0_compare(const void *elem1, const void *elem2); static int pass3_stage1_compare(const void *elem1, const void *elem2); static int vertex_compare(const void *elem1, const void *elem2); typedef int (*COMPARE_FUNC_TYPE)(const void * elem1,const void * elem2); COMPARE_FUNC_TYPE Texture_Compare_Funcs[MeshBuilderClass::MAX_PASSES][MeshBuilderClass::MAX_STAGES] = { { pass0_stage0_compare, pass0_stage1_compare }, { pass1_stage0_compare, pass1_stage1_compare }, { pass2_stage0_compare, pass2_stage1_compare }, { pass3_stage0_compare, pass3_stage1_compare }, }; /************************************************************************************ ** ** FaceHasherClass, support class for the unique faces hash table. The unique ** faces table is going to detect exactly duplicated faces and discard them. It ** appears that the artists occasionally accidentally duplicate a face which causes ** problems in the stripping algorithm... ** ************************************************************************************/ class FaceHasherClass : public HashCalculatorClass { public: virtual bool Items_Match(const MeshBuilderClass::FaceClass & a, const MeshBuilderClass::FaceClass & b) { // Note: if we want this to detect duplicates that are "rotated", must change // both this function and the Compute_Hash function... return ( (a.VertIdx[0] == b.VertIdx[0]) && (a.VertIdx[1] == b.VertIdx[1]) && (a.VertIdx[2] == b.VertIdx[2]) ); } virtual void Compute_Hash(const MeshBuilderClass::FaceClass & item) { HashVal = (int)(item.VertIdx[0]*12345.6f + item.VertIdx[1]*1714.38484f + item.VertIdx[2]*27561.3f)&1023; } virtual int Num_Hash_Bits(void) { return 10; } virtual int Num_Hash_Values(void) { return 1; } virtual int Get_Hash_Value(int /*index*/) { return HashVal; } private: int HashVal; }; /************************************************************************************ ** ** VertexArray, build an array of unique vertices. Can't use the UniqueArray ** template due to the special considerations needed to properly handle smoothing ** groups... (DAMN!!! this was the reason I *WROTE* UniqueArray, oh well :-) ** ************************************************************************************/ class VertexArrayClass { public: enum { HASH_TABLE_SIZE = 4096, }; VertexArrayClass(int maxsize,int match_normals = 0) { Verts = NULL; assert(maxsize > 0); Verts = new MeshBuilderClass::VertClass[maxsize]; assert(Verts); VertCount = 0; UVSplits = 0; HashTable = new MeshBuilderClass::VertClass * [HASH_TABLE_SIZE]; memset(HashTable,0,sizeof(MeshBuilderClass::VertClass *) * HASH_TABLE_SIZE); MatchNormals = match_normals; // initialize the center and extent to do nothing to the points input Center.Set(0.0f,0.0f,0.0f); Extent.Set(1.0f,1.0f,1.0f); } ~VertexArrayClass(void) { delete[] Verts; delete[] HashTable; } void VertexArrayClass::Set_Bounds(const Vector3 & minv,const Vector3 & maxv) { Extent = (maxv - minv) / 2.0f; Center = (maxv + minv) / 2.0f; } int Submit_Vertex(const MeshBuilderClass::VertClass & vert) { // 2D floating point hashing... unsigned int lasthash = 0xFFFFFFFF; unsigned int hash; unsigned int shadeindex = 0xFFFFFFFF; // transform the position of the point into the range // -1 < p < 1 as defined by the center and extent. // aja/ehc 19991005 - Handle the case where an extent is zero. Vector3 position = (vert.Position - Center); double xstart; if(fabs(Extent.X) > EPSILON) xstart = (vert.Position.X - Center.X) / Extent.X; else xstart = Center.X; double ystart; if(fabs(Extent.Y) > EPSILON) ystart = (vert.Position.Y - Center.Y) / Extent.Y; else ystart = Center.Y; double x,y; for (x = xstart - EPSILON; x <= xstart + EPSILON + 0.0000001; x+= EPSILON) { for (y = ystart - EPSILON; y <= ystart + EPSILON + 0.000001; y+= EPSILON) { hash = compute_hash((float)x,(float)y); if (hash != lasthash) { MeshBuilderClass::VertClass * test_vert = HashTable[hash]; while (test_vert) { if (Verts_Shading_Match(vert,*test_vert)) { if (shadeindex == 0xFFFFFFFF) { shadeindex = test_vert->UniqueIndex; // mask the "master" smoothed vertex's smoothing group with our // face's smoothing group since we are going to be smoothed with it. Verts[shadeindex].SharedSmGroup &= vert.SmGroup; } } if (Verts_Match(vert,*test_vert)) { return test_vert->UniqueIndex; } test_vert = test_vert->NextHash; } } lasthash = hash; } } // Not found, add to the end of the array int newindex = VertCount; VertCount++; Verts[newindex] = vert; Verts[newindex].UniqueIndex = newindex; if (shadeindex == 0xFFFFFFFF) { Verts[newindex].ShadeIndex = newindex; // This is a new vertex,so store the face's smoothing group into SharedSmGroup Verts[newindex].SharedSmGroup = Verts[newindex].SmGroup; } else { Verts[newindex].ShadeIndex = shadeindex; } // This is a new vertex, store the face's smoothing group with it //Verts[newindex].SmoothingGroup = face_smooth_group; // And add to the hash table x = (vert.Position.X - Center.X) / Extent.X; y = (vert.Position.Y - Center.Y) / Extent.Y; hash = compute_hash((float)x,(float)y); Verts[newindex].NextHash = HashTable[hash]; HashTable[hash] = &Verts[newindex]; return newindex; } int Verts_Match(const MeshBuilderClass::VertClass & v0,const MeshBuilderClass::VertClass & v1) { // if user is specifying unique id's, they must match: if (v0.Id != v1.Id) return 0; // position must match: float dp = (v0.Position - v1.Position).Length(); if (dp > EPSILON) return 0; // normal or smoothing group must match: if (MatchNormals == 0) { int smgroup_match = ((v0.SmGroup & v1.SmGroup) || (v0.SmGroup == v1.SmGroup)); if (!smgroup_match) return 0; } else { float dn = (v0.Normal - v1.Normal).Length(); if (dn > EPSILON) return 0; } // colors, and material id's must match for all passes for (int pass=0; pass < MeshBuilderClass::MAX_PASSES; pass++) { if (v0.DiffuseColor[pass] != v1.DiffuseColor[pass]) return 0; if (v0.SpecularColor[pass] != v1.SpecularColor[pass]) return 0; if (v0.DiffuseIllumination[pass] != v1.DiffuseIllumination[pass]) return 0; if (v0.Alpha[pass] != v1.Alpha[pass]) return 0; if (v0.VertexMaterialIndex[pass] != v1.VertexMaterialIndex[pass]) return 0; } // texcoords must match for all passes and stages // Note: I'm checking them separately and last so that I can keep track // of how many splits are caused solely by u-v discontinuities... for (pass=0; pass 0.0000001f) { ok = false; } } return ok; } /*********************************************************************************************** * MeshBuilderClass::Compute_Vertex_Normals -- Computes the vertex normals for the mesh * * * * INPUT: * * * * OUTPUT: * * * * WARNINGS: * * This function should only be called after the mesh has been "optimized". The algorithm * * relies on non-smooth vertices having been split... * * * * HISTORY: * * 5/15/98 GTH : Created. * *=============================================================================================*/ void MeshBuilderClass::Compute_Vertex_Normals(void) { int vertidx; int faceidx; int facevertidx; /* ** First, zero all vertex normals */ for (vertidx = 0; vertidx < VertCount; vertidx++) { Verts[vertidx].Normal.Set(0,0,0); } /* ** Now, go through all of the faces, accumulating the face normals into the ** "first" vertices containing the appropriate vertex normal for each vertex. */ for (faceidx = 0; faceidx < FaceCount; faceidx++) { for (facevertidx = 0; facevertidx < 3; facevertidx++) { int vertindex = Faces[faceidx].VertIdx[facevertidx]; int shadeindex = Verts[vertindex].ShadeIndex; Verts[shadeindex].Normal += Faces[faceidx].Normal; } } /* ** Smooth this mesh with neighboring meshes! */ if (WorldInfo != NULL && WorldInfo->Are_Meshes_Smoothed ()) { for (vertidx = 0; vertidx < VertCount; vertidx++) { if (Verts[vertidx].ShadeIndex == vertidx) { Verts[vertidx].Normal += WorldInfo->Get_Shared_Vertex_Normal(Verts[vertidx].Position, Verts[vertidx].SharedSmGroup); } } } /* ** Propogate the accumulated normals to all of the other verts which share them */ for (vertidx = 0; vertidx < VertCount; vertidx++) { int shadeindex = Verts[vertidx].ShadeIndex; Verts[vertidx].Normal = Verts[shadeindex].Normal; Verts[vertidx].Normal.Normalize(); } } /*********************************************************************************************** * MeshBuilderClass::Remove_Degenerate_Faces -- discard invalid or duplicated faces * * * * INPUT: * * * * OUTPUT: * * * * WARNINGS: * * * * HISTORY: * * 7/10/98 GTH : Created. * *=============================================================================================*/ void MeshBuilderClass::Remove_Degenerate_Faces(void) { int faceidx; FaceHasherClass facehasher; UniqueArrayClass uniquefaces(FaceCount,FaceCount/4,&facehasher); for (faceidx = 0; faceidx < FaceCount; faceidx++) { if (!Faces[faceidx].Is_Degenerate()) { uniquefaces.Add(Faces[faceidx]); } } FaceCount = uniquefaces.Count(); AllocFaceCount = uniquefaces.Count(); CurFace = FaceCount; delete[] Faces; Faces = new FaceClass[AllocFaceCount]; for (faceidx = 0; faceidx < FaceCount; faceidx++) { Faces[faceidx] = uniquefaces.Get(faceidx); } } /*********************************************************************************************** * MeshBuilderClass::Compute_Mesh_Stats -- compute some stats about the mesh * * * * INPUT: * * * * OUTPUT: * * * * WARNINGS: * * * * HISTORY: * * 10/19/98 GTH : Created. * *=============================================================================================*/ void MeshBuilderClass::Compute_Mesh_Stats(void) { int pass; int stage; int face_index; int vert_index; int tex_index[MAX_PASSES][MAX_STAGES]; int shader_index[MAX_PASSES]; int vmat_index[MAX_PASSES]; Stats.Reset(); for (pass = 0; passX = Verts[0].Position.X; set_min->Y = Verts[0].Position.Y; set_min->Z = Verts[0].Position.Z; set_max->X = Verts[0].Position.X; set_max->Y = Verts[0].Position.Y; set_max->Z = Verts[0].Position.Z; for (i=0; iX) set_min->X = Verts[i].Position.X; if (Verts[i].Position.Y < set_min->Y) set_min->Y = Verts[i].Position.Y; if (Verts[i].Position.Z < set_min->Z) set_min->Z = Verts[i].Position.Z; if (Verts[i].Position.X > set_max->X) set_max->X = Verts[i].Position.X; if (Verts[i].Position.Y > set_max->Y) set_max->Y = Verts[i].Position.Y; if (Verts[i].Position.Z > set_max->Z) set_max->Z = Verts[i].Position.Z; } } /*********************************************************************************************** * MeshBuilderClass::Compute_Bounding_Sphere -- computes a bounding sphere for the mesh * * * * INPUT: * * * * OUTPUT: * * * * WARNINGS: * * * * HISTORY: * * 10/29/98 GTH : Created. * *=============================================================================================*/ void MeshBuilderClass::Compute_Bounding_Sphere(Vector3 * set_center,float * set_radius) { int i; double dx,dy,dz; // bounding sphere // Using the algorithm described in Graphics Gems I page 301. // This algorithm supposedly generates a bounding sphere within // 5% of the optimal one but is much faster and simpler to implement. Vector3 xmin(Verts[0].Position.X,Verts[0].Position.Y,Verts[0].Position.Z); Vector3 xmax(Verts[0].Position.X,Verts[0].Position.Y,Verts[0].Position.Z); Vector3 ymin(Verts[0].Position.X,Verts[0].Position.Y,Verts[0].Position.Z); Vector3 ymax(Verts[0].Position.X,Verts[0].Position.Y,Verts[0].Position.Z); Vector3 zmin(Verts[0].Position.X,Verts[0].Position.Y,Verts[0].Position.Z); Vector3 zmax(Verts[0].Position.X,Verts[0].Position.Y,Verts[0].Position.Z); // FIRST PASS: // finding the 6 minima and maxima points for (i=1; i xmax.X) { xmax.X = Verts[i].Position.X; xmax.Y = Verts[i].Position.Y; xmax.Z = Verts[i].Position.Z; } if (Verts[i].Position.Y < ymin.Y) { ymin.X = Verts[i].Position.X; ymin.Y = Verts[i].Position.Y; ymin.Z = Verts[i].Position.Z; } if (Verts[i].Position.Y > ymax.Y) { ymax.X = Verts[i].Position.X; ymax.Y = Verts[i].Position.Y; ymax.Z = Verts[i].Position.Z; } if (Verts[i].Position.Z < zmin.Z) { zmin.X = Verts[i].Position.X; zmin.Y = Verts[i].Position.Y; zmin.Z = Verts[i].Position.Z; } if (Verts[i].Position.Z > zmax.Z) { zmax.X = Verts[i].Position.X; zmax.Y = Verts[i].Position.Y; zmax.Z = Verts[i].Position.Z; } } // xspan = distance between the 2 points xmin and xmax squared. // same goes for yspan and zspan. dx = xmax.X - xmin.X; dy = xmax.Y - xmin.Y; dz = xmax.Z - xmin.Z; double xspan = dx*dx + dy*dy + dz*dz; dx = ymax.X - ymin.X; dy = ymax.Y - ymin.Y; dz = ymax.Z - ymin.Z; double yspan = dx*dx + dy*dy + dz*dz; dx = zmax.X - zmin.X; dy = zmax.Y - zmin.Y; dz = zmax.Z - zmin.Z; double zspan = dx*dx + dy*dy + dz*dz; // Set points dia1 and dia2 to the maximally separated pair // This will be the diameter of the initial sphere Vector3 dia1 = xmin; Vector3 dia2 = xmax; double maxspan = xspan; if (yspan > maxspan) { maxspan = yspan; dia1 = ymin; dia2 = ymax; } if (zspan > maxspan) { maxspan = zspan; dia1 = zmin; dia2 = zmax; } // Compute initial center and radius and radius squared Vector3 center; center.X = (dia1.X + dia2.X) / 2.0f; center.Y = (dia1.Y + dia2.Y) / 2.0f; center.Z = (dia1.Z + dia2.Z) / 2.0f; dx = dia2.X - center.X; dy = dia2.Y - center.Y; dz = dia2.Z - center.Z; double radsqr = dx*dx + dy*dy + dz*dz; double radius = sqrt(radsqr); // SECOND PASS: // Increment current sphere if any points fall outside of it. for (i=0; i radsqr) { // this point was outside the old sphere, compute a new // center point and radius which contains this point double testrad = sqrt(testrad2); // adjust center and radius radius = (radius + testrad) / 2.0; radsqr = radius * radius; double oldtonew = testrad - radius; center.X = (radius * center.X + oldtonew * Verts[i].Position.X) / testrad; center.Y = (radius * center.Y + oldtonew * Verts[i].Position.Y) / testrad; center.Z = (radius * center.Z + oldtonew * Verts[i].Position.Z) / testrad; } } *set_center = center; *set_radius = radius; } /*********************************************************************************************** * MeshBuilderClass::Optimize_Mesh -- "optimize" the mesh * * * * Generates the array of unique vertices and sets the vertex indices in each face. Then * * all of the faces are sorted by material and then into stripping order. * * * * INPUT: * * * * OUTPUT: * * * * WARNINGS: * * * * HISTORY: * * 5/15/98 GTH : Created. * *=============================================================================================*/ void MeshBuilderClass::Optimize_Mesh(bool compute_normals) { int faceidx; int vertidx; int facevertidx; int match_normals = 0; if (!compute_normals) { match_normals = 1; } VertexArrayClass unique_verts(FaceCount * 3,match_normals); /* ** Find the min and max of the array of vertices */ Vector3 minv = Faces[0].Verts[0].Position; Vector3 maxv = Faces[0].Verts[0].Position; for (faceidx = 0; faceidx < FaceCount; faceidx++) { for (facevertidx = 0; facevertidx < 3; facevertidx++) { minv.Update_Min(Faces[faceidx].Verts[facevertidx].Position); maxv.Update_Max(Faces[faceidx].Verts[facevertidx].Position); } } /* ** Tell the vertex array the bounds so that it can do better hashing. */ unique_verts.Set_Bounds(minv,maxv); /* ** Build the array of unique vertices */ for (faceidx = 0; faceidx < FaceCount; faceidx++) { for (facevertidx = 0; facevertidx < 3; facevertidx++) { Faces[faceidx].VertIdx[facevertidx] = unique_verts.Submit_Vertex(Faces[faceidx].Verts[facevertidx]); } } /* ** Assign the shared smoothing groups from each 'master' vertex ** to each referring vertex. */ unique_verts.Propogate_Shared_Smooth_Groups(); /* ** Replace the vertex array with the new unique vertex array */ VertCount = unique_verts.VertCount; Verts = new VertClass[VertCount]; for (vertidx=0; vertidx v1) { int tmp = v0; v0 = v1; v1 = tmp; } // hash value for the edge hash = (v0 + v1*119)&511; // seek edge from hash table for (edge = edgehash[hash]; edge; edge = edge->Next) { if (edge->Vertex[0] == v0 && edge->Vertex[1] == v1 && edge->MaterialIdx == mat) break; } if (edge) { // found the edge edge->Poly[1] = i; } else { // create new edge and link it to hash table edge = edgetab + edgecount++; edge->Next = edgehash[hash]; edgehash[hash] = edge; edge->Vertex[0] = v0; edge->Vertex[1] = v1; edge->Poly[0] = i; edge->Poly[1] = -1; edge->MaterialIdx = mat; } pedges[i].Edge[j] = edge; } } // the following loop inserts polygons into a new polygon list until // all polygons have been inserted. Internally it attempts to create // as long strips as possible while minimizing material switches // and maintaining vertex reusage coherency to optimize geometry // engine performance. while (polysinserted < FaceCount) { int startpoly = -1; // best polygon found so far int bestc = (1<<29); // best polygon weight value found so far int startpass = 0; // should we start from pass 0 or 1? int findpass; // 0 = same material only, 1 = any polygon int c; // c = number of shared edges // first attempt to minimize material switches by choosing a polygon with same material // as the starting poly. Basically what we want is the poly with same material with // least shared edges and most recent vertices (as we'd like to start the strip from // a 'corner polygon'). This pass is done only if mesh has multiple materials. // The second pass scans through all polygons. // this loop is O(N*N) -> might turn a bit nasty on larger meshes. Try // to figure out a way to solve the problem... for (findpass = startpass; findpass < 2; findpass++) { // loop through all polygons for (i = 0; i < FaceCount; i++) { // if polygon not picked yet if (premap[i]==-1) { // if material mismatch, cannot choose poly (except in pass 1) if (findpass == 0 && Faces[i].TextureIndex[PolyOrderPass][PolyOrderStage] != lastmat) continue; // calculate number of shared edges for (c = 0, j = 0; j < 3; j++) { // if edge j is shared by two tris, // use a weight factor of vCount for each edge if (pedges[i].Edge[j]->Poly[1] >= 0) { c += (vcount+1); } } // calculate delta vertex timestamp for (j = 0; j < 3; j++) { c += (vcount-vtimestamp[Faces[i].VertIdx[j]]); } // if better than current best pick if (c < bestc) { bestc = c; startpoly = i; } } } // if we managed to find a suitable starting poly if (startpoly != -1) break; } // track the fact that we created a new strip: Stats.StripCount++; // update the material index lastmat = Faces[startpoly].TextureIndex[PolyOrderPass][PolyOrderStage]; newmat[polysinserted] = Faces[startpoly].TextureIndex[PolyOrderPass][PolyOrderStage]; // Add the selected polygon to the new polygon list. // for each edge of start poly, see if the "other" polygon using that edge // is untaken, if so, store the new polygon such that that edge index is last bool found_shared_edge = false; newpolys[polysinserted] = Faces[startpoly]; FaceClass * newpoly = &(newpolys[polysinserted]); for (int edge_index = 0; (edge_index < 3) && !found_shared_edge; edge_index++) { for (int side_index = 0; (side_index < 2) && !found_shared_edge; side_index++) { // if this polygon is not the startpoly and it is not "taken", then this edge is ok! int poly = pedges[startpoly].Edge[edge_index]->Poly[side_index]; if ((poly != -1) && (poly != startpoly) && (premap[poly] == -1)) { // find vert which is not on the final edge int first_vert = -1; for (int vidx=0; vidx<3; vidx++) { if ( (newpoly->VertIdx[vidx] != pedges[startpoly].Edge[edge_index]->Vertex[0]) && (newpoly->VertIdx[vidx] != pedges[startpoly].Edge[edge_index]->Vertex[1])) { first_vert = newpoly->VertIdx[vidx]; break; } } assert(first_vert != -1); // rotate the vertex indices until first_vert is the index of VertIdx[0] while (newpoly->VertIdx[0] != first_vert) { int tmp = newpoly->VertIdx[0]; newpoly->VertIdx[0] = newpoly->VertIdx[1]; newpoly->VertIdx[1] = newpoly->VertIdx[2]; newpoly->VertIdx[2] = tmp; } // ok, we found a shareable edge and adjusted our poly so the strip // will start with it. Now break out of this loop... found_shared_edge = true; break; } } } // if a shared edge wasn't found, just copy the poly if (!found_shared_edge) { newpolys[polysinserted] = Faces[startpoly]; } // Increment the count. Mark the vertices as used (i.e. update their timestamps) premap[startpoly] = polysinserted; polysinserted++; for (i = 0; i < 3; i++) { if (vtimestamp[Faces[startpoly].VertIdx[i]]==-1) { vtimestamp[Faces[startpoly].VertIdx[i]] = vcount++; } } // If we have no shared edges, this is a lone poly, start another strip if (pedges[startpoly].Edge[0]->Poly[1] == -1 && pedges[startpoly].Edge[1]->Poly[1] == -1 && pedges[startpoly].Edge[2]->Poly[1] == -1) continue; // Build the strip starting from the polygon chosen in the previous loop int vFIFO[2]; // vertex fifo int scnt = 0; // strip index count (for poly order flipping) int nextpoly = startpoly; vFIFO[0] = newpoly->VertIdx[1]; // setup the vFIFO vFIFO[1] = newpoly->VertIdx[2]; while (nextpoly != -1) { startpoly = nextpoly; nextpoly = -1; for (i = 0; i < 3; i++) { // if edge 'i' of startpoly matches the vertices in the fifo if ((pedges[startpoly].Edge[i]->Vertex[0] == vFIFO[0] && pedges[startpoly].Edge[i]->Vertex[1] == vFIFO[1]) || (pedges[startpoly].Edge[i]->Vertex[1] == vFIFO[0] && pedges[startpoly].Edge[i]->Vertex[0] == vFIFO[1])) { for (j = 0; j < 2; j++) { // if poly 'j' attached to this edge has not been used already, use it! if (pedges[startpoly].Edge[i]->Poly[j] > -1 && premap[pedges[startpoly].Edge[i]->Poly[j]] == -1) { nextpoly = pedges[startpoly].Edge[i]->Poly[j]; goto found; } } } } // couldn't find another poly, break from loop break; found:; // now, find the new vertex (two verts are on the edge, find the third) int nw = -1; for (i = 0; i < 3; i++) { if (Faces[nextpoly].VertIdx[i] != vFIFO[0] && Faces[nextpoly].VertIdx[i] != vFIFO[1]) { nw = i; break; } } assert(nw != -1); int new_vindex = Faces[nextpoly].VertIdx[nw]; newmat[polysinserted] = Faces[nextpoly].TextureIndex[PolyOrderPass][PolyOrderStage]; // add the poly to the newpolys array. newpolys[polysinserted].VertIdx[0] = vFIFO[0]; newpolys[polysinserted].VertIdx[1] = vFIFO[1]; newpolys[polysinserted].VertIdx[2] = new_vindex; // if we are on an even triangle, swap the vertex ordering if (!(scnt&1)) { int tmp = newpolys[polysinserted].VertIdx[0]; newpolys[polysinserted].VertIdx[0] = newpolys[polysinserted].VertIdx[1]; newpolys[polysinserted].VertIdx[1] = tmp; } // push the new vertex into the fifo vFIFO[0] = vFIFO[1]; vFIFO[1] = new_vindex; if (vtimestamp[new_vindex]==-1) { vtimestamp[new_vindex] = vcount++; } premap[nextpoly] = polysinserted++; scnt++; } // scnt+1 is the number of polys that were added to the strip Stats.AvgStripLength += scnt+1; if (scnt+1 > Stats.MaxStripLength) { Stats.MaxStripLength = scnt+1; } } // Use the premap array to get the rest of the info into the new face table, for (i=0; iTextureIndex[0][0] < f1->TextureIndex[0][0]) return -1; if (f0->TextureIndex[0][0] > f1->TextureIndex[0][0]) return 1; return 0; } inline int tex_compare(const void * elem1,const void * elem2,int pass,int stage) { MeshBuilderClass::FaceClass * f0 = (MeshBuilderClass::FaceClass *)elem1; MeshBuilderClass::FaceClass * f1 = (MeshBuilderClass::FaceClass *)elem2; /* ** Primarily, sort by texture */ if (f0->TextureIndex[pass][stage] < f1->TextureIndex[pass][stage]) return -1; if (f0->TextureIndex[pass][stage] > f1->TextureIndex[pass][stage]) return 1; /* ** Secondarily, sort by vertex material index */ if (f0->Verts[0].VertexMaterialIndex[pass] < f1->Verts[0].VertexMaterialIndex[pass]) return -1; if (f0->Verts[0].VertexMaterialIndex[pass] > f1->Verts[0].VertexMaterialIndex[pass]) return 1; return 0; } int pass0_stage0_compare(const void *elem1, const void *elem2) { return tex_compare(elem1,elem2,0,0); } int pass0_stage1_compare(const void *elem1, const void *elem2) { return tex_compare(elem1,elem2,0,1); } int pass1_stage0_compare(const void *elem1, const void *elem2) { return tex_compare(elem1,elem2,1,0); } int pass1_stage1_compare(const void *elem1, const void *elem2) { return tex_compare(elem1,elem2,1,1); } int pass2_stage0_compare(const void *elem1, const void *elem2) { return tex_compare(elem1,elem2,2,0); } int pass2_stage1_compare(const void *elem1, const void *elem2) { return tex_compare(elem1,elem2,2,1); } int pass3_stage0_compare(const void *elem1, const void *elem2) { return tex_compare(elem1,elem2,3,0); } int pass3_stage1_compare(const void *elem1, const void *elem2) { return tex_compare(elem1,elem2,3,1); } int vertex_compare(const void *elem1, const void *elem2) { MeshBuilderClass::VertClass * v0 = (MeshBuilderClass::VertClass *)elem1; MeshBuilderClass::VertClass * v1 = (MeshBuilderClass::VertClass *)elem2; /* ** Sort first by bone index, then by vertex material in pass0 */ if (v0->BoneIndex < v1->BoneIndex) return -1; if (v0->BoneIndex > v1->BoneIndex) return 1; if (v0->VertexMaterialIndex[0] < v1->VertexMaterialIndex[0]) return -1; if (v0->VertexMaterialIndex[0] > v1->VertexMaterialIndex[0]) return 1; return 0; }