Automatically merging vertices

I would like to know, where I can find the code for automatically merging vertices.

Instead of two vertices, which share exactly the same position, logical I need one, right?



I heard there is already something for collada, so I want to ask, if it's possible to use this on other model importers like md5 and 3ds?

Thanks!

scene worker has this functionality…

i've tested it on 3ds imported models and it seems to work…

here's the code to do it…



public class TriStruct {
   // indices into the float buffer for the triangle points...divide by 3 to get the indices into the index buffer...
   public   int         mia_indices[] = new int[3];
   public   Triangle   mcl_tri;
   
   public   TriStruct() {
      
   }
   
   final   public   int      vertexIndexInTri(int i_index) {
      if(mia_indices[0] == i_index) {
         return 0;
      }
      
      if(mia_indices[1] == i_index) {
         return 1;
      }
      
      if(mia_indices[2] == i_index) {
         return 2;
      }
      
      return -1;
   }

}




final  static  private     int     offsetInVertexBuffer(Vector3f cl_v, ArrayList<Vector3f> cla_vertexBuffer) {
      int        li_cnt = 0;

      for(Vector3f lcl_v : cla_vertexBuffer) {
         if((lcl_v.x == cl_v.x) && (lcl_v.y == cl_v.y) && (lcl_v.z == cl_v.z)) {
            return li_cnt;
         }
         li_cnt++;
      }

      return -1;
   }




final  static  private     TriStruct[]     buildTriStructArrayFromMesh(TriMesh cl_mesh) {
      FloatBuffer     lcla_vertices = cl_mesh.getVertexBuffer();
      IntBuffer       lcla_indices = cl_mesh.getIndexBuffer();

      lcla_vertices.rewind();
      lcla_indices.rewind();

      TriStruct       lcla_tris[] = new TriStruct[lcla_indices.capacity() / 3];

      Vector3f        lcl_a = new Vector3f();
      Vector3f        lcl_b = new Vector3f();
      Vector3f        lcl_c = new Vector3f();

      float           lf_temp;
      int             li_temp;
      int             li_triCnt = 0;

      for(int li_index = 0; li_index < lcla_indices.capacity(); li_index += 3, li_triCnt++) {
         lcla_tris[li_triCnt] = new TriStruct();
         lcla_tris[li_triCnt].mcl_tri = new Triangle(new Vector3f(), new Vector3f(), new Vector3f());

         li_temp = lcla_indices.get(li_index) * 3;
         lcla_tris[li_triCnt].mia_indices[0] = li_temp;

         lcl_a.x = lcla_vertices.get(li_temp);
         lcl_a.y = lcla_vertices.get(li_temp + 1);
         lcl_a.z = lcla_vertices.get(li_temp + 2);

         li_temp = lcla_indices.get(li_index + 1) * 3;
         lcla_tris[li_triCnt].mia_indices[1] = li_temp;

         lcl_b.x = lcla_vertices.get(li_temp);
         lcl_b.y = lcla_vertices.get(li_temp + 1);
         lcl_b.z = lcla_vertices.get(li_temp + 2);

         li_temp = lcla_indices.get(li_index + 2) * 3;
         lcla_tris[li_triCnt].mia_indices[2] = li_temp;

         lcl_c.x = lcla_vertices.get(li_temp);
         lcl_c.y = lcla_vertices.get(li_temp + 1);
         lcl_c.z = lcla_vertices.get(li_temp + 2);

         lcla_tris[li_triCnt].mcl_tri.set(0, lcl_a);
         lcla_tris[li_triCnt].mcl_tri.set(1, lcl_b);
         lcla_tris[li_triCnt].mcl_tri.set(2, lcl_c);
         lcla_tris[li_triCnt].mcl_tri.calculateNormal();
      }

      return lcla_tris;
   }




final  static  public  void    smoothMesh(TriMesh cl_mesh) {
      TriStruct       lcla_tris[] = buildTriStructArrayFromMesh(cl_mesh);

      // out vertex array
      ArrayList<Vector3f>         lcla_vertexBufferNew = new ArrayList<Vector3f>();

      // new tex coords
      ArrayList<TexCoords>        lcla_newCoords = new ArrayList<TexCoords>();

      // old tex coords
      ArrayList<TexCoords>            lcla_oldCoords = cl_mesh.getTextureCoords();
      ArrayList<ArrayList<Float>>              lcla_newTexureCoordsBuffer = new ArrayList<ArrayList<Float>>();

      if(lcla_oldCoords != null) {
         for(TexCoords lcl_coords : lcla_oldCoords) {
            if(lcl_coords != null) {
               TexCoords       lcl_newCoord = new TexCoords();
               lcl_newCoord.perVert = lcl_coords.perVert;
               lcla_newCoords.add(lcl_newCoord);
               lcla_newTexureCoordsBuffer.add(new ArrayList<Float>());
            }

         }
      }

      // for each triangle vertex check to see if a vertex is used already
      // if not add it to the vertex buffer
      for(TriStruct lcl_tri : lcla_tris) {
         for(int li_triVIndex = 0; li_triVIndex < 3; li_triVIndex++) {
            int     li_offset = offsetInVertexBuffer(lcl_tri.mcl_tri.get(li_triVIndex), lcla_vertexBufferNew);
            if(li_offset == -1) {
               li_offset = lcla_vertexBufferNew.size();
               // add the vertex
               lcla_vertexBufferNew.add(lcl_tri.mcl_tri.get(li_triVIndex));

               // copy this vertex texture coordinate into the new coords buffer
               for(int li_texIndex = 0; li_texIndex < lcla_newTexureCoordsBuffer.size(); li_texIndex++) {
                  TexCoords      lcl_oldCoords = lcla_oldCoords.get(li_texIndex);
                  ArrayList<Float>        lfa_temp = lcla_newTexureCoordsBuffer.get(li_texIndex);

                  for(int li_vertexTextureCoordIndex = 0; li_vertexTextureCoordIndex < lcl_oldCoords.perVert; li_vertexTextureCoordIndex++) {
                     int     li_vertexOffsetInTextureCoords = ((lcl_tri.mia_indices[li_triVIndex] / 3) * lcl_oldCoords.perVert) + li_vertexTextureCoordIndex;

                     lfa_temp.add(lcl_oldCoords.coords.get(li_vertexOffsetInTextureCoords));
                  }
               }
            }

            lcl_tri.mia_indices[li_triVIndex] = li_offset;
         }
      }

      // rebuild the index buffer
      IntBuffer       lcl_indexBufferNew = BufferUtils.createIntBuffer(lcla_tris.length * 3);
      int     li_cnt = 0;
      for(TriStruct lcl_tri : lcla_tris) {
         for(int li_triVIndex = 0; li_triVIndex < 3; li_triVIndex++) {
            lcl_indexBufferNew.put(li_cnt, lcl_tri.mia_indices[li_triVIndex]);
            li_cnt++;
         }
      }

      // assign the float vertex buffer
      FloatBuffer     lcl_vertexBufferNew = BufferUtils.createFloatBuffer(lcla_vertexBufferNew.size() * 3);
      for(Vector3f lcl_v : lcla_vertexBufferNew) {
         lcl_vertexBufferNew.put(lcl_v.x);
         lcl_vertexBufferNew.put(lcl_v.y);
         lcl_vertexBufferNew.put(lcl_v.z);
      }

      cl_mesh.reconstruct(lcl_vertexBufferNew, null, null, null, lcl_indexBufferNew);

      cl_mesh.clearTextureBuffers();

      // recreate the texture coordinates
      for(int li_coordIndex = 0; li_coordIndex < lcla_newCoords.size(); li_coordIndex++) {
         TexCoords           lcl_coords = lcla_newCoords.get(li_coordIndex);
         ArrayList<Float>    lfa_coords = lcla_newTexureCoordsBuffer.get(li_coordIndex);
         FloatBuffer         lcl_textureCoordBuffer = BufferUtils.createFloatBuffer(lfa_coords.size());

         for(int li_bufferIndex = 0; li_bufferIndex < lfa_coords.size(); li_bufferIndex++) {
            lcl_textureCoordBuffer.put(lfa_coords.get(li_bufferIndex).floatValue());
         }

         lcl_coords.coords = lcl_textureCoordBuffer;

         cl_mesh.addTextureCoordinates(lcl_coords, lcl_coords.perVert);
      }


      cl_mesh.updateGeometricState(0, true);
   }



just call smooth mesh on the tri mesh you want to merge....

How does JME handle normals (its an area I keep meaning to look into but never got round to as a while back I wanted a faceted sphere)?



It looks like there is one per vertex (looking at the code in Box.java) - so if this is the case, when merging vertices you would need to take care of normals too, but don't necessarily want to just collapse them.



If you had for example, a box, and you merge the vertices so you now have 8 rather than the 24 it originally had - would it have the side effect of "rounding off" the edges, as there is now only 1 normal for the whole corner, or would it get rather upset attempting to "twist" (not sure of terminology) 2 sides to match the third if you dont average the normal too?


You might want to have a look at com.jme.util.geom.GeometryTool.  It was written to minimize verts and allows for specifying what parts can and can't differ when merging verts.

scene worker code looks good :smiley:



Two questions



What happens with the UV Texture?

Programms like Maya and Milkshape crush the UV Map(Maya randomly).

On the other hand I have seen many games, which merge and smooth the vertices afterwards without any problems.





What happens if I use MD5 Animations?

DarkPhoenixX said:

scene worker code looks good :D

Two questions

What happens with the UV Texture?
Programms like Maya and Milkshape crush the UV Map(Maya randomly).
On the other hand I have seen many games, which merge and smooth the vertices afterwards without any problems.


What happens if I use MD5 Animations?



For the first one:

I'd imagine it should only merge vertices with the same UV coords (and perhaps same normal vector, or possibly adds them). Otherwise you'd be messing up the texturing...

this is a brute force vertices aggregrator that i wrote because i had a load of meshes loaded from max that had individual vertices where they should of been shared, each vertex had it's own normal so the render got all messed up…

the main aspect of this operation was just to work with/debug what i had before the artist rejigged the art to meet the requirements…

no idea if it would work with animations but i doubt it…

the GeometryTool looks good actually…

i was unaware it existed…

could of saved me alot of bother…

doh…

JOC said:

How does JME handle normals (its an area I keep meaning to look into but never got round to as a while back I wanted a faceted sphere)?

It looks like there is one per vertex (looking at the code in Box.java) - so if this is the case, when merging vertices you would need to take care of normals too, but don't necessarily want to just collapse them.

If you had for example, a box, and you merge the vertices so you now have 8 rather than the 24 it originally had - would it have the side effect of "rounding off" the edges, as there is now only 1 normal for the whole corner, or would it get rather upset attempting to "twist" (not sure of terminology) 2 sides to match the third if you dont average the normal too?


you are right in jme has one normal per vertex......
the code above does not care about normals...
this is because scene worker had normal calculation operations already so the user can recreate the normals after they "smooth" the mesh...
the normal calculator i implemented just averages them...standard enough stuff...
i can post that code as well if anyone wants it....