• Advertisement
Sign in to follow this  

[SlimDX] Mesh utility functions (source included)

This topic is 3039 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

I'm playing around with SlimDX to learn more about shaders, and in the process I wrote a few mesh utility functions. Currently I have functions to: * Compute a bounding sphere/box * Compute texture coordinates Here's the code:
public static BoundingSphere ComputeBoundingSphere(Mesh mesh)
{
    List<Vector3> verts = new List<Vector3>(mesh.VertexCount);
    DataStream ds = mesh.LockVertexBuffer(LockFlags.ReadOnly);

    while (ds.Position < ds.Length)
    {
        long oldPos = ds.Position;

        Vector3 pos = ds.Read<Vector3>();
        verts.Add(pos);

        ds.Position = oldPos + mesh.BytesPerVertex;
    }

    mesh.UnlockVertexBuffer();

    return BoundingSphere.FromPoints(verts.ToArray());
}

public static BoundingBox ComputeBoundingBox(Mesh mesh)
{
    List<Vector3> verts = new List<Vector3>(mesh.VertexCount);
    DataStream ds = mesh.LockVertexBuffer(LockFlags.ReadOnly);

    while (ds.Position < ds.Length)
    {
        long oldPos = ds.Position;

        Vector3 pos = ds.Read<Vector3>();
        verts.Add(pos);

        ds.Position = oldPos + mesh.BytesPerVertex;
    }

    mesh.UnlockVertexBuffer();

    return BoundingBox.FromPoints(verts.ToArray());
}

public static int FindElementIndex(VertexElement[] elems, DeclarationUsage usage)
{
    for (int i = 0; i < elems.Length; ++i)
    {
        if (elems[i].Usage == usage)
            return i;
    }

    return -1;
}

// This code uses the methods described in:
// www.mvps.org/directx/articles/spheremap.htm
public static void ComputeTexCoords(Device device, ref Mesh mesh, bool useNormals)
{
    if (useNormals)
    {
        if ((mesh.VertexFormat & VertexFormat.Normal) == 0)
        {
            mesh.ComputeNormals();
        }
    }

    // Make room for texture coordinates
    // The 'newMesh' variable is not needed anymore. The extra {} ensure that it is not used accidentally
    {
        Mesh newMesh = mesh.Clone(device, mesh.CreationOptions, mesh.VertexFormat | VertexFormat.Texture1);
        mesh.Dispose();
        mesh = newMesh;
    }

    VertexElement[] elems = mesh.GetDeclaration();
    int posElem       = FindElementIndex(elems, DeclarationUsage.Position);
    int normalElem    = FindElementIndex(elems, DeclarationUsage.Normal);
    int texCoordsElem = FindElementIndex(elems, DeclarationUsage.TextureCoordinate);

    // Needed for positional spherical mapping
    BoundingBox bbox = ComputeBoundingBox(mesh);

    DataStream ds = mesh.LockVertexBuffer(LockFlags.None);

    while (ds.Position < ds.Length)
    {
        long oldPos = ds.Position;

        Vector3 vec;
        if (useNormals)     // Spherical mapping with normals
        {
            ds.Position += elems[normalElem].Offset;
            vec = ds.Read<Vector3>();
            ds.Position = oldPos;
        }
        else                // Spherical mapping with positions
        {
            ds.Position += elems[posElem].Offset;
            Vector3 pos = ds.Read<Vector3>();
            ds.Position = oldPos;

            Vector3 center = (bbox.Minimum + bbox.Maximum) * 0.5f;
            vec = Vector3.Normalize(pos - center);
        }

        float u = (float)Math.Asin(vec.X) / (float)Math.PI + 0.5f;
        float v = (float)Math.Asin(vec.Y) / (float)Math.PI + 0.5f;

        ds.Position += elems[texCoordsElem].Offset;
        ds.Write<float>(u);
        ds.Write<float>(v);

        ds.Position = oldPos + mesh.BytesPerVertex;
    }

    mesh.UnlockVertexBuffer();
}

The functions seem to work, but I have not tested them thoroughly, so I can't say that with full certainty. If you see any errors, please let me know. I'm also not too happy with the implementation, mostly because there's a lot of duplicate code. If you know of a better way to do this sort of thing, I would love to hear about it. I hope this will be useful to someone.

Share this post


Link to post
Share on other sites
Advertisement
You can cut down on a lot of that code by using a little LINQ and a few helper methods that exist in SlimDX already. This is how I'd write the function. It should be the same thing, although I wrote it on the fly so it might have a few typos:


public static Mesh ComputeTextureCoords(Device device, Mesh mesh, bool useNormals)
{
if (useNormals && (mesh.VertexFormat & VertexFormat.Normal) == 0)
mesh.ComputeNormals();

mesh = mesh.Clone(device, mesh.CreationOptions, mesh.VertexFormat | VertexFormat.Texture1);

var elements = mesh.GetDeclaration();
var dataStream = mesh.LockVertexBuffer(LockFlags.None);
var box = BoundingBox.FromPoints(D3DX.GetVectors(dataStream, mesh.VertexCount, mesh.BytesPerVertex));
var center = (box.Minimum + box.Maximum) * 0.5f;

dataStream.Position = elements.FirstOrDefault(e => useNormals ? e.Usage == DeclarationUsage.Normal : e.Usage == DeclarationUsage.Position).Offset;
var vectors = D3DX.GetVectors(dataStream, mesh.VertexCount, mesh.BytesPerVertex).Select(v => useNormals ? v : Vector3.Normalize(v - center));
dataStream.Position = elements.FirstOrDefault(e => e.Usage == DeclarationUsage.TextureCoordinate).Offset;

foreach (var vector in vectors)
{
dataStream.Write((float)(Math.Asin(vector.X) / Math.PI + 0.5));
dataStream.Write((float)(Math.Asin(vector.Y) / Math.PI + 0.5));
dataStream.Position += mesh.BytesPerVertex - (sizeof(float) * 2);
}

mesh.UnlockVertexBuffer();
return mesh;
}

Share this post


Link to post
Share on other sites
Thanks. I'm still not familiar with the C# 3.0 features but that does look pretty nice.

Also, I somehow managed to miss the D3DX class...

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement