/// <summary>
        /// Loads this SBM model
        /// </summary>
        /// <returns></returns>
        public bool Load(out string err)
        {
            // Check the file exists
            if (!File.Exists(filename))
            {
                err = "File not found";
                return false;
            }

            // Load it
            using (FileStream strm = File.OpenRead(filename))
            {
                // Create reader
                BinaryReader rdr = new BinaryReader(strm);

                // Read header
                string magic = new string(rdr.ReadChars(4));
                if (magic != "SBM\0")
                {
                    err = "Specified file is not an SBM file";
                    return false;
                }
                int version = rdr.ReadInt32();
                if (version != 1)
                {
                    err = "Unsupported SBM version";
                    return false;
                }
                int num_materials = rdr.ReadInt32();
                int num_meshes = rdr.ReadInt32();

                // Read all materials
                string[] materials = new string[num_materials];
                for (int i = 0; i < num_materials; i++)
                    materials[i] = rdr.ReadNullTerminatedString();

                // Read all meshes
                meshes = new SBMMesh[num_meshes];
                for (int i = 0; i < num_meshes; i++)
                {
                    // Read mesh header
                    int num_vertices = rdr.ReadInt32();
                    int num_submeshes = rdr.ReadInt32();

                    // Create mesh builder
                    MeshBuilder builder = new MeshBuilder();
                    builder.UseNormals = true;
                    builder.UseTexCoords = true;
                    builder.UseTangents = true;

                    // Read all vertices
                    for (int j = 0; j < num_vertices; j++)
                    {
                        builder.AddPosition(new Vector3(rdr.ReadSingle(), rdr.ReadSingle(), rdr.ReadSingle()));
                        builder.AddNormal(new Vector3(rdr.ReadSingle(), rdr.ReadSingle(), rdr.ReadSingle()));
                        builder.AddTextureCoord(new Vector2(rdr.ReadSingle(), rdr.ReadSingle()));
                        builder.AddTangent(new Vector3(rdr.ReadSingle(), rdr.ReadSingle(), rdr.ReadSingle()));
                    }

                    // Loop each submesh
                    string[] meshmats = new string[num_submeshes];
                    for (int j = 0; j < num_submeshes; j++)
                    {
                        // Read submesh header
                        int num_indices = rdr.ReadInt32();
                        int material_index = rdr.ReadInt32();
                        meshmats[j] = materials[material_index];

                        // Read all indices
                        for (int k = 0; k < num_indices; k++)
                            builder.AddIndex(j, rdr.ReadUInt32());
                    }

                    // Add element to mesh array
                    SBMMesh sbmmesh = new SBMMesh();
                    sbmmesh.Mesh = builder.Build();
                    sbmmesh.Materials = meshmats;
                    meshes[i] = sbmmesh;
                }
            }

            // Success
            err = null;
            return true;
        }
        public static Mesh BuildPlane(bool texcoords, bool tangents)
        {
            // Create mesh builder
            MeshBuilder builder = new MeshBuilder();
            builder.UseNormals = true;
            builder.UseTexCoords = texcoords;
            builder.UseTangents = tangents;

            // Add vertices
            builder.AddPosition(new Vector3(0.0f, 0.0f, 0.0f));
            builder.AddPosition(new Vector3(0.0f, 0.0f, 1.0f));
            builder.AddPosition(new Vector3(1.0f, 0.0f, 1.0f));
            builder.AddPosition(new Vector3(1.0f, 0.0f, 0.0f));
            builder.AddNormal(Vector3.UnitY);
            builder.AddNormal(Vector3.UnitY);
            builder.AddNormal(Vector3.UnitY);
            builder.AddNormal(Vector3.UnitY);
            if (texcoords)
            {
                builder.AddTextureCoord(new Vector2(0.0f, 1.0f));
                builder.AddTextureCoord(new Vector2(1.0f, 1.0f));
                builder.AddTextureCoord(new Vector2(1.0f, 0.0f));
                builder.AddTextureCoord(new Vector2(0.0f, 0.0f));
            }
            if (tangents)
            {
                builder.AddTangent(new Vector3(1.0f, 0.0f, 0.0f));
                builder.AddTangent(new Vector3(1.0f, 0.0f, 0.0f));
                builder.AddTangent(new Vector3(1.0f, 0.0f, 0.0f));
                builder.AddTangent(new Vector3(1.0f, 0.0f, 0.0f));
            }

            // Add indices
            builder.AddIndex(0);
            builder.AddIndex(1);
            builder.AddIndex(2);
            builder.AddIndex(2);
            builder.AddIndex(3);
            builder.AddIndex(0);

            // Build mesh
            return builder.Build();
        }
        public static Mesh BuildSphere(float radius, int subdivisions, bool usetexcoords, bool usetangents)
        {
            // Starting points
            var A = new Vector3(-1.0f, -(1.0f / Root3), -(1.0f / Root6));
            var B = new Vector3(0.0f, 2.0f / Root3, -1.0f / Root6);
            var C = new Vector3(1.0f, -(1.0f / Root3), -(1.0f / Root6));
            var D = new Vector3(0.0f, 0.0f, 3.0f / Root6);

            // Build a triangular based pyramid
            var tris = new List<Triangle>();
            tris.Add(new Triangle(A, B, C));
            tris.Add(new Triangle(C, D, A));
            tris.Add(new Triangle(B, D, C));
            tris.Add(new Triangle(A, D, B));
            var tmplist = new List<Triangle>();

            // Run subdivide passes
            for (int i = 0; i < subdivisions; i++)
            {
                // Normalise all vertices
                for (int j = 0; j < tris.Count; j++)
                {
                    Triangle tri = tris[j];
                    tri.A.Normalize();
                    tri.A *= radius;
                    tri.B.Normalize();
                    tri.B *= radius;
                    tri.C.Normalize();
                    tri.C *= radius;
                    tris[j] = tri;
                }

                // Check for last pass
                if (i == subdivisions - 1) break;

                // Subdivide all triangles
                tmplist.Clear();
                for (int j = 0; j < tris.Count; j++)
                {
                    Triangle tri = tris[j];
                    Vector3 midAB = (tri.A + tri.B) * 0.5f;
                    Vector3 midBC = (tri.B + tri.C) * 0.5f;
                    Vector3 midCA = (tri.C + tri.A) * 0.5f;
                    tmplist.Add(new Triangle(tri.A, midAB, midCA));
                    tmplist.Add(new Triangle(midAB, midBC, midCA));
                    tmplist.Add(new Triangle(midCA, midBC, tri.C));
                    tmplist.Add(new Triangle(midAB, tri.B, midBC));
                }

                // Swap the lists
                var tmp = tris;
                tris = tmplist;
                tmplist = tmp;
            }

            // Create builder
            var builder = new MeshBuilder();
            builder.UseNormals = true;
            builder.UseTexCoords = usetexcoords;
            builder.UseTangents = usetangents;
            for (int i = 0; i < tris.Count; i++)
            {
                Triangle tri = tris[i];
                builder.AddPosition(tri.A);
                builder.AddPosition(tri.B);
                builder.AddPosition(tri.C);
                builder.AddNormal(GetNormal(tri.A));
                builder.AddNormal(GetNormal(tri.B));
                builder.AddNormal(GetNormal(tri.C));
                if (usetangents)
                {
                    builder.AddTangent(GetSphereTangent(tri.A));
                    builder.AddTangent(GetSphereTangent(tri.B));
                    builder.AddTangent(GetSphereTangent(tri.C));
                }
                if (usetexcoords)
                {
                    // TODO: This
                    builder.AddTextureCoord(new Vector2(0.0f, 0.0f));
                    builder.AddTextureCoord(new Vector2(0.0f, 0.0f));
                    builder.AddTextureCoord(new Vector2(0.0f, 0.0f));
                }
                builder.AddIndex((ushort)(i * 3));
                builder.AddIndex((ushort)(i * 3 + 1));
                builder.AddIndex((ushort)(i * 3 + 2));
            }

            // Create mesh
            return builder.Build();
        }
        public static Mesh BuildHeightmap(HeightSampler sampler, int width, int height, float tilesize, int subdivide = 1)
        {
            // Determine sizes
            int totalW = width * subdivide;
            int totalH = height * subdivide;
            float invtotalW = 1.0f / totalW;
            float invtotalH = 1.0f / totalH;

            // Precache an array of heights
            float[,] heights = new float[totalW, totalH];
            for (int x = 0; x < totalW; x++)
            {
                float fX = x * invtotalW * width;
                for (int y = 0; y < totalH; y++)
                {
                    float fY = y * invtotalH * height;
                    heights[x, y] = sampler(fX, fY);
                }
            }

            // Recreate the sampler to use the cache
            sampler = new HeightSampler((x, y) =>
            {
                int ix = (int)(x * subdivide);
                int iy = (int)(y * subdivide);
                if (ix < 0) ix = 0; if (iy < 0) iy = 0;
                if (ix >= totalW) ix = totalW - 1;
                if (iy >= totalH) iy = totalH - 1;
                return heights[ix, iy];
            });

            // Prepare the builder
            MeshBuilder builder = new MeshBuilder();
            builder.UseNormals = true;
            builder.UseTangents = true;

            // Perform the loop
            for (int x = 0; x < totalW; x++)
            {
                float fX = x * invtotalW * width;
                for (int y = 0; y < totalH; y++)
                {
                    float fY = y * invtotalH * height;
                    builder.AddPosition(new Vector3(fX * tilesize, sampler(fX, fY), fY * tilesize));
                    builder.AddNormal(CalculateNormal(sampler, fX, fY));
                    builder.AddTangent(CalculateTangent(sampler, fX, fY));
                    if ((x < totalW - 1) && (y < totalH - 1))
                    {
                        int idx = (x * totalH) + y;
                        builder.AddIndex((ushort)idx);
                        builder.AddIndex((ushort)(idx + 1));
                        builder.AddIndex((ushort)(idx + totalH + 1));
                        builder.AddIndex((ushort)(idx + totalH + 1));
                        builder.AddIndex((ushort)(idx + totalH));
                        builder.AddIndex((ushort)idx);
                    }
                }
            }
            return builder.Build();
        }