/// <summary> /// Translates each Vertices.Default's position vector by the specified amounts in the x, y, and z directions. /// </summary> /// <param name="vertices">Any array of vertices.</param> /// <param name="x">The x-position offset to be applied.</param> /// <param name="y">The y-position offset to be applied.</param> /// <param name="z">The z-position offset to be applied.</param> /// <returns>The translated Vertices.Default array.</returns> public static Vertex.Default[] Translate(this Vertex.Default[] vertices, float x, float y, float z) { Vertex.Default[] translated = new Vertex.Default[vertices.Length]; Vector3 translation = new Vector3(x, y, z); for (int a = 0; a < vertices.Length; a++) { translated[a] = vertices[a]; translated[a].Position += translation; } return translated; }
/// <summary> /// Generates a geodesic sphere of unit radius centered on (0, 0, 0). Vertices are not shared. /// <para> </para> /// Spheres are generated by tessellating icosahedron geometry and setting all vertices to unit distance from the origin <para/> /// in model space. Higher tessellation factors increase the smoothness of the sphere but quickly become computationally <para/> /// expensive. It is therefore recommended that this parameter not exceed a value of 8. /// </summary> /// <param name="n">The number of tessellations to perform on the icosahedron geometry.</param> /// <returns>The Vertex.Default array that constructs the sphere.</returns> public static Vertex.Default[] Sphere(uint n) { Vertex.Default[] icoVerts = Icosahedron(); List<Vertex.Default> vertices = new List<Vertex.Default>(); for (int a = 0; a < icoVerts.Length; a += 3) { Vertex.Default[] verts = new Vertex.Default[] { icoVerts[a], icoVerts[a + 1], icoVerts[a + 2] }; for (int b = 0; b < n; b++) verts = Tessellate(verts); for (int b = 0; b < verts.Length; b++) verts[b].Position.Normalize(); CalculateSphericalUV(ref verts); vertices.AddRange(verts); //for (int b = 0; b < verts.Length; b++) //{ // verts[b].Position.Normalize(); // CalculateSphericalUV(ref verts[b]); // vertices.Add(verts[b]); //} } Vertex.Default[] sphere = vertices.ToArray(); Vertex.Default.CalculateVertexNormals(ref sphere); return sphere; }
/// <summary> /// Subdivides one or more triangles into four new triangles with unshared vertices. /// <para> </para> /// This function tessellates a triangle into four equal-sized, new triangles whose vertices are coplanar with the original. <para/> /// Thus, for every three inputted vertices, 12 vertices are returned since vertices are not shared. To create additional <para/> /// subdivisions, simply call this function repeatedly on the output. /// </summary> /// <param name="vertices">A list of vertices that sequentially build triangles.</param> /// <returns>A new list of vertices representing the tessellated version of the input triangles.</returns> public static Vertex.Default[] Tessellate(this Vertex.Default[] vertices) { if (vertices.Length % 3 != 0) throw new ArgumentException("The number of inputted vertices must be a multiple of 3 so as to constitute a triangle list."); int nTriangles = vertices.Length / 3; int idx = 0; Vertex.Default[] tessellated = new Vertex.Default[nTriangles * 12]; for (int a = 0; a < vertices.Length; a += 3) { var p1 = vertices[a].Position + (0.5f * (vertices[a + 1].Position - vertices[a].Position)); var p2 = vertices[a].Position + (0.5f * (vertices[a + 2].Position - vertices[a].Position)); var p3 = vertices[a + 1].Position + (0.5f * (vertices[a + 2].Position - vertices[a + 1].Position)); var v01 = new Vertex.Default(p1.X, p1.Y, p1.Z); var v02 = new Vertex.Default(p2.X, p2.Y, p2.Z); var v12 = new Vertex.Default(p3.X, p3.Y, p3.Z); tessellated[idx++] = vertices[a]; tessellated[idx++] = v01; tessellated[idx++] = v02; tessellated[idx++] = v01; tessellated[idx++] = vertices[a + 1]; tessellated[idx++] = v12; tessellated[idx++] = v01; tessellated[idx++] = v12; tessellated[idx++] = v02; tessellated[idx++] = v02; tessellated[idx++] = v12; tessellated[idx++] = vertices[a + 2]; } return tessellated; }
/// <summary> /// Rotates each Vertices.Default's position vector by the specified angles around the x, y, and z axes. /// </summary> /// <param name="vertices">Any array of vertices.</param> /// <param name="pitch">The x-axis rotation angle in radians to be applied.</param> /// <param name="yaw">The y-axis rotation angle in radians to be applied.</param> /// <param name="roll">The z-axis rotation angle in radians to be applied.</param> /// <returns>The rotated Vertices.Default array.</returns> public static Vertex.Default[] Rotate(this Vertex.Default[] vertices, float pitch, float yaw, float roll) { Vertex.Default[] rotated = new Vertex.Default[vertices.Length]; Vector3 position; Matrix3x3 transform = YawTransform(yaw) * PitchTransform(pitch) * RollTransform(roll); for (int a = 0; a < vertices.Length; a++) { rotated[a] = vertices[a]; position = rotated[a].Position; rotated[a].Position.X = Vector3.Dot(transform.Row1, position); rotated[a].Position.Y = Vector3.Dot(transform.Row2, position); rotated[a].Position.Z = Vector3.Dot(transform.Row3, position); rotated[a].Normal.X = Vector3.Dot(transform.Row1, rotated[a].Normal); rotated[a].Normal.Y = Vector3.Dot(transform.Row2, rotated[a].Normal); rotated[a].Normal.Z = Vector3.Dot(transform.Row3, rotated[a].Normal); rotated[a].Normal.Normalize(); } return rotated; }
/// <summary> /// Scales each Vertices.Default's position vector by the specified amounts in the x, y, and z directions. /// </summary> /// <param name="vertices">Any array of vertices.</param> /// <param name="x">The x-position multiplier to be applied.</param> /// <param name="y">The y-position multiplier to be applied.</param> /// <param name="z">The z-position multiplier to be applied.</param> /// <returns>The scaled Vertices.Default array.</returns> public static Vertex.Default[] Scale(this Vertex.Default[] vertices, float x, float y, float z) { Vertex.Default[] scaled = new Vertex.Default[vertices.Length]; for (int a = 0; a < vertices.Length; a++) { scaled[a] = vertices[a]; scaled[a].Position.X *= x; scaled[a].Position.Y *= y; scaled[a].Position.Z *= z; } return scaled; }
/// <summary> /// Generates a regular icosahedron of unit radius centered on (0, 0, 0). Vertices are shared. /// </summary> /// <returns>The Vertex.Default array that constructs the icosahedron.</returns> public static Vertex.Default[] Icosahedron() { Vertex.Default[] v = new Vertex.Default[] { new Vertex.Default( 0.525731f, 0, 0.850651f), // 1 new Vertex.Default(-0.525731f, 0, 0.850651f), // 0 new Vertex.Default( 0, 0.850651f, 0.525731f), // 4 new Vertex.Default( 0, 0.850651f, 0.525731f), // 4 new Vertex.Default(-0.525731f, 0, 0.850651f), // 0 new Vertex.Default(-0.850651f, 0.525731f, 0), // 9 new Vertex.Default( 0, 0.850651f, 0.525731f), // 4 new Vertex.Default(-0.850651f, 0.525731f, 0), // 9 new Vertex.Default( 0, 0.850651f, -0.525731f), // 5 new Vertex.Default( 0.850651f, 0.525731f, 0), // 8 new Vertex.Default( 0, 0.850651f, 0.525731f), // 4 new Vertex.Default( 0, 0.850651f, -0.525731f), // 5 new Vertex.Default( 0.525731f, 0, 0.850651f), // 1 new Vertex.Default( 0, 0.850651f, 0.525731f), // 4 new Vertex.Default( 0.850651f, 0.525731f, 0), // 8 new Vertex.Default( 0.525731f, 0, 0.850651f), // 1 new Vertex.Default( 0.850651f, 0.525731f, 0), // 8 new Vertex.Default( 0.850651f, -0.525731f, 0), // 10 new Vertex.Default( 0.850651f, -0.525731f, 0), // 10 new Vertex.Default( 0.850651f, 0.525731f, 0), // 8 new Vertex.Default( 0.525731f, 0, -0.850651f), // 3 new Vertex.Default( 0.850651f, 0.525731f, 0), // 8 new Vertex.Default( 0, 0.850651f, -0.525731f), // 5 new Vertex.Default( 0.525731f, 0, -0.850651f), // 3 new Vertex.Default( 0.525731f, 0, -0.850651f), // 3 new Vertex.Default( 0, 0.850651f, -0.525731f), // 5 new Vertex.Default(-0.525731f, 0, -0.850651f), // 2 new Vertex.Default( 0.525731f, 0, -0.850651f), // 3 new Vertex.Default(-0.525731f, 0, -0.850651f), // 2 new Vertex.Default( 0, -0.850651f, -0.525731f), // 7 new Vertex.Default( 0.525731f, 0, -0.850651f), // 3 new Vertex.Default( 0, -0.850651f, -0.525731f), // 7 new Vertex.Default( 0.850651f, -0.525731f, 0), // 10 new Vertex.Default( 0.850651f, -0.525731f, 0), // 10 new Vertex.Default( 0, -0.850651f, -0.525731f), // 7 new Vertex.Default( 0, -0.850651f, 0.525731f), // 6 new Vertex.Default( 0, -0.850651f, 0.525731f), // 6 new Vertex.Default( 0, -0.850651f, -0.525731f), // 7 new Vertex.Default(-0.850651f, -0.525731f, 0), // 11 new Vertex.Default( 0, -0.850651f, 0.525731f), // 6 new Vertex.Default(-0.850651f, -0.525731f, 0), // 11 new Vertex.Default(-0.525731f, 0, 0.850651f), // 0 new Vertex.Default( 0, -0.850651f, 0.525731f), // 6 new Vertex.Default(-0.525731f, 0, 0.850651f), // 0 new Vertex.Default( 0.525731f, 0, 0.850651f), // 1 new Vertex.Default( 0.850651f, -0.525731f, 0), // 10 new Vertex.Default( 0, -0.850651f, 0.525731f), // 6 new Vertex.Default( 0.525731f, 0, 0.850651f), // 1 new Vertex.Default(-0.850651f, -0.525731f, 0), // 11 new Vertex.Default(-0.850651f, 0.525731f, 0), // 9 new Vertex.Default(-0.525731f, 0, 0.850651f), // 0 new Vertex.Default(-0.525731f, 0, -0.850651f), // 2 new Vertex.Default(-0.850651f, 0.525731f, 0), // 9 new Vertex.Default(-0.850651f, -0.525731f, 0), // 11 new Vertex.Default( 0, 0.850651f, -0.525731f), // 5 new Vertex.Default(-0.850651f, 0.525731f, 0), // 9 new Vertex.Default(-0.525731f, 0, -0.850651f), // 2 new Vertex.Default(-0.850651f, -0.525731f, 0), // 11 new Vertex.Default( 0, -0.850651f, -0.525731f), // 7 new Vertex.Default(-0.525731f, 0, -0.850651f), // 2 }; CalculateSphericalUV(ref v); Vertex.Default.CalculateVertexNormals(ref v); return v; }
/// <summary> /// Generates a regular icosahedron of unit radius centered on (0, 0, 0). Vertices are shared. /// </summary> /// <param name="indices">An ordered list of indices that tell the GPU how to construct the icosahedron.</param> /// <returns>The Vertex.Default array that constructs the icosahedron.</returns> public static Vertex.Default[] Icosahedron(out uint[] indices) { indices = new uint[] { 1,0,4, 4,0,9, 4,9,5, 8,4,5, 1,4,8, 1,8,10, 10,8,3, 8,5,3, 3,5,2, 3,2,7, 3,7,10, 10,7,6, 6,7,11, 6,11,0, 6,0,1, 10,6,1, 11,9,0, 2,9,11, 5,9,2, 11,7,2 }; var vertices = new Vertex.Default[] { new Vertex.Default(-0.525731f, 0, 0.850651f), // 0 new Vertex.Default( 0.525731f, 0, 0.850651f), // 1 new Vertex.Default(-0.525731f, 0, -0.850651f), // 2 new Vertex.Default( 0.525731f, 0, -0.850651f), // 3 new Vertex.Default( 0, 0.850651f, 0.525731f), // 4 new Vertex.Default( 0, 0.850651f, -0.525731f), // 5 new Vertex.Default( 0, -0.850651f, 0.525731f), // 6 new Vertex.Default( 0, -0.850651f, -0.525731f), // 7 new Vertex.Default( 0.850651f, 0.525731f, 0), // 8 new Vertex.Default(-0.850651f, 0.525731f, 0), // 9 new Vertex.Default( 0.850651f, -0.525731f, 0), // 10 new Vertex.Default(-0.850651f, -0.525731f, 0), // 11 }; CalculateSphericalUV(ref vertices); Vertex.Default.CalculateVertexNormals(ref vertices, indices); return vertices; }
/// <summary> /// Generates an evenly distributed grid of vertices that plot the elevations found in the inputted heightmap. /// <para> </para> /// This function generates a grid of vertices where each vertex represents an element of the inputted heightmap matrix. The <para/> /// output is a flattened series of vertices that are ordered by the secondary ouput indices. Together these produce <para/> /// counterclockwise wound triangles that can be used to build a mesh. The flattened vertex array proceeds sequentially along <para/> /// columns and then along rows (i.e. in row-major form) of the heightmap after it has been flipped across the row dimension. <para/> /// This is done so that the appearance of a rendered mesh matches how the heightmap would apepar in image space. Normal <para/> /// vectors are generated automatically by this function. /// </summary> /// <param name="indices">An ordered list of indices that tell the GPU how to construct the heightmap.</param> /// <param name="heightmap">A matrix of elevation values for each X and Y coordinate pair.</param> /// <returns>An array of vertices that can be used to generate a 3D mesh of the heightmap.</returns> public static Vertex.Default[] Heightmap(out uint[] indices, float[,] heightmap) { heightmap = heightmap.Flip(); int h = heightmap.GetLength(0); int w = heightmap.GetLength(1); int numel = h * w; indices = new uint[(h - 1) * (w - 1) * 6]; Vertex.Default[] vertices = new Vertex.Default[numel]; List<Vector3>[] norms = new List<Vector3>[numel]; // Initialize the vertices corresponding with the first row of the heightmap for (int a = 0; a < w; a++) { vertices[a] = new Vertex.Default(a, heightmap[0, a], 0); norms[a] = new List<Vector3>(); } // Initialize the vertices corresponding with the first column of the heightmap for (int a = 1; a < h; a++) { vertices[a * w] = new Vertex.Default(0, heightmap[a, 0], a); norms[a * w] = new List<Vector3>(); } float u, v; uint idxIDs = 0; uint idxLL, idxLR, idxUL, idxUR; Vector3 diff1, diff2; for (int a = 0; a < h - 1; a++) for (int b = 0; b < w - 1; b++) { // Calculate vertex linear array indices (combinations of Upper, Lower, Left, Right) idxLL = (uint)(a * w + b); idxLR = (uint)(a * w + b + 1); idxUL = (uint)((a + 1) * w + b); idxUR = (uint)((a + 1) * w + b + 1); // Set the position & texture coordinates of the next lower-right vertex (others initialized by previous passes) u = (float)b / (float)w; v = (float)a / (float)h; vertices[idxUR] = new Vertex.Default(b + 1, heightmap[a + 1, b + 1], a + 1, 0, 0, 0, u, v); norms[idxUR] = new List<Vector3>(); // Determine the indices of counterclockwise winding indices[idxIDs] = idxUL; indices[idxIDs + 1] = idxLL; indices[idxIDs + 2] = idxLR; indices[idxIDs + 3] = idxUL; indices[idxIDs + 4] = idxLR; indices[idxIDs + 5] = idxUR; idxIDs += 6; // Add normals for the upper-left vertex diff1 = vertices[idxUL].Position - vertices[idxLL].Position; diff2 = vertices[idxUR].Position - vertices[idxLL].Position; norms[idxLL].Add(Vector3.Normalize(Vector3.Cross(diff1, diff2))); // Add normals for the upper-left vertex (second triangle of the grid quad) diff1 = vertices[idxUR].Position - vertices[idxLL].Position; diff2 = vertices[idxLR].Position - vertices[idxLL].Position; norms[idxLL].Add(Vector3.Normalize(Vector3.Cross(diff1, diff2))); // Add normals for the lower-left vertex diff1 = vertices[idxUR].Position - vertices[idxUL].Position; diff2 = vertices[idxLL].Position - vertices[idxUL].Position; norms[idxUL].Add(Vector3.Normalize(Vector3.Cross(diff1, diff2))); // Add normals for the lower-right vertex diff1 = vertices[idxLL].Position - vertices[idxUR].Position; diff2 = vertices[idxUL].Position - vertices[idxUR].Position; norms[idxUR].Add(Vector3.Normalize(Vector3.Cross(diff1, diff2))); // Add normals for the upper-left vertex (second triangle of the grid quad) diff1 = vertices[idxLR].Position - vertices[idxUR].Position; diff2 = vertices[idxLL].Position - vertices[idxUR].Position; norms[idxUR].Add(Vector3.Normalize(Vector3.Cross(diff1, diff2))); // Add normals for the upper-right vertex diff1 = vertices[idxLL].Position - vertices[idxLR].Position; diff2 = vertices[idxUR].Position - vertices[idxLR].Position; norms[idxLR].Add(Vector3.Normalize(Vector3.Cross(diff1, diff2))); } // Average together the various normals found in the above loops Vector3 ctsum; for (int a = 0; a < norms.Length; a++) { ctsum = Vector3.Zero; foreach (Vector3 norm in norms[a]) ctsum += norm; vertices[a].Normal = ctsum / norms[a].Count; } return vertices; }
/// <summary> /// Generates a cylinder of unit radius centered on (0, 0, 0) and extending one unit along the positive Y axis. Vertices are shared. /// </summary> /// <param name="indices">An ordered list of indices that tell the GPU how to construct the cylinder.</param> /// <param name="n">The number of quad faces that will be used to approximate the cylinder's circular profile.</param> /// <returns>The Vertex.Default array that constructs the cylinder.</returns> public static Vertex.Default[] Cylinder(out uint[] indices, int n) { indices = new uint[6 * n]; Vertex.Default[] vertices = new Vertex.Default[n * 2]; float angleIncrement = Constants.TwoPi / n; for (int a = 0; a < n; a++) { float ctangle = angleIncrement * a; float ntangle = angleIncrement * (a + 1); float x1 = (float)Math.Cos(ctangle); float z1 = (float)Math.Sin(ctangle); float x2 = (float)Math.Cos(ntangle); float z2 = (float)Math.Sin(ntangle); vertices[a * 2] = new Vertex.Default(x1, 1, z1); vertices[a * 2 + 1] = new Vertex.Default(x1, 0, z1); indices[a * 6] = (uint)(a * 2); indices[a * 6 + 1] = (uint)(a * 2 + 1); indices[a * 6 + 2] = (uint)(a * 2 + 3); indices[a * 6 + 3] = (uint)(a * 2); indices[a * 6 + 4] = (uint)(a * 2 + 3); indices[a * 6 + 5] = (uint)(a * 2 + 2); } indices[n * 6 - 4] = 1; indices[n * 6 - 2] = 1; indices[n * 6 - 1] = 0; Vertex.Default.CalculateVertexNormals(ref vertices, indices); CalculateSphericalUV(ref vertices, indices); return vertices; }
/// <summary> /// Generates a cylinder of unit radius centered on (0, 0, 0) and extending one unit along the positive Y axis. Vertices are not shared. /// </summary> /// <param name="n">The number of quad faces that will be used to approximate the cylinder's circular profile.</param> /// <returns>The Vertex.Default array that constructs the cylinder.</returns> public static Vertex.Default[] Cylinder(int n) { Vertex.Default[] vertices = new Vertex.Default[6 * n]; float angleIncrement = Constants.TwoPi / n; float hAngleIncrement = angleIncrement / 2f; for (int a = 0; a < n; a++) { float ctangle = angleIncrement * a; float hangle = ctangle + hAngleIncrement; float ntangle = angleIncrement * (a + 1); float x1 = (float)Math.Cos(ctangle); float z1 = (float)Math.Sin(ctangle); float x2 = (float)Math.Cos(ntangle); float z2 = (float)Math.Sin(ntangle); float nx = (float)Math.Cos(hangle); float nz = (float)Math.Sin(hangle); vertices[a * 6] = new Vertex.Default(x1, 1, z1, nx, 0, nz); vertices[a * 6 + 1] = new Vertex.Default(x1, 0, z1, nx, 0, nz); vertices[a * 6 + 2] = new Vertex.Default(x2, 0, z2, nx, 0, nz); vertices[a * 6 + 3] = new Vertex.Default(x1, 1, z1, nx, 0, nz); vertices[a * 6 + 4] = new Vertex.Default(x2, 0, z2, nx, 0, nz); vertices[a * 6 + 5] = new Vertex.Default(x2, 1, z2, nx, 0, nz); } CalculateSphericalUV(ref vertices); return vertices; }