Exemple #1
0
            /// <summary>
            /// Creates a Geodesic sphere.
            /// </summary>
            /// <param name="graphicsDevice">The graphics device.</param>
            /// <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 true.</param>
            /// <returns>A Geodesic sphere.</returns>
            private unsafe GeometricPrimitive Create(GraphicsDevice graphicsDevice, 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 GeometricPrimitive(graphicsDevice, vertices.ToArray(), indexList.ToArray(), toLeftHanded)
                {
                    Name = "GeoSphere"
                });
            }
            /// <summary>
            /// Creates a sphere primitive.
            /// </summary>
            /// <param name="device">The device.</param>
            /// <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 true.</param>
            /// <returns>A sphere primitive.</returns>
            /// <exception cref="System.ArgumentOutOfRangeException">tessellation;Must be >= 3</exception>
            public static GeometricPrimitive New(GraphicsDevice device, float diameter = 1.0f, 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];

                float radius = diameter / 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);

                    // 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.
                return(new GeometricPrimitive(device, vertices, indices, toLeftHanded)
                {
                    Name = "Sphere"
                });
            }