/// <summary> /// Builds a smooth surfaced grid with the origin in the bottom-left corner. /// </summary> /// <remarks> /// The grid is comprised of quads with no shared vertices, allowing flat shading. /// </remarks> /// <param name="meshBuilder">Mesh builder.</param> /// <param name="cellWidth">Cell width.</param> /// <param name="cellLength">Cell length.</param> /// <param name="segmentCount">Segment count.</param> /// <param name="heightFunc">Height func.</param> public static void BuildSmoothGrid(this IMeshBuilder meshBuilder, float cellWidth, float cellLength, int segmentCount, Func <int, int, float> heightFunc) { int baseIndex = 0; for (int i = 0; i < segmentCount; i++) { for (int j = 0; j < segmentCount; j++) { var x = cellWidth * j; var z = cellLength * i; meshBuilder.Vertices.Add(new Vector3(x, heightFunc(j, i), z)); meshBuilder.Vertices.Add(new Vector3(x, heightFunc(j, i + 1), z + cellLength)); meshBuilder.Vertices.Add(new Vector3(x + cellWidth, heightFunc(j + 1, i + 1), z + cellLength)); meshBuilder.Vertices.Add(new Vector3(x + cellWidth, heightFunc(j + 1, i), z)); meshBuilder.Normals.Add(Vector3.up); meshBuilder.Normals.Add(Vector3.up); meshBuilder.Normals.Add(Vector3.up); meshBuilder.Normals.Add(Vector3.up); meshBuilder.AddTriangle(baseIndex, baseIndex + 1, baseIndex + 2); meshBuilder.AddTriangle(baseIndex, baseIndex + 2, baseIndex + 3); baseIndex += 4; } } }
public static void ExtrudeToCenter(this IMeshBuilder meshBuilder, IMeshSelection meshSelection) { var p = Vector3.zero; for (int i = 0; i < meshSelection.Count; i++) { p += meshBuilder.Vertices[meshSelection[i]]; } p = p / meshSelection.Count; meshBuilder.Vertices.Add(p); meshBuilder.UVs.Add(Vector2.zero); // meshBuilder.Colors.Add(new Color(1f, 1f, 1f, 1f)); for (int i = 0; i < meshSelection.Count - 1; i++) { var i1 = meshSelection[i]; var i2 = meshBuilder.Vertices.Count - 1; var i3 = meshSelection[i + 1]; meshBuilder.AddTriangle(i1, i2, i3); } // Add the final face. meshBuilder.AddTriangle(meshBuilder.Vertices.Count - 2, meshBuilder.Vertices.Count - 1, meshSelection[0]); }
/// <summary> /// Builds a single quad based on a position offset and width and length vectors. /// </summary> /// <param name="meshBuilder">The mesh builder currently being added to.</param> /// <param name="offset">A position offset for the quad.</param> /// <param name="widthDir">The width vector of the quad.</param> /// <param name="lengthDir">The length vector of the quad.</param> public static void BuildQuad(this IMeshBuilder meshBuilder, Vector3 offset, Vector3 widthDir, Vector3 lengthDir) { Vector3 normal = Vector3.Cross(lengthDir, widthDir).normalized; meshBuilder.Vertices.Add(offset); meshBuilder.UVs.Add(new Vector2(0.0f, 0.0f)); meshBuilder.Normals.Add(normal); meshBuilder.Vertices.Add(offset + lengthDir); meshBuilder.UVs.Add(new Vector2(0.0f, 1.0f)); meshBuilder.Normals.Add(normal); meshBuilder.Vertices.Add(offset + lengthDir + widthDir); meshBuilder.UVs.Add(new Vector2(1.0f, 1.0f)); meshBuilder.Normals.Add(normal); meshBuilder.Vertices.Add(offset + widthDir); meshBuilder.UVs.Add(new Vector2(1.0f, 0.0f)); meshBuilder.Normals.Add(normal); //we don't know how many verts the meshBuilder is up to, but we only care about the four we just added: int baseIndex = meshBuilder.Vertices.Count - 4; meshBuilder.AddTriangle(baseIndex, baseIndex + 1, baseIndex + 2); meshBuilder.AddTriangle(baseIndex, baseIndex + 2, baseIndex + 3); }
/// <summary> /// Builds a grid using quads with shared vertices, and the origin in the bottom-left corner. /// </summary> public static void BuildSmoothGrid(this IMeshBuilder meshBuilder, float cellWidth, float cellLength, int columns, int rows, Func <int, int, float> heightFunc, Func <int, int, QuadCorners, Color> colorFunc = null) { if (colorFunc == null) { colorFunc = (i, j, quad) => Color.white; } int baseIndex = 0; for (int i = 0; i < rows; i++) { for (int j = 0; j < columns; j++) { var x = cellWidth * j; var z = cellLength * i; var h0 = heightFunc(j, i); var h1 = heightFunc(j, i + 1); var h2 = heightFunc(j + 1, i + 1); var h3 = heightFunc(j + 1, i); QuadCorners quad; quad.c0 = new Vector3(x, h0, z); quad.c1 = new Vector3(x, h1, z + cellLength); quad.c2 = new Vector3(x + cellWidth, h2, z + cellLength); quad.c3 = new Vector3(x + cellWidth, h3, z); var c = colorFunc(j, i, quad); meshBuilder.Vertices.Add(quad.c0); meshBuilder.Colors.Add(c); meshBuilder.Vertices.Add(quad.c1); meshBuilder.Colors.Add(c); meshBuilder.Vertices.Add(quad.c2); meshBuilder.Colors.Add(c); meshBuilder.Vertices.Add(quad.c3); meshBuilder.Colors.Add(c); meshBuilder.Normals.Add(Vector3.up); meshBuilder.Normals.Add(Vector3.up); meshBuilder.Normals.Add(Vector3.up); meshBuilder.Normals.Add(Vector3.up); meshBuilder.AddTriangle(baseIndex, baseIndex + 1, baseIndex + 2); meshBuilder.AddTriangle(baseIndex, baseIndex + 2, baseIndex + 3); baseIndex += 4; } } }
public static void BuildCylinder(this IMeshBuilder meshBuilder, int numSegments, int numLayers, float radius, float height) { var angleDelta = Mathf.PI * 2f / numSegments; var layerHeight = height / numLayers; var bottom = new Vector3(0f, 0f, 0f); var top = new Vector3(0f, height, 0f); meshBuilder.Vertices.Add(bottom); meshBuilder.Vertices.Add(top); for (int j = 0; j < numLayers; j++) { // The vertical position of the bottom of the current layer. var h = j * layerHeight; for (int i = 0; i <= numSegments; i++) { var a = angleDelta * -i; var p1 = new Vector3(Mathf.Cos(a) * radius, h, Mathf.Sin(a) * radius); var p2 = p1 + Vector3.up * layerHeight; meshBuilder.Vertices.Add(p1); meshBuilder.Vertices.Add(p2); var numVerts = meshBuilder.Vertices.Count; if (i > 0) { // side meshBuilder.AddTriangle(numVerts - 2, numVerts - 3, numVerts - 4); meshBuilder.AddTriangle(numVerts - 2, numVerts - 1, numVerts - 3); // bottom if (j == 0) { meshBuilder.AddTriangle(numVerts - 4, 0, numVerts - 2); } // top if (j == numLayers - 1) { meshBuilder.AddTriangle(numVerts - 1, 1, numVerts - 3); } } } } }
/// <summary> /// Builds a single quad as part of a mesh grid. /// </summary> /// <param name="meshBuilder">The mesh builder currently being added to.</param> /// <param name="position">A position offset for the quad. Specifically the position of the corner vertex of the quad.</param> /// <param name="uv">The UV coordinates of the quad's corner vertex.</param> /// <param name="buildTriangles">Should triangles be built for this quad? This value should be false if this is the first quad in any row or collumn.</param> /// <param name="vertsPerRow">The number of vertices per row in this grid.</param> public static void BuildQuadForGrid(this IMeshBuilder meshBuilder, Vector3 position, Vector2 uv, bool buildTriangles, int vertsPerRow) { meshBuilder.Vertices.Add(position); meshBuilder.UVs.Add(uv); if (buildTriangles) { int baseIndex = meshBuilder.Vertices.Count - 1; int index0 = baseIndex; int index1 = baseIndex - 1; int index2 = baseIndex - vertsPerRow; int index3 = baseIndex - vertsPerRow - 1; meshBuilder.AddTriangle(index0, index2, index1); meshBuilder.AddTriangle(index2, index3, index1); } }
public static void BuildQuad(this IMeshBuilder meshBuilder, int bottomIndex, int topIndex) { var bl = bottomIndex; var tl = topIndex; var tr = topIndex + 1; var br = bottomIndex + 1; Log.Trace("Building quad: verts=[{0},{1},{2},{3}], indices=[{4},{5},{6},{7}]", meshBuilder.Vertices[bl], meshBuilder.Vertices[tl], meshBuilder.Vertices[tr], meshBuilder.Vertices[br], bl, tl, tr, br); meshBuilder.AddTriangle(bl, tl, br); meshBuilder.AddTriangle(br, tl, tr); }
public virtual void BuildMesh(IMeshBuilder meshBuilder, IStyleConfig styleConfig) { this.ApplyStyle(styleConfig); var baseIndex = meshBuilder.Vertices.Count; foreach (var face in this.Faces) { var numVerts = face.Corners.Count; for (int i = 0; i < face.Corners.Count; i++) { var v = face.Corners[i].Position; var uv = face.UVs[i]; var worldPos = this.Transform.Position + (this.Transform.Rotation * Vector3.Scale(v, this.Transform.Scale)); meshBuilder.Vertices.Add(worldPos); meshBuilder.UVs.Add(uv); meshBuilder.Colors.Add(face.Color); } if (numVerts == 3) { meshBuilder.AddTriangle(baseIndex, baseIndex + 1, baseIndex + 2); } else if (numVerts == 4) { meshBuilder.AddTriangle(baseIndex, baseIndex + 1, baseIndex + 3); meshBuilder.AddTriangle(baseIndex + 1, baseIndex + 2, baseIndex + 3); } else { throw new InvalidOperationException(string.Format("Cannot build mesh for faces with {0} vertices", numVerts)); } baseIndex = meshBuilder.Vertices.Count; } }
/// <summary> /// Builds a single quad in the XZ plane, facing up the Y axis. /// </summary> /// <param name="meshBuilder">The mesh builder currently being added to.</param> /// <param name="offset">A position offset for the quad.</param> /// <param name="width">The width of the quad.</param> /// <param name="length">The length of the quad.</param> public static void BuildQuad(this IMeshBuilder meshBuilder, Vector3 offset, float width, float length) { meshBuilder.Vertices.Add(new Vector3(0.0f, 0.0f, 0.0f) + offset); meshBuilder.UVs.Add(new Vector2(0.0f, 0.0f)); meshBuilder.Normals.Add(Vector3.up); meshBuilder.Vertices.Add(new Vector3(0.0f, 0.0f, length) + offset); meshBuilder.UVs.Add(new Vector2(0.0f, 1.0f)); meshBuilder.Normals.Add(Vector3.up); meshBuilder.Vertices.Add(new Vector3(width, 0.0f, length) + offset); meshBuilder.UVs.Add(new Vector2(1.0f, 1.0f)); meshBuilder.Normals.Add(Vector3.up); meshBuilder.Vertices.Add(new Vector3(width, 0.0f, 0.0f) + offset); meshBuilder.UVs.Add(new Vector2(1.0f, 0.0f)); meshBuilder.Normals.Add(Vector3.up); //we don't know how many verts the meshBuilder is up to, but we only care about the four we just added: int baseIndex = meshBuilder.Vertices.Count - 4; meshBuilder.AddTriangle(baseIndex, baseIndex + 1, baseIndex + 2); meshBuilder.AddTriangle(baseIndex, baseIndex + 2, baseIndex + 3); }
/// <summary> /// Builds a single triangle. /// </summary> /// <param name="meshBuilder">The mesh builder currently being added to.</param> /// <param name="corner0">The vertex position at index 0 of the triangle.</param> /// <param name="corner1">The vertex position at index 1 of the triangle.</param> /// <param name="corner2">The vertex position at index 2 of the triangle.</param> public static void BuildTriangle(this IMeshBuilder meshBuilder, Vector3 corner0, Vector3 corner1, Vector3 corner2) { Vector3 normal = Vector3.Cross((corner1 - corner0), (corner2 - corner0)).normalized; meshBuilder.Vertices.Add(corner0); meshBuilder.UVs.Add(new Vector2(0.0f, 0.0f)); meshBuilder.Normals.Add(normal); meshBuilder.Vertices.Add(corner1); meshBuilder.UVs.Add(new Vector2(0.0f, 1.0f)); meshBuilder.Normals.Add(normal); meshBuilder.Vertices.Add(corner2); meshBuilder.UVs.Add(new Vector2(1.0f, 1.0f)); meshBuilder.Normals.Add(normal); int baseIndex = meshBuilder.Vertices.Count - 3; meshBuilder.AddTriangle(baseIndex, baseIndex + 1, baseIndex + 2); }
public static void BuildIcosahedron(this IMeshBuilder meshBuilder, float radius = 1f) { // create 12 vertices of a icosahedron float t = (1f + Mathf.Sqrt(5f)) / 2f; meshBuilder.AddVertex(new Vector3(-1f, t, 0f).normalized *radius); meshBuilder.AddVertex(new Vector3(1f, t, 0f).normalized *radius); meshBuilder.AddVertex(new Vector3(-1f, -t, 0f).normalized *radius); meshBuilder.AddVertex(new Vector3(1f, -t, 0f).normalized *radius); meshBuilder.AddVertex(new Vector3(0f, -1f, t).normalized *radius); meshBuilder.AddVertex(new Vector3(0f, 1f, t).normalized *radius); meshBuilder.AddVertex(new Vector3(0f, -1f, -t).normalized *radius); meshBuilder.AddVertex(new Vector3(0f, 1f, -t).normalized *radius); meshBuilder.AddVertex(new Vector3(t, 0f, -1f).normalized *radius); meshBuilder.AddVertex(new Vector3(t, 0f, 1f).normalized *radius); meshBuilder.AddVertex(new Vector3(-t, 0f, -1f).normalized *radius); meshBuilder.AddVertex(new Vector3(-t, 0f, 1f).normalized *radius); // create 20 triangles of the icosahedron // 5 faces around point 0 meshBuilder.AddTriangle(0, 11, 5); meshBuilder.AddTriangle(0, 5, 1); meshBuilder.AddTriangle(0, 1, 7); meshBuilder.AddTriangle(0, 7, 10); meshBuilder.AddTriangle(0, 10, 11); // 5 adjacent faces meshBuilder.AddTriangle(1, 5, 9); meshBuilder.AddTriangle(5, 11, 4); meshBuilder.AddTriangle(11, 10, 2); meshBuilder.AddTriangle(10, 7, 6); meshBuilder.AddTriangle(7, 1, 8); // 5 faces around point 3 meshBuilder.AddTriangle(3, 9, 4); meshBuilder.AddTriangle(3, 4, 2); meshBuilder.AddTriangle(3, 2, 6); meshBuilder.AddTriangle(3, 6, 8); meshBuilder.AddTriangle(3, 8, 9); // 5 adjacent faces meshBuilder.AddTriangle(4, 9, 5); meshBuilder.AddTriangle(2, 4, 11); meshBuilder.AddTriangle(6, 2, 10); meshBuilder.AddTriangle(8, 6, 7); meshBuilder.AddTriangle(9, 8, 1); }
public static void BuildIcoSphere(this IMeshBuilder meshBuilder, int recursionLevel = 3, float radius = 1f) { List <Vector3> vertList = new List <Vector3>(); Dictionary <long, int> middlePointIndexCache = new Dictionary <long, int>(); // create 12 vertices of a icosahedron float t = (1f + Mathf.Sqrt(5f)) / 2f; vertList.Add(new Vector3(-1f, t, 0f).normalized *radius); vertList.Add(new Vector3(1f, t, 0f).normalized *radius); vertList.Add(new Vector3(-1f, -t, 0f).normalized *radius); vertList.Add(new Vector3(1f, -t, 0f).normalized *radius); vertList.Add(new Vector3(0f, -1f, t).normalized *radius); vertList.Add(new Vector3(0f, 1f, t).normalized *radius); vertList.Add(new Vector3(0f, -1f, -t).normalized *radius); vertList.Add(new Vector3(0f, 1f, -t).normalized *radius); vertList.Add(new Vector3(t, 0f, -1f).normalized *radius); vertList.Add(new Vector3(t, 0f, 1f).normalized *radius); vertList.Add(new Vector3(-t, 0f, -1f).normalized *radius); vertList.Add(new Vector3(-t, 0f, 1f).normalized *radius); // create 20 triangles of the icosahedron List <TriangleIndices> faces = new List <TriangleIndices>(); // 5 faces around point 0 faces.Add(new TriangleIndices(0, 11, 5)); faces.Add(new TriangleIndices(0, 5, 1)); faces.Add(new TriangleIndices(0, 1, 7)); faces.Add(new TriangleIndices(0, 7, 10)); faces.Add(new TriangleIndices(0, 10, 11)); // 5 adjacent faces faces.Add(new TriangleIndices(1, 5, 9)); faces.Add(new TriangleIndices(5, 11, 4)); faces.Add(new TriangleIndices(11, 10, 2)); faces.Add(new TriangleIndices(10, 7, 6)); faces.Add(new TriangleIndices(7, 1, 8)); // 5 faces around point 3 faces.Add(new TriangleIndices(3, 9, 4)); faces.Add(new TriangleIndices(3, 4, 2)); faces.Add(new TriangleIndices(3, 2, 6)); faces.Add(new TriangleIndices(3, 6, 8)); faces.Add(new TriangleIndices(3, 8, 9)); // 5 adjacent faces faces.Add(new TriangleIndices(4, 9, 5)); faces.Add(new TriangleIndices(2, 4, 11)); faces.Add(new TriangleIndices(6, 2, 10)); faces.Add(new TriangleIndices(8, 6, 7)); faces.Add(new TriangleIndices(9, 8, 1)); // refine triangles for (int i = 0; i < recursionLevel; i++) { List <TriangleIndices> faces2 = new List <TriangleIndices>(); foreach (var tri in faces) { // replace triangle by 4 triangles int a = getMiddlePoint(tri.v1, tri.v2, ref vertList, ref middlePointIndexCache, radius); int b = getMiddlePoint(tri.v2, tri.v3, ref vertList, ref middlePointIndexCache, radius); int c = getMiddlePoint(tri.v3, tri.v1, ref vertList, ref middlePointIndexCache, radius); faces2.Add(new TriangleIndices(tri.v1, a, c)); faces2.Add(new TriangleIndices(tri.v2, b, a)); faces2.Add(new TriangleIndices(tri.v3, c, b)); faces2.Add(new TriangleIndices(a, b, c)); } faces = faces2; } for (int i = 0; i < vertList.Count; i++) { meshBuilder.Vertices.Add(vertList[i]); } for (int i = 0; i < faces.Count; i++) { meshBuilder.AddTriangle(faces[i].v1, faces[i].v2, faces[i].v3); } for (int i = 0; i < vertList.Count; i++) { meshBuilder.Normals.Add(vertList[i].normalized); // meshBuilder.UVs.Add(Vector2.zero); // meshBuilder.Colors.Add(ColorHelper.RandomRGB()); } }
public unsafe void PolygonizeCell(float[] values, Vector3[] positions, float isoLevel, IMeshBuilder builder) { Profiler.BeginSample("cubeIndex"); // Determine the index into the edge table which // tells us which vertices are inside of the surface var cubeIndex = 0; if (values[0] < isoLevel) { cubeIndex |= 1; } if (values[1] < isoLevel) { cubeIndex |= 2; } if (values[2] < isoLevel) { cubeIndex |= 4; } if (values[3] < isoLevel) { cubeIndex |= 8; } if (values[4] < isoLevel) { cubeIndex |= 16; } if (values[5] < isoLevel) { cubeIndex |= 32; } if (values[6] < isoLevel) { cubeIndex |= 64; } if (values[7] < isoLevel) { cubeIndex |= 128; } Profiler.EndSample(); // Cube is entirely in/out of the surface if (EdgeTable[cubeIndex] == 0) { return; } Profiler.BeginSample("EdgeTable"); var vertices = stackalloc Vector3[12]; // Find the vertices where the surface intersects the cube if ((EdgeTable[cubeIndex] & 1) > 0) { vertices[0] = Interpolate(isoLevel, positions[0], positions[1], values[0], values[1]); } if ((EdgeTable[cubeIndex] & 2) > 0) { vertices[1] = Interpolate(isoLevel, positions[1], positions[2], values[1], values[2]); } if ((EdgeTable[cubeIndex] & 4) > 0) { vertices[2] = Interpolate(isoLevel, positions[2], positions[3], values[2], values[3]); } if ((EdgeTable[cubeIndex] & 8) > 0) { vertices[3] = Interpolate(isoLevel, positions[3], positions[0], values[3], values[0]); } if ((EdgeTable[cubeIndex] & 16) > 0) { vertices[4] = Interpolate(isoLevel, positions[4], positions[5], values[4], values[5]); } if ((EdgeTable[cubeIndex] & 32) > 0) { vertices[5] = Interpolate(isoLevel, positions[5], positions[6], values[5], values[6]); } if ((EdgeTable[cubeIndex] & 64) > 0) { vertices[6] = Interpolate(isoLevel, positions[6], positions[7], values[6], values[7]); } if ((EdgeTable[cubeIndex] & 128) > 0) { vertices[7] = Interpolate(isoLevel, positions[7], positions[4], values[7], values[4]); } if ((EdgeTable[cubeIndex] & 256) > 0) { vertices[8] = Interpolate(isoLevel, positions[0], positions[4], values[0], values[4]); } if ((EdgeTable[cubeIndex] & 512) > 0) { vertices[9] = Interpolate(isoLevel, positions[1], positions[5], values[1], values[5]); } if ((EdgeTable[cubeIndex] & 1024) > 0) { vertices[10] = Interpolate(isoLevel, positions[2], positions[6], values[2], values[6]); } if ((EdgeTable[cubeIndex] & 2048) > 0) { vertices[11] = Interpolate(isoLevel, positions[3], positions[7], values[3], values[7]); } Profiler.EndSample(); Profiler.BeginSample("TriTable"); /* Create the triangle */ for (var i = 0; TriTable[cubeIndex][i] != -1; i += 3) { builder.AddTriangle( vertices[TriTable[cubeIndex][i]], vertices[TriTable[cubeIndex][i + 1]], vertices[TriTable[cubeIndex][i + 2]] ); } Profiler.EndSample(); }
public static IMeshSelection ExtrudeEdge( this IMeshBuilder meshBuilder, IMeshSelection edge, Func <int, Vector3, Vector3> extrudeTranslationFunc) { Log.Trace("Will extrude edge {0}. Total vertices in mesh = {1}", edge, meshBuilder.Vertices.Count); var lowerEdge = new MeshSelection(edge); var extrudedEdge = new MeshSelection(); // Check if we're dealing with a closed selection loop. var isClosed = lowerEdge.IsClosed; if (isClosed) { Log.Trace("The edge has the same start and end point {0}", lowerEdge[0]); } // Extrude new vertices from the current source vertices. for (int i = 0; i < lowerEdge.Count; i++) { var extrudeIdx = lowerEdge[i]; var p = meshBuilder.Vertices[extrudeIdx]; Log.Trace("extrudeIdx = {0}", extrudeIdx); // TODO: Need to skip duplicate indexes when the start/end range is wrapped. var translation = extrudeTranslationFunc(i, p); var newVertexIdx = meshBuilder.AddVertex(p + translation); extrudedEdge.Add(newVertexIdx); // TODO: This currently assumes the normal won't be changed by the translation. Need fix // support rotation as well. meshBuilder.Normals.Add(meshBuilder.Normals[extrudeIdx]); } if (isClosed) { extrudedEdge.Add(extrudedEdge[0]); } Log.Trace("Building {0} quads", edge.Count); Assert.AreEqual(lowerEdge.Count, extrudedEdge.Count, "Number of extruded indices should match the number of lower indices"); Log.Trace("lowerEdge = {0}", lowerEdge); Log.Trace("extrudedEdge = {0}", extrudedEdge); // Build quads between the start vertices and the extruded vertices, using the virtual // selection which includes the closing vertex index. for (int i = 0; i < lowerEdge.VirtualCount - 1; i++) { var bl = lowerEdge.GetAtVirtualIndex(i); var tl = extrudedEdge.GetAtVirtualIndex(i); var tr = extrudedEdge.GetAtVirtualIndex(i + 1); var br = lowerEdge.GetAtVirtualIndex(i + 1); Log.Trace("Building quad: verts=[{0},{1},{2},{3}], indices=[{4},{5},{6},{7}]", meshBuilder.Vertices[bl], meshBuilder.Vertices[tl], meshBuilder.Vertices[tr], meshBuilder.Vertices[br], bl, tl, tr, br); meshBuilder.AddTriangle(bl, tl, br); meshBuilder.AddTriangle(br, tl, tr); } return(extrudedEdge); }