Extracting Vertices and Triangles from an XNA Model

jwatte's picture

Here is some code that lets you extract the vertices and indices from a loaded XNA Model. You do not need to use any special processor, such as the JigLibX VertexProcessor or some derivative of the ModelProcessor -- it works straight out of the box!

    public struct TriangleVertexIndices {
      public int I0;
      public int I1;
      public int I2;
    }
 
    public static void ExtractData(Model mdl, List<Vector3> vtcs, List<TriangleVertexIndices> idcs, bool includeNoncoll)
    {
      Matrix m = Matrix.Identity;
      foreach (ModelMesh mm in mdl.Meshes)
      {
        m = GetAbsoluteTransform(mm.ParentBone);
        ExtractModelMeshData(mm, ref m, vtcs, idcs, "Collision Model", includeNoncoll);
      }
    }
 
    public static Matrix GetAbsoluteTransform(ModelBone bone)
    {
      if (bone == null)
        return Matrix.Identity;
      return bone.Transform * GetAbsoluteTransform(bone.Parent);
    }
 
    public static void ExtractModelMeshData(ModelMesh mm, ref Matrix xform,
        List<Vector3> vertices, List<TriangleVertexIndices> indices, string name, bool includeNoncoll)
    {
      foreach (ModelMeshPart mmp in mm.MeshParts)
      {
 
        if (!includeNoncoll)
        {
          EffectAnnotation annot = mmp.Effect.CurrentTechnique.Annotations["collide"];
          if (annot != null && annot.GetValueBoolean() == false)
          {
            Console.WriteLine("Ignoring model mesh part {0}:{1} because it's set to not collide.",
                name, mm.Name);
            continue;
          }
        }
        ExtractModelMeshPartData(mm, mmp, ref xform, vertices, indices, name);
      }
    }
 
    public static void ExtractModelMeshPartData(ModelMesh mm, ModelMeshPart mmp, ref Matrix xform,
        List<Vector3> vertices, List<TriangleVertexIndices> indices, string name)
    {
      int offset = vertices.Count;
      Vector3[] a = new Vector3[mmp.NumVertices];
      mm.VertexBuffer.GetData<Vector3>(mmp.StreamOffset + mmp.BaseVertex * mmp.VertexStride,
          a, 0, mmp.NumVertices, mmp.VertexStride);
      for (int i = 0; i != a.Length; ++i)
        Vector3.Transform(ref a[i], ref xform, out a[i]);
      vertices.AddRange(a);
 
      if (mm.IndexBuffer.IndexElementSize != IndexElementSize.SixteenBits)
        throw new Exception(
            String.Format("Model {0} uses 32-bit indices, which are not supported.",
                          name));
      short[] s = new short[mmp.PrimitiveCount * 3];
      mm.IndexBuffer.GetData<short>(mmp.StartIndex * 2, s, 0, mmp.PrimitiveCount * 3);
      TriangleVertexIndices[] tvi = new TriangleVertexIndices[mmp.PrimitiveCount];
      for (int i = 0; i != tvi.Length; ++i)
      {
        tvi[i].I0 = s[i * 3 + 0] + offset;
        tvi[i].I1 = s[i * 3 + 1] + offset;
        tvi[i].I2 = s[i * 3 + 2] + offset;
      }
      indices.AddRange(tvi);
    }

Comments

Is there something you need

Is there something you need in the vector list before its passed to ExtractData?

jwatte's picture

The output list objects need

The output list objects need to be allocated but empty.

jwatte's picture

Someone said they couldn't

Someone said they couldn't get this code working for 32-bit indices.
If you are creating a proper 32-bit index buffer, then a simple edit to replace "short" with "int" in the last function above should work fine.
Note that, if you build data for the Xbox, data in the .xnb file will be the wrong endian if you look at it on the x86 box, because the Xbox is big-endian, and x86 is little-endian.

How to apply it?

Hi jwatte,

i'm kinda new to xna but i do sort of understand this code, though i can't see how i can use this code in my program and, after performing this code what variables contain the coordinates for the width height and depth?

i hope you understand what i mean, thanks.

Terrific! Can I possibly scale this?

Do you have any suggestions for scaling this to not produce as many vertices? It does very well, but my convex hull class takes like 2 minuets to initialize. I don't reproduce the points more than once; you're algorithm completes in no time. Yet, every time I generate a convex hull from those vertices it takes a very long time. Altering the convex hull class is not an option, unfortunately. Thanks!

--Alex

jwatte's picture

Reducing the number of

Reducing the number of vertices is a totally different algorithm, and can be quite hard if you want good results.

Works perfectly. Thanks

The following line:

JigLibX.Geometry.TriangleVertexIndices[] tvi = new 
JigLibX.Geometry.TriangleVertexIndices[mmp.PrimitiveCount];

Should read:

TriangleVertexIndices[] tvi = new 
TriangleVertexIndices[mmp.PrimitiveCount];

Reading the code prior to testing was a bit confusing because the short pre-amble contadicted the typed code.

Regards

Yes, I believe most users use

Yes, I believe most users use this code together with JigLibX, so the problem isn't as noticeable. I have updated the text on the page; thanks for calling it out!

Thanks for this!

thanks for that code! It simplifies my solution explorer considerably,

Best,
Byron

?????

thanks but how can it be used for collision checking.
can you explain by providing an example of two mesh colliding pleas?

jwatte's picture

General mesh-to-mesh

General mesh-to-mesh collision testing is a quite hard problem, especially if you want to also generate a contact manifold so that you can do something about the collision, not just detecting that "there is collision." You may want to google for the GJK method for the currently most popular approach to this problem.

Drawing

I edited this code a bit to take a VertexPositionNormalTexture vertices from the buffers. I then used all of the indices and vertices output to draw the model using DrawIndexedPrimitives. There are seemingly random triangles everywhere, many of them going to the same place on the model. Any idea why this might be happening?

Re: Drawing

Hey, I'm just replying to this year-and-a-half-old comment for the sake of my fellow Googlers. I'm pretty sure the cause of this problem is that the index buffer doesn't have absolute indexes when your Model has more than one ModelMesh. This means if you extract the full vertex buffer and index buffer straight into arrays and use them to DrawUserIndexedPrimitives, you'll get a big mess, since the index buffer drops back to zero and the indices will be pointing to the wrong vertices.

Here's my code for XNA 4.0 to copy a model to jagged arrays of VertexPositionNormalTextures and shorts for indices. Jagged arrays because of this oddity in indexing.

// Stores a copy of the given model's vertex data.
// Jagged array indices are: [ModelMesh][ModelMeshPart][vertex]
VertexPositionNormalTexture[][][] vertices;
short[][][] indices;
 
// Index is [ModelMesh]
int[] boneIndices;
 
// Index is boneIndices[ModelMesh]
Matrix[] transforms;
 
public void GetModelData(Model model)
{
    if (model.Meshes[0].MeshParts[0].IndexBuffer.IndexElementSize != IndexElementSize.SixteenBits)
    {
        // Use int arrays instead of short for indices if this is a problem
        throw new Exception("Model uses 32-bit indices, which are not supported.");
    }
 
    vertices = new VertexPositionNormalTexture[model.Meshes.Count][][];
    indices = new short[model.Meshes.Count][][];
 
    transforms = new Matrix[model.Bones.Count];
    boneIndices = new int[model.Bones.Count];
 
    // Copy bone transforms so that meshes can be rendered in the correct positions
    model.CopyAbsoluteBoneTransformsTo(transforms);
 
    // Copy model data
    VertexPositionNormalTexture[] vertexBuffer =
    new VertexPositionNormalTexture[model.Meshes[0].MeshParts[0].VertexBuffer.VertexCount];
    short[] indexBuffer =
    new short[model.Meshes[0].MeshParts[0].IndexBuffer.IndexCount];
    model.Meshes[0].MeshParts[0].VertexBuffer.GetData<VertexPositionNormalTexture>(vertexBuffer);
    model.Meshes[0].MeshParts[0].IndexBuffer.GetData<short>(indexBuffer);
 
    // Convert vertex data into a usable format
    for (int i = 0; i < model.Meshes.Count; i++)
    {
        // Set up the indexing arrays
        boneIndices[i] = model.Meshes[i].ParentBone.Index;
 
        vertices[i] = new VertexPositionNormalTexture[model.Meshes[i].MeshParts.Count][];
        indices[i] = new short[model.Meshes[i].MeshParts.Count][];
 
        for (int j = 0; j < model.Meshes[i].MeshParts.Count; j++)
        {
            ModelMeshPart part = model.Meshes[i].MeshParts[j];
            vertices[i][j] = new VertexPositionNormalTexture[part.NumVertices];
            indices[i][j] = new short[part.PrimitiveCount * 3];
 
            // As far as I can tell, this only doesn't work due to it being broken in the API. If the intellisense
            // comments on these methods are correct then this should work.
            //part.VertexBuffer.GetData<VertexPositionNormalTexture>(vertices[i][j], part.VertexOffset, part.NumVertices);
            //part.IndexBuffer.GetData<short>(indices[i][j], part.StartIndex, part.PrimitiveCount * 3);
 
            Array.Copy(vertexBuffer, part.VertexOffset, vertices[i][j], 0, part.NumVertices);
            Array.Copy(indexBuffer, part.StartIndex, indices[i][j], 0, part.PrimitiveCount * 3);
        }
    }

To draw the model, here's the code:

    basicEffect.CurrentTechnique.Passes[0].Apply(); // replace with your effect
 
    for (int i = 0; i < indices.Length; i++)
    {
    // Set up the effect matrices
    //basicEffect.World = transforms[boneIndices[i]];
 
    for (int j = 0; j < indices[i].Length; j++)
    {
    graphicsDevice.DrawUserIndexedPrimitives<VertexPositionNormalTexture>(
        PrimitiveType.TriangleList,
        vertices[i][j],
        0,
        vertices[i][j].Length,
        indices[i][j],
        0,
        indices[i][j].Length / 3);
    }
 
}

jwatte's picture

I have no general idea of

I have no general idea of what might be happening. Perhaps the stride is off? Or perhaps the index list is wrong (perhaps a 16-bit vs 32-bit problem)?

Problem with invalid indicies????

Jwatte,

Your code compiled clean and looks great to me, but I've spend the last couple days racking my head unable to figure out a problem and I'm hoping you or someone else on the board maybe can help. I'm using XNA 3.1 and C# 2008 Express.

When I look at my model (.X saved as ascii) I can see the indicies section looks something like this below, which looks quite valid to me. If has one mesh, and one mesh part. When I look at the model loaded, I see it has a stride of 44 and is 16 bits.

Here's a sample of the indicies from my model.x file:

248;
3;0,2,1;,
3;0,4,3;,
3;2,6,5;,
3;1,7,4;,
3;6,9,8;,
.
.
.
however when I use your routine to extract the data from the model I get:

1,2,3
4,5,6
7,8,9
10,11,12
...

this seems highly unlikely to me and I'm guessing something is wrong. I'm trying to use this for collision detection, and none of my collision routines are working.

I've also noticed that the first vert I read in is actually the 9th one in the file:

Mesh {
259;
-0.014301;9.360699;-9.259300;,
38.758690;19.301199;-93.605499;,
-0.010707;19.715199;-100.608490;,
6.607199;9.359999;-6.648801;,
71.136986;19.707397;-71.202911;,
40.619690;27.329397;-98.104706;,
-0.009207;26.934196;-105.407898;,
74.537895;26.925997;-74.602104;,
5.973499;52.609993;-5.995400;, <---- this one is read first
0.000799;52.610596;-8.285899;,
93.604492;19.292698;-38.801216;,
9.238699;9.359199;-0.018501;,
100.601494;19.699499;-0.020615;,
.
.
.

and the second vert read is elsewhere in the list which is baffling to me

It seems like its doing what it's supposed to do, but its not getting the right results. Should I expect it to appear just like the .X file?

I also noticed that the # of indicies read does match the # stored in the X file, but the number of verticies read shows 271 whereas the file only shows 259.

Any help would be GREATLY appreciated!

Thanks