/// <summary> /// Creates a sphere primitive. /// </summary> /// <param name="diameter">The diameter.</param> /// <param name="tessellation">The tessellation.</param> /// <param name="toLeftHanded">if set to <c>true</c> vertices and indices will be transformed to left handed. Default is false.</param> /// <returns>A sphere primitive.</returns> /// <exception cref="System.ArgumentOutOfRangeException">tessellation;Must be >= 3</exception> public static GeometricMeshData <VertexPositionNormalTexture> New(float height = 1.0f, float radius = 0.5f, int tessellation = 16, bool toLeftHanded = false) { if (tessellation < 3) { throw new ArgumentOutOfRangeException("tessellation", "Must be >= 3"); } int verticalSegments = tessellation; int horizontalSegments = tessellation * 2; var vertices = new VertexPositionNormalTexture[(verticalSegments + 1) * (horizontalSegments + 1)]; var indices = new int[(verticalSegments) * (horizontalSegments + 1) * 6]; var middle = verticalSegments / 2; int vertexCount = 0; // Create rings of vertices at progressively higher latitudes. for (int i = 0; i <= verticalSegments; i++) { float v = 1.0f - (float)i / verticalSegments; var latitude = (float)((i * Math.PI / verticalSegments) - Math.PI / 2.0); var dy = (float)Math.Sin(latitude); var dxz = (float)Math.Cos(latitude); var deltaY = latitude <= 0.0f ? -height : height; dy += deltaY; // Create a single ring of vertices at this latitude. for (int j = 0; j <= horizontalSegments; j++) { float u = (float)j / horizontalSegments; var longitude = (float)(j * 2.0 * Math.PI / horizontalSegments); var dx = (float)Math.Sin(longitude); var dz = (float)Math.Cos(longitude); dx *= dxz; dz *= dxz; var normal = new Vector3(dx, dy, dz); var textureCoordinate = new Vector2(u, v); vertices[vertexCount++] = new VertexPositionNormalTexture(normal * radius, normal, textureCoordinate); } } // Fill the index buffer with triangles joining each pair of latitude rings. int stride = horizontalSegments + 1; int indexCount = 0; for (int i = 0; i < verticalSegments; i++) { for (int j = 0; j <= horizontalSegments; j++) { int nextI = i + 1; int nextJ = (j + 1) % stride; indices[indexCount++] = (i * stride + j); indices[indexCount++] = (nextI * stride + j); indices[indexCount++] = (i * stride + nextJ); indices[indexCount++] = (i * stride + nextJ); indices[indexCount++] = (nextI * stride + j); indices[indexCount++] = (nextI * stride + nextJ); } } // Create the primitive object. // Create the primitive object. return(new GeometricMeshData <VertexPositionNormalTexture>(vertices, indices, toLeftHanded) { Name = "Capsule" }); }
/// <summary> /// Creates a Plane primitive on the X/Y plane with a normal equal to -<see cref="Vector3.UnitZ"/>. /// </summary> /// <param name="sizeX">The size X.</param> /// <param name="sizeY">The size Y.</param> /// <param name="tessellationX">The tessellation, as the number of quads per X axis.</param> /// <param name="tessellationY">The tessellation, as the number of quads per Y axis.</param> /// <param name="toLeftHanded">if set to <c>true</c> vertices and indices will be transformed to left handed. Default is false.</param> /// <param name="uvFactors">Scale UVs between 0 and the values of this parameter.</param> /// <returns>A Plane primitive.</returns> /// <exception cref="System.ArgumentOutOfRangeException">tessellation;tessellation must be > 0</exception> public static GeometricMeshData <VertexPositionNormalTexture> New(float sizeX = 1.0f, float sizeY = 1.0f, int tessellationX = 1, int tessellationY = 1, bool toLeftHanded = false, Vector2?uvFactors = null, bool generateBackFace = false) { if (tessellationX < 1) { throw new ArgumentOutOfRangeException("tessellationX", "tessellationX must be > 0"); } if (tessellationY < 1) { throw new ArgumentOutOfRangeException("tessellationY", "tessellationY must be > 0"); } var lineWidth = tessellationX + 1; var lineHeight = tessellationY + 1; var vertices = new VertexPositionNormalTexture[lineWidth * lineHeight]; var indices = new int[tessellationX * tessellationY * 6 * (generateBackFace? 2: 1)]; var deltaX = sizeX / tessellationX; var deltaY = sizeY / tessellationY; sizeX /= 2.0f; sizeY /= 2.0f; int vertexCount = 0; int indexCount = 0; var normal = Vector3.UnitZ; var uv = uvFactors.HasValue ? uvFactors.Value : new Vector2(1, 1); // Create vertices for (int y = 0; y < (tessellationY + 1); y++) { for (int x = 0; x < (tessellationX + 1); x++) { var position = new Vector3(-sizeX + deltaX * x, sizeY - deltaY * y, 0); var texCoord = new Vector2(uv.X * x / tessellationX, uv.Y * y / tessellationY); vertices[vertexCount++] = new VertexPositionNormalTexture(position, normal, texCoord); } } // Create indices for (int y = 0; y < tessellationY; y++) { for (int x = 0; x < tessellationX; x++) { // Six indices (two triangles) per face. int vbase = lineWidth * y + x; indices[indexCount++] = (vbase + 1); indices[indexCount++] = (vbase + 1 + lineWidth); indices[indexCount++] = (vbase + lineWidth); indices[indexCount++] = (vbase + 1); indices[indexCount++] = (vbase + lineWidth); indices[indexCount++] = (vbase); } } if (generateBackFace) { // Create indices for (int y = 0; y < tessellationY; y++) { for (int x = 0; x < tessellationX; x++) { // Six indices (two triangles) per face. int vbase = lineWidth * y + x; indices[indexCount++] = (vbase + 1); indices[indexCount++] = (vbase + lineWidth); indices[indexCount++] = (vbase + 1 + lineWidth); indices[indexCount++] = (vbase + 1); indices[indexCount++] = (vbase); indices[indexCount++] = (vbase + lineWidth); } } } // Create the primitive object. return(new GeometricMeshData <VertexPositionNormalTexture>(vertices, indices, toLeftHanded) { Name = "Plane" }); }
/// <summary> /// Creates a Geodesic sphere. /// </summary> /// <param name="diameter">The diameter.</param> /// <param name="tessellation">The tessellation.</param> /// <param name="toLeftHanded">if set to <c>true</c> vertices and indices will be transformed to left handed. Default is false.</param> /// <returns>A Geodesic sphere.</returns> private unsafe GeometricMeshData <VertexPositionNormalTexture> Create(float diameter = 1.0f, int tessellation = 3, bool toLeftHanded = false) { subdividedEdges = new Dictionary <UndirectedEdge, int>(); float radius = diameter / 2.0f; // Start with an octahedron; copy the data into the vertex/index collection. vertexPositions = new List <Vector3>(OctahedronVertices); indexList = new List <int>(OctahedronIndices); // We know these values by looking at the above index list for the octahedron. Despite the subdivisions that are // about to go on, these values aren't ever going to change because the vertices don't move around in the array. // We'll need these values later on to fix the singularities that show up at the poles. const int northPoleIndex = 0; const int southPoleIndex = 5; for (int iSubdivision = 0; iSubdivision < tessellation; ++iSubdivision) { // The new index collection after subdivision. var newIndices = new List <int>(); subdividedEdges.Clear(); int triangleCount = indexList.Count / 3; for (int iTriangle = 0; iTriangle < triangleCount; ++iTriangle) { // For each edge on this triangle, create a new vertex in the middle of that edge. // The winding order of the triangles we output are the same as the winding order of the inputs. // Indices of the vertices making up this triangle int iv0 = indexList[iTriangle * 3 + 0]; int iv1 = indexList[iTriangle * 3 + 1]; int iv2 = indexList[iTriangle * 3 + 2]; //// The existing vertices //Vector3 v0 = vertexPositions[iv0]; //Vector3 v1 = vertexPositions[iv1]; //Vector3 v2 = vertexPositions[iv2]; // Get the new vertices Vector3 v01; // vertex on the midpoint of v0 and v1 Vector3 v12; // ditto v1 and v2 Vector3 v20; // ditto v2 and v0 int iv01; // index of v01 int iv12; // index of v12 int iv20; // index of v20 // Add/get new vertices and their indices DivideEdge(iv0, iv1, out v01, out iv01); DivideEdge(iv1, iv2, out v12, out iv12); DivideEdge(iv0, iv2, out v20, out iv20); // Add the new indices. We have four new triangles from our original one: // v0 // o // /a\ // v20 o---o v01 // /b\c/d\ // v2 o---o---o v1 // v12 // a newIndices.Add(iv0); newIndices.Add(iv01); newIndices.Add(iv20); // b newIndices.Add(iv20); newIndices.Add(iv12); newIndices.Add(iv2); // d newIndices.Add(iv20); newIndices.Add(iv01); newIndices.Add(iv12); // d newIndices.Add(iv01); newIndices.Add(iv1); newIndices.Add(iv12); } indexList.Clear(); indexList.AddRange(newIndices); } // Now that we've completed subdivision, fill in the final vertex collection vertices = new List <VertexPositionNormalTexture>(vertexPositions.Count); for (int i = 0; i < vertexPositions.Count; i++) { var vertexValue = vertexPositions[i]; var normal = vertexValue; normal.Normalize(); var pos = normal * radius; // calculate texture coordinates for this vertex float longitude = (float)Math.Atan2(normal.X, -normal.Z); float latitude = (float)Math.Acos(normal.Y); float u = (float)(longitude / (Math.PI * 2.0) + 0.5); float v = (float)(latitude / Math.PI); var texcoord = new Vector2(1.0f - u, v); vertices.Add(new VertexPositionNormalTexture(pos, normal, texcoord)); } const float XMVectorSplatEpsilon = 1.192092896e-7f; // There are a couple of fixes to do. One is a texture coordinate wraparound fixup. At some point, there will be // a set of triangles somewhere in the mesh with texture coordinates such that the wraparound across 0.0/1.0 // occurs across that triangle. Eg. when the left hand side of the triangle has a U coordinate of 0.98 and the // right hand side has a U coordinate of 0.0. The intent is that such a triangle should render with a U of 0.98 to // 1.0, not 0.98 to 0.0. If we don't do this fixup, there will be a visible seam across one side of the sphere. // // Luckily this is relatively easy to fix. There is a straight edge which runs down the prime meridian of the // completed sphere. If you imagine the vertices along that edge, they circumscribe a semicircular arc starting at // y=1 and ending at y=-1, and sweeping across the range of z=0 to z=1. x stays zero. It's along this edge that we // need to duplicate our vertices - and provide the correct texture coordinates. int preCount = vertices.Count; var indicesArray = indexList.ToArray(); fixed(void *pIndices = indicesArray) { indices = (int *)pIndices; for (int i = 0; i < preCount; ++i) { // This vertex is on the prime meridian if position.x and texcoord.u are both zero (allowing for small epsilon). bool isOnPrimeMeridian = MathUtil.WithinEpsilon(vertices[i].Position.X, 0, XMVectorSplatEpsilon) && MathUtil.WithinEpsilon(vertices[i].TextureCoordinate.X, 0, XMVectorSplatEpsilon); if (isOnPrimeMeridian) { int newIndex = vertices.Count; // copy this vertex, correct the texture coordinate, and add the vertex VertexPositionNormalTexture v = vertices[i]; v.TextureCoordinate.X = 1.0f; vertices.Add(v); // Now find all the triangles which contain this vertex and update them if necessary for (int j = 0; j < indexList.Count; j += 3) { var triIndex0 = &indices[j + 0]; var triIndex1 = &indices[j + 1]; var triIndex2 = &indices[j + 2]; if (*triIndex0 == i) { // nothing; just keep going } else if (*triIndex1 == i) { Utilities.Swap(ref *triIndex0, ref *triIndex1); } else if (*triIndex2 == i) { Utilities.Swap(ref *triIndex0, ref *triIndex2); } else { // this triangle doesn't use the vertex we're interested in continue; } // check the other two vertices to see if we might need to fix this triangle if (Math.Abs(vertices[*triIndex0].TextureCoordinate.X - vertices[*triIndex1].TextureCoordinate.X) > 0.5f || Math.Abs(vertices[*triIndex0].TextureCoordinate.X - vertices[*triIndex2].TextureCoordinate.X) > 0.5f) { // yep; replace the specified index to point to the new, corrected vertex indices[j + 0] = newIndex; } } } } FixPole(northPoleIndex); FixPole(southPoleIndex); // Clear indices as it will not be accessible outside the fixed statement indices = (int *)0; } return(new GeometricMeshData <VertexPositionNormalTexture>(vertices.ToArray(), indexList.ToArray(), toLeftHanded) { Name = "GeoSphere" }); }