[Code]Cube Sphere SkyBox Sphere From IndexedPrimitives.

The below class generates a cube or sphere with the specified number of vertices per face.
It creates smooth normals on the sphere as well as tangents for normal mapping.
It has a switch to create it as either a cube or sphere or to create it as a sky cube or sphere.
It has switches to use it with a standard cross type image or a blender block type image or with 6 seperate images per face. This is so it can be used for a RenderTargetCube.
It was created and tested to perform depth mapping or reflection but i extended it a bit and polished it up.

    /// <summary>
    /// This is a sphere or a sky sphere. A face resolution of 2 is also a cube or sky cube.
    /// It can use 6 seperate images on 6 faces or a cross or blender block type texture..
    /// Both Sphere and skyShere Uses CCW culling in regular operation.
    /// It generates positions normals texture and tangents for normal maping.
    /// It tesselates face points into sphereical coordinates on creation.
    /// It can also switch tangent or normal directions or u v that shouldn't be needed though.
    /// </summary>
    public class SpherePNTT
    {
        bool changeToSkySphere = false;
        bool changeToSingleImageTexture = true;
        bool blenderStyleElseCross = false;
        bool flipTangentSign = false;
        bool flipNormalDirection = false;
        bool flipU = false;
        bool flipV = false;
        int verticeFaceResolution = 3;
        float scale = 1f;

        int verticeFaceDrawOffset = 0;
        int indiceFaceDrawOffset = 0;
        int verticesPerFace = 0;
        int indicesPerFace = 0;
        int primitivesPerFace = 0;

        // face identifiers
        const int FaceFront = 0;
        const int FaceBack = 1;
        const int FaceLeft = 2;
        const int FaceRight = 3;
        const int FaceTop = 4;
        const int FaceBottom = 5;

        VertexPositionNormalTextureTangent[] vertices = new VertexPositionNormalTextureTangent[24];
        int[] indices = new int[36];

        /// <summary>
        /// Defaults to a seperate image hexahedron. 
        /// Use the other overloads if you want something more specific like a sphere.
        /// The spheres are counter clockwise wound.
        /// The skySphere is clockwise wound.
        /// </summary>
        public SpherePNTT()
        {
            CreateSixFaceSphere(true, false, false, false, false, false, false, verticeFaceResolution, scale);
        }
        // seperate faces
        public SpherePNTT(bool changeToSkySphere)
        {
            CreateSixFaceSphere(changeToSkySphere, false, false, false, false, false, false, verticeFaceResolution, scale);
        }
        // seperate faces at resolution
        public SpherePNTT(bool changeToSkySphere, int vertexResolutionPerFace, float scale)
        {
            CreateSixFaceSphere(changeToSkySphere, false, false, false, false, false, false, vertexResolutionPerFace, scale);
        }
        /// <summary>
        /// Set the type, if the faces are in a single image or six seperate images and if the single image is a cross or blender type image.
        /// Additionally specify the number of vertices per face this value is squared as it is used for rows and columns.
        /// </summary>
        public SpherePNTT(bool changeToSkySphere, bool changeToSingleImageTexture, bool blenderStyleSkyBox, int vertexResolutionPerFace, float scale)
        {
            CreateSixFaceSphere(changeToSkySphere, changeToSingleImageTexture, blenderStyleSkyBox, false, false, false, false, vertexResolutionPerFace, scale);
        }
        public SpherePNTT(bool changeToSkySphere, bool changeToSingleImageTexture, bool blenderStyleSkyBox, bool flipNormalDirection, bool flipTangentDirection, bool flipTextureDirectionU, bool flipTextureDirectionV, int vertexResolutionPerFace, float scale)
        {
            CreateSixFaceSphere(changeToSkySphere, changeToSingleImageTexture, blenderStyleSkyBox, flipNormalDirection, flipTangentDirection, flipTextureDirectionU, flipTextureDirectionV, vertexResolutionPerFace, scale);
        }

        void CreateSixFaceSphere(bool changeToSkySphere, bool changeToSingleImageTexture, bool blenderStyleElseCross, bool flipNormalDirection, bool flipTangentDirection, bool flipU, bool flipV, int vertexResolutionPerFace, float scale)
        {
            this.scale = scale;
            this.changeToSkySphere = changeToSkySphere;
            this.changeToSingleImageTexture = changeToSingleImageTexture;
            this.blenderStyleElseCross = blenderStyleElseCross;
            this.flipNormalDirection = flipNormalDirection;
            this.flipTangentSign = flipTangentDirection;
            this.flipU = flipU;
            this.flipV = flipV;
            if (vertexResolutionPerFace < 2)
                vertexResolutionPerFace = 2;
            this.verticeFaceResolution = vertexResolutionPerFace;
            Vector3 offset = new Vector3(.5f, .5f, .5f);
            // 8 vertice points ill label them, then reassign them for clarity.
            Vector3 LT_f = new Vector3(0, 1, 0) - offset; Vector3 A = LT_f * scale;
            Vector3 LB_f = new Vector3(0, 0, 0) - offset; Vector3 B = LB_f * scale;
            Vector3 RT_f = new Vector3(1, 1, 0) - offset; Vector3 C = RT_f * scale;
            Vector3 RB_f = new Vector3(1, 0, 0) - offset; Vector3 D = RB_f * scale;
            Vector3 LT_b = new Vector3(0, 1, 1) - offset; Vector3 E = LT_b * scale;
            Vector3 LB_b = new Vector3(0, 0, 1) - offset; Vector3 F = LB_b * scale;
            Vector3 RT_b = new Vector3(1, 1, 1) - offset; Vector3 G = RT_b * scale;
            Vector3 RB_b = new Vector3(1, 0, 1) - offset; Vector3 H = RB_b * scale;

            // Six faces to a cube or sphere
            // each face of the cube wont actually share vertices as each will use its own texture.
            // unless it is actually using single skybox texture

            // we will need to precalculate the grids size now
            int vw = vertexResolutionPerFace;
            int vh = vertexResolutionPerFace;
            int vlen = vw * vh * 6; // the extra six here is the number of faces
            int iw = vw - 1;
            int ih = vh - 1;
            int ilen = iw * ih * 6 * 6; // the extra six here is the number of faces
            vertices = new VertexPositionNormalTextureTangent[vlen];
            indices = new int[ilen];
            verticeFaceDrawOffset = vlen = vw * vh;
            indiceFaceDrawOffset = ilen = iw * ih * 6;
            verticesPerFace = vertexResolutionPerFace * vertexResolutionPerFace;
            indicesPerFace = iw * ih * 6;
            primitivesPerFace = iw * ih * 2; // 2 triangles per quad

            if (changeToSkySphere)
            {
                // passed uv texture coordinates.
                Vector2 uv0 = new Vector2(1f, 1f);
                Vector2 uv1 = new Vector2(0f, 1f);
                Vector2 uv2 = new Vector2(1f, 0f);
                Vector2 uv3 = new Vector2(0f, 0f);
                SetFaceGrid(FaceFront, D, B, C, A, uv0, uv1, uv2, uv3, vertexResolutionPerFace);
                SetFaceGrid(FaceBack, F, H, E, G, uv0, uv1, uv2, uv3, vertexResolutionPerFace);
                SetFaceGrid(FaceLeft, B, F, A, E, uv0, uv1, uv2, uv3, vertexResolutionPerFace);
                SetFaceGrid(FaceRight, H, D, G, C, uv0, uv1, uv2, uv3, vertexResolutionPerFace);
                SetFaceGrid(FaceTop, C, A, G, E, uv0, uv1, uv2, uv3, vertexResolutionPerFace);
                SetFaceGrid(FaceBottom, H, F, D, B, uv0, uv1, uv2, uv3, vertexResolutionPerFace);
            }
            else // regular sphere or cube
            {
                Vector2 uv0 = new Vector2(0f, 0f);
                Vector2 uv1 = new Vector2(0f, 1f);
                Vector2 uv2 = new Vector2(1f, 0f);
                Vector2 uv3 = new Vector2(1f, 1f);
                SetFaceGrid(FaceFront, A, B, C, D, uv0, uv1, uv2, uv3, vertexResolutionPerFace);
                SetFaceGrid(FaceBack, G, H, E, F, uv0, uv1, uv2, uv3, vertexResolutionPerFace);
                SetFaceGrid(FaceLeft, E, F, A, B, uv0, uv1, uv2, uv3, vertexResolutionPerFace);
                SetFaceGrid(FaceRight, C, D, G, H, uv0, uv1, uv2, uv3, vertexResolutionPerFace);
                SetFaceGrid(FaceTop, E, A, G, C, uv0, uv1, uv2, uv3, vertexResolutionPerFace);
                SetFaceGrid(FaceBottom, B, F, D, H, uv0, uv1, uv2, uv3, vertexResolutionPerFace);
            }
        }

        void SetFaceGrid(int faceMultiplier, Vector3 v0, Vector3 v1, Vector3 v2, Vector3 v3, Vector2 uv0, Vector2 uv1, Vector2 uv2, Vector2 uv3, int vertexResolution)
        {
            if (changeToSingleImageTexture)
                UvSkyTextureReassignment(faceMultiplier, ref uv0, ref uv1, ref uv2, ref uv3);
            int vw = vertexResolution;
            int vh = vertexResolution;
            int vlen = vw * vh;
            int iw = vw - 1;
            int ih = vh - 1;
            int ilen = iw * ih * 6;
            // actual start index's
            int vIndex = faceMultiplier * vlen;
            int iIndex = faceMultiplier * ilen;
            // we now must build the grid/
            float ratio = 1f / (float)(vertexResolution - 1);
            // well do it all simultaneously no point in spliting it up
            for (int y = 0; y < vertexResolution; y++)
            {
                float ratioY = (float)y * ratio;
                for (int x = 0; x < vertexResolution; x++)
                {
                    // index
                    int index = vIndex + (y * vertexResolution + x);
                    float ratioX = (float)x * ratio;
                    // calculate uv_n_p tangent comes later
                    var uv = InterpolateUv(uv0, uv1, uv2, uv3, ratioX, ratioY);
                    var n = InterpolateToNormal(v0, v1, v2, v3, ratioX, ratioY);
                    var p = n * .5f; // displace to distance
                    if (changeToSkySphere)
                        n = -n;
                    if (flipNormalDirection)
                        n = -n;
                    // handle u v fliping if its desired.
                    if (flipU)
                        uv.X = 1.0f - uv.X;
                    if (flipV)
                        uv.Y = 1.0f - uv.Y;
                    // assign
                    vertices[index].Position = p;
                    vertices[index].TextureCoordinate = uv;
                    vertices[index].Normal = n;
                }
            }

            // ToDo... 
            // We could loop all the vertices which are nearly the exact same and make sure they are the same place but seperate.
            // sort of redundant but floating point errors happen under interpolation, well get back to that later on.
            // not sure i really need to it looks pretty spot on.

            // ok so now we have are positions our normal and uv per vertice we need to loop again and handle the tangents
            for (int y = 0; y < (vertexResolution - 1); y++)
            {
                for (int x = 0; x < (vertexResolution - 1); x++)
                {
                    //
                    int indexV0 = vIndex + (y * vertexResolution + x);
                    int indexV1 = vIndex + ((y + 1) * vertexResolution + x);
                    int indexV2 = vIndex + (y * vertexResolution + (x + 1));
                    int indexV3 = vIndex + ((y + 1) * vertexResolution + (x + 1));
                    var p0 = vertices[indexV0].Position;
                    var p1 = vertices[indexV1].Position;
                    var p2 = vertices[indexV2].Position;
                    var p3 = vertices[indexV3].Position;
                    var t = -(p0 - p1);
                    if (changeToSkySphere)
                        t = -t;
                    t.Normalize();
                    if (flipTangentSign)
                        t = -t;
                    vertices[indexV0].Tangent = t; vertices[indexV1].Tangent = t; vertices[indexV2].Tangent = t; vertices[indexV3].Tangent = t;
                    //
                    // set our indices while were at it.
                    int indexI = iIndex + ((y * (vertexResolution - 1) + x) * 6);
                    int via = indexV0, vib = indexV1, vic = indexV2, vid = indexV3;
                    indices[indexI + 0] = via; indices[indexI + 1] = vib; indices[indexI + 2] = vic;
                    indices[indexI + 3] = vic; indices[indexI + 4] = vib; indices[indexI + 5] = vid;
                }
            }
        }

        // this allows for the use of a single texture skybox.
        void UvSkyTextureReassignment(int faceMultiplier, ref Vector2 uv0, ref Vector2 uv1, ref Vector2 uv2, ref Vector2 uv3)
        {
            if (changeToSingleImageTexture)
            {
                Vector2 tupeBuvwh = new Vector2(.250000000f, .333333333f); // this is a 8 square left sided skybox
                Vector2 tupeAuvwh = new Vector2(.333333333f, .500000000f); // this is a 6 square blender type skybox
                Vector2 currentuvWH = tupeBuvwh;
                Vector2 uvStart = Vector2.Zero;
                Vector2 uvEnd = Vector2.Zero;

                // crossstyle
                if (blenderStyleElseCross == false)
                {
                    currentuvWH = tupeBuvwh;
                    switch (faceMultiplier)
                    {
                        case FaceFront:
                            uvStart = new Vector2(currentuvWH.X * 1f, currentuvWH.Y * 1f);
                            uvEnd = uvStart + currentuvWH;
                            break;
                        case FaceBack:
                            uvStart = new Vector2(currentuvWH.X * 3f, currentuvWH.Y * 1f);
                            uvEnd = uvStart + currentuvWH;
                            break;
                        case FaceRight:
                            uvStart = new Vector2(currentuvWH.X * 2f, currentuvWH.Y * 1f);
                            uvEnd = uvStart + currentuvWH;
                            break;
                        case FaceLeft:
                            uvStart = new Vector2(currentuvWH.X * 0f, currentuvWH.Y * 1f);
                            uvEnd = uvStart + currentuvWH;
                            //uvStart = new Vector2(currentuvWH.X * 1f, currentuvWH.Y * 0f);
                            //uvEnd = uvStart + currentuvWH;
                            break;
                        case FaceTop:
                            uvStart = new Vector2(currentuvWH.X * 1f, currentuvWH.Y * 0f);
                            uvEnd = uvStart + currentuvWH;
                            break;
                        case FaceBottom:
                            uvStart = new Vector2(currentuvWH.X * 1f, currentuvWH.Y * 2f);
                            uvEnd = uvStart + currentuvWH;
                            break;
                    }
                    if (changeToSkySphere)
                    {
                        uv0 = new Vector2(uvEnd.X, uvEnd.Y); uv1 = new Vector2(uvStart.X, uvEnd.Y); uv2 = new Vector2(uvEnd.X, uvStart.Y); uv3 = new Vector2(uvStart.X, uvStart.Y);
                    }
                    else
                    {
                        uv0 = new Vector2(uvStart.X, uvStart.Y); uv1 = new Vector2(uvStart.X, uvEnd.Y); uv2 = new Vector2(uvEnd.X, uvStart.Y); uv3 = new Vector2(uvEnd.X, uvEnd.Y);
                    }
                }
                else
                {
                    currentuvWH = tupeAuvwh;
                    switch (faceMultiplier)
                    {
                        case FaceLeft:
                            uvStart = new Vector2(currentuvWH.X * 0f, currentuvWH.Y * 0f);
                            uvEnd = uvStart + currentuvWH;
                            break;
                        case FaceBack:
                            uvStart = new Vector2(currentuvWH.X * 1f, currentuvWH.Y * 0f);
                            uvEnd = uvStart + currentuvWH;
                            break;
                        case FaceRight:
                            uvStart = new Vector2(currentuvWH.X * 2f, currentuvWH.Y * 0f);
                            uvEnd = uvStart + currentuvWH;
                            break;
                        case FaceBottom:
                            uvStart = new Vector2(currentuvWH.X * 0f, currentuvWH.Y * 1f);
                            uvEnd = uvStart + currentuvWH;
                            break;
                        case FaceTop:
                            uvStart = new Vector2(currentuvWH.X * 1f, currentuvWH.Y * 1f);
                            uvEnd = uvStart + currentuvWH;
                            break;
                        case FaceFront:
                            uvStart = new Vector2(currentuvWH.X * 2f, currentuvWH.Y * 1f);
                            uvEnd = uvStart + currentuvWH;
                            break;
                    }
                    if (changeToSkySphere)
                    {
                        uv0 = new Vector2(uvEnd.X, uvEnd.Y); uv2 = new Vector2(uvEnd.X, uvStart.Y); uv1 = new Vector2(uvStart.X, uvEnd.Y); uv3 = new Vector2(uvStart.X, uvStart.Y);
                    }
                    else
                    {
                        uv0 = new Vector2(uvStart.X, uvStart.Y); uv1 = new Vector2(uvStart.X, uvEnd.Y); uv2 = new Vector2(uvEnd.X, uvStart.Y); uv3 = new Vector2(uvEnd.X, uvEnd.Y);
                    }
                }
            }
        }

        Vector3 InterpolateToNormal(Vector3 v0, Vector3 v1, Vector3 v2, Vector3 v3, float timeX, float timeY)
        {
            var y0 = ((v1 - v0) * timeY + v0);
            var y1 = ((v3 - v2) * timeY + v2);
            var n = ((y1 - y0) * timeX + y0) * 10f; // * 10f ensure its sufficiently denormalized.
            n.Normalize();
            return n;
        }
        Vector2 InterpolateUv(Vector2 v0, Vector2 v1, Vector2 v2, Vector2 v3, float timeX, float timeY)
        {
            var y0 = ((v1 - v0) * timeY + v0);
            var y1 = ((v3 - v2) * timeY + v2);
            return ((y1 - y0) * timeX + y0);
        }

        public void Draw(GraphicsDevice gd, Effect effect)
        {
            foreach (EffectPass pass in effect.CurrentTechnique.Passes)
            {
                pass.Apply();
                gd.DrawUserIndexedPrimitives(PrimitiveType.TriangleList, vertices, 0, vertices.Length, indices, 0, (indices.Length / 3), VertexPositionNormalTextureTangent.VertexDeclaration);
            }
        }

        /// <summary>
        /// Seperate faced cube or sphere or sky
        /// This method is pretty dependant on being able to pass to textureA not good but....
        /// You can rename that to whatever name you use for your texture in your shader.
        /// </summary>
        public void Draw(GraphicsDevice gd, Effect effect, Texture2D front, Texture2D back, Texture2D left, Texture2D right, Texture2D top, Texture2D bottom)
        {
            int FaceFront = 0;
            int FaceBack = 1;
            int FaceLeft = 2;
            int FaceRight = 3;
            int FaceTop = 4;
            int FaceBottom = 5;
            for (int t = 0; t < 6; t++)
            {
                if (t == FaceFront) effect.Parameters["TextureA"].SetValue(front);
                if (t == FaceBack) effect.Parameters["TextureA"].SetValue(back);
                if (t == FaceLeft) effect.Parameters["TextureA"].SetValue(left);
                if (t == FaceRight) effect.Parameters["TextureA"].SetValue(right);
                if (t == FaceTop) effect.Parameters["TextureA"].SetValue(top);
                if (t == FaceBottom) effect.Parameters["TextureA"].SetValue(bottom);
                int ifoffset = t * indicesPerFace;
                foreach (EffectPass pass in effect.CurrentTechnique.Passes)
                {
                    pass.Apply();
                    gd.DrawUserIndexedPrimitives(PrimitiveType.TriangleList, vertices, 0, vertices.Length, indices, ifoffset, primitivesPerFace, VertexPositionNormalTextureTangent.VertexDeclaration);
                }
            }
        }

        /// <summary>
        /// Single texture multi faced cube or sphere or sky
        /// This method is pretty dependant on being able to pass to textureA not good but.... 
        /// You can rename that to whatever name you use for your texture in your shader.
        /// </summary>
        public void Draw(GraphicsDevice gd, Effect effect, Texture2D cubeTexture)
        {
            effect.Parameters["TextureA"].SetValue(cubeTexture);
            for (int t = 0; t < 6; t++)
            {
                int ifoffset = t * indicesPerFace;
                foreach (EffectPass pass in effect.CurrentTechnique.Passes)
                {
                    pass.Apply();
                    gd.DrawUserIndexedPrimitives(PrimitiveType.TriangleList, vertices, 0, vertices.Length, indices, ifoffset, primitivesPerFace, VertexPositionNormalTextureTangent.VertexDeclaration);
                }
            }
        }

        /// <summary>
        /// Untested. 
        /// </summary>
        public void DrawWithBasicEffect(GraphicsDevice gd, BasicEffect effect, Texture2D front, Texture2D back, Texture2D left, Texture2D right, Texture2D top, Texture2D bottom)
        {
            int FaceFront = 0;
            int FaceBack = 1;
            int FaceLeft = 2;
            int FaceRight = 3;
            int FaceTop = 4;
            int FaceBottom = 5;
            for (int t = 0; t < 6; t++)
            {
                if (t == FaceFront) effect.Texture = front;
                if (t == FaceBack) effect.Texture = back;
                if (t == FaceLeft) effect.Texture = left;
                if (t == FaceRight) effect.Texture = right;
                if (t == FaceTop) effect.Texture = top;
                if (t == FaceBottom) effect.Texture = bottom;
                int vi = t * 4;
                int ii = t * indicesPerFace;
                foreach (EffectPass pass in effect.CurrentTechnique.Passes)
                {
                    pass.Apply();
                    gd.DrawUserIndexedPrimitives(PrimitiveType.TriangleList, vertices, 0, vertices.Length, indices, ii, primitivesPerFace, VertexPositionNormalTextureTangent.VertexDeclaration);
                }
            }
        }

        /// <summary>
        /// Single texture multi faced cube or sphere or sky
        /// Untested.
        /// </summary>
        public void DrawWithBasicEffect(GraphicsDevice gd, BasicEffect effect, Texture2D cubeTexture)
        {
            effect.Texture = cubeTexture;
            for (int t = 0; t < 6; t++)
            {
                int ifoffset = t * indicesPerFace;
                foreach (EffectPass pass in effect.CurrentTechnique.Passes)
                {
                    pass.Apply();
                    gd.DrawUserIndexedPrimitives(PrimitiveType.TriangleList, vertices, 0, vertices.Length, indices, ifoffset, primitivesPerFace, VertexPositionNormalTextureTangent.VertexDeclaration);
                }
            }
        }

        public Vector3 Norm(Vector3 n)
        {
            return Vector3.Normalize(n);
        }

        /// <summary>
        /// Positional cross product, Counter Clock wise positive.
        /// </summary>
        public static Vector3 CrossVectors3d(Vector3 a, Vector3 b, Vector3 c)
        {
            // no point in doing reassignments the calculation is straight forward.
            return new Vector3
                (
                ((b.Y - a.Y) * (c.Z - b.Z)) - ((c.Y - b.Y) * (b.Z - a.Z)),
                ((b.Z - a.Z) * (c.X - b.X)) - ((c.Z - b.Z) * (b.X - a.X)),
                ((b.X - a.X) * (c.Y - b.Y)) - ((c.X - b.X) * (b.Y - a.Y))
                );
        }

        /// <summary>
        /// use the vector3 cross
        /// </summary>
        public static Vector3 CrossXna(Vector3 a, Vector3 b, Vector3 c)
        {
            var v1 = a - b;
            var v2 = c - b;

            return Vector3.Cross(v1, v2);
        }

        // vertex structure data.
        public struct VertexPositionNormalTextureTangent : IVertexType
        {
            public Vector3 Position;
            public Vector3 Normal;
            public Vector2 TextureCoordinate;
            public Vector3 Tangent;

            public static VertexDeclaration VertexDeclaration = new VertexDeclaration
            (
                  new VertexElement(VertexElementByteOffset.PositionStartOffset(), VertexElementFormat.Vector3, VertexElementUsage.Position, 0),
                  new VertexElement(VertexElementByteOffset.OffsetVector3(), VertexElementFormat.Vector3, VertexElementUsage.Normal, 0),
                  new VertexElement(VertexElementByteOffset.OffsetVector2(), VertexElementFormat.Vector2, VertexElementUsage.TextureCoordinate, 0),
                  new VertexElement(VertexElementByteOffset.OffsetVector3(), VertexElementFormat.Vector3, VertexElementUsage.Normal, 1)
            );
            VertexDeclaration IVertexType.VertexDeclaration { get { return VertexDeclaration; } }
        }
        /// <summary>
        /// This is a helper struct for tallying byte offsets
        /// </summary>
        public struct VertexElementByteOffset
        {
            public static int currentByteSize = 0;
            [STAThread]
            public static int PositionStartOffset() { currentByteSize = 0; var s = sizeof(float) * 3; currentByteSize += s; return currentByteSize - s; }
            public static int Offset(float n) { var s = sizeof(float); currentByteSize += s; return currentByteSize - s; }
            public static int Offset(Vector2 n) { var s = sizeof(float) * 2; currentByteSize += s; return currentByteSize - s; }
            public static int Offset(Color n) { var s = sizeof(int); currentByteSize += s; return currentByteSize - s; }
            public static int Offset(Vector3 n) { var s = sizeof(float) * 3; currentByteSize += s; return currentByteSize - s; }
            public static int Offset(Vector4 n) { var s = sizeof(float) * 4; currentByteSize += s; return currentByteSize - s; }

            public static int OffsetFloat() { var s = sizeof(float); currentByteSize += s; return currentByteSize - s; }
            public static int OffsetColor() { var s = sizeof(int); currentByteSize += s; return currentByteSize - s; }
            public static int OffsetVector2() { var s = sizeof(float) * 2; currentByteSize += s; return currentByteSize - s; }
            public static int OffsetVector3() { var s = sizeof(float) * 3; currentByteSize += s; return currentByteSize - s; }
            public static int OffsetVector4() { var s = sizeof(float) * 4; currentByteSize += s; return currentByteSize - s; }
        }
    }

Here is a example project using basic effect.

From a higher to lower resolution and with tangents mapped to spheres.

Some of these are smaller sky spheres or reflection maps so the labels on them are mirrored from the outside.

Here is a shader that shows how to use the tangents in the sphere.
These are typically used to map a normal map to the sphere.
This can be used for skyspheres or just regular spheres.

The one nice thing about this is you don’t need to load a model.

It seems to look good to me when the resolution is set over 10 or 15.
At a resolution of 2 it is just a cube.

//_______________________________________________________________
// technique 
// NormalMapLightShadowDrawing
//
// requires a tangent in the vertex data and 3 textures pixel , normalmap, depthmap
// 
// This one has a couple flaws still one of them is 
// The side opposite the light still can get some specular reflection when using normal maps. 
// I still need to fix that.
//_______________________________________________________________

struct VsNormMapLightShadowInput
{
    float4 Position : POSITION0;
    float3 Normal : NORMAL0;
    float2 TexureCoordinateA : TEXCOORD0;
    float3 Tangent : NORMAL1;
};

struct VsNormMapLightShadowOutput
{
    float4 Position : SV_Position;//: SV_POSITION;
    float4 Position3D : TEXCOORD4;
    float2 TexureCoordinateA : TEXCOORD0;
    float3 Normal: TEXCOORD1;
    float3 Tangent : TEXCOORD2;
};

VsNormMapLightShadowOutput VsNormMapLightShadow(VsNormMapLightShadowInput input)
{

    VsNormMapLightShadowOutput output;
    output.Position3D = mul(input.Position, World);
    float4x4 vp = mul(View, Projection);
    output.Position = mul(output.Position3D, vp);
    output.Normal = input.Normal;
    output.Tangent = input.Tangent;
    output.TexureCoordinateA = input.TexureCoordinateA;
    return output;
}

float4 PsNormMapLightShadow(VsNormMapLightShadowOutput input) : COLOR0
{
    // Normal Map
    float3 NormalMap = tex2D(TextureNormalSampler, input.TexureCoordinateA).rgb;
    NormalMap.g = 1.0f - NormalMap.g; // flips the y. the program i used fliped the green.
    NormalMap = normalize(NormalMap * 2.0 - 1.0);
    float3 normal = input.Normal;
    float3 tangent = input.Tangent;
    float3x3 mat;
    mat[0] = cross(normal, tangent); // right
    mat[1] = tangent; // up
    mat[2] = normal; // forward
    NormalMap = mul(NormalMap, mat);
    NormalMap = mul(NormalMap, World);
    NormalMap = normalize(NormalMap); // we do this to ensure scaling wont break the normal.
    // prep
    float3 temp = WorldLightPosition - input.Position3D;
    float distancePixelToLight = length(temp);
    float3 surfaceToCamera = normalize(CameraPosition - input.Position3D);
    float3 surfaceToLight = temp / distancePixelToLight; // cheapen the normalize. normalize(pixelToLight);
    float3 lightToSurface = -surfaceToLight;
    float shadowDepth = texCUBE(TextureDepthSampler, float4(lightToSurface, 0)).x;
    float4 TexelColor = tex2D(TextureSamplerA, input.TexureCoordinateA) * (1.0f - LightVsTexelRatio) + (LightColor * LightVsTexelRatio); // LightVsTexelRatio == .5 is normal        
    // shadow 
    float lightFalloff = (1.0f - saturate(distancePixelToLight / (IlluminationRange + 0.001f)));
    float LightDistanceIntensity = saturate(sign((shadowDepth + .2f) - distancePixelToLight)) * lightFalloff; // if removal.
    // lighting
    float3 surfNom = NormalMap;
    float diffuse = saturate((dot(lightToSurface, -surfNom) + DiffuseCresting) * (1.0f / (1.0f + DiffuseCresting))); // I've added over or underdraw to the diffuse.
    diffuse *= diffuse;
    float3 reflectionTheta = dot(surfaceToCamera, -reflect(surfaceToLight, surfNom));
    float specular = saturate(reflectionTheta - SpecularSharpness) * (1.0f / (1.0f - SpecularSharpness)); // this is for sharpness i didn't want to use powers.
    // finalize it.
    float3 inverseAmbientControl = 1.0f - AmbientStrength;
    float3 additiveAmbient = AmbientStrength;
    float3 additiveDiffuse = diffuse * DiffuseStrength * LightDistanceIntensity * inverseAmbientControl;
    float3 additiveSpecular = specular * SpecularStrength  * LightDistanceIntensity  * inverseAmbientControl;
    float3 FinalColor = TexelColor * (additiveAmbient + additiveDiffuse + additiveSpecular);
    return float4(FinalColor, 1.0f);
}

technique NormalMapLightShadowDrawing
{
    pass
    {
        VertexShader = compile VS_SHADERMODEL VsNormMapLightShadow();
        PixelShader = compile PS_SHADERMODEL PsNormMapLightShadow();
    }
}

Why?

http://www.iquilezles.org/www/articles/patchedsphere/patchedsphere.htm

There’s no point. The normalized vector of any point on a cube multiplied by the radii is the sphere point. Phi theta rho can then be collapsed back to the cube, which is older than any living person.

You’re concocting wasteful math to do trivial tasks.

Unfortunately this doesn’t compile.

So i have no basis to compare for your complaint.

If i were to color each face of my cube/sphere you would see the exact same thing.

Which i have done below.

If i am to divine your exact critisism. Which is not entirely clear to me.

There’s no point. The normalized vector of any point on a cube
multiplied by the radii is the sphere point. Phi theta rho can then be
collapsed back to the cube, which is older than any living person.

It seems you are complaining that i am not doing what i am in fact doing ?

I am actually starting with a cube face exactly like that picture shows (a value of 2 given in the above class constructor is a cube). I am then just interpolating points to find the position and the normal.
This is because the class allows the user to specify the number of vertices per face for the width and height.
The interpolated points on the sphere form the normals by the difference from the center.
That value is then simply normalized to create a normal n = normalize(p - c);
I don’t see how that could be more simple.

You’re concocting wasteful math to do trivial tasks.

While this may seem wasteful much of the seemingly extra code is so that the cube or sphere can be used as a skybox or a sphere with the same winding, As well with different types of standard image mappings, so there is quite a bit of extra code to handle different cases of textures passed in.

To say it can take textures of the following types.

A left cross standard image.
A blender style 6 face texture were there is no wasted part of the single texture.
Six individual images per face.
(primarily but not neccessarily for rendertarget cube operations and such)

If you note the class allows for fliping of some other values. In order to deal with specific cases were the user may need some specialization as well. Which may arrise for any unknown reason. If you look again much of the seeming extra fluff is just handling special cases or options.

It’s also made to be used easily on the outside not to look pretty on the inside.
To use it you only need to construct a instance, give it a texture, and draw it.
Though you might need to alter the draw method with your own shader.

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;

namespace CubeTestWithBasicEffect
{
    public class Game1 : Game
    {
        GraphicsDeviceManager graphics;
        SpriteBatch spriteBatch;
        BasicEffect beffect;

        SpherePNTT sphere;

        public Game1()
        {
            graphics = new GraphicsDeviceManager(this);
            Content.RootDirectory = "Content";
        }

        protected override void Initialize()
        {
            base.Initialize();
        }

        protected override void LoadContent()
        {
            spriteBatch = new SpriteBatch(GraphicsDevice);

            BasicTextures.Load(GraphicsDevice);

            sphere = new SpherePNTT(false, 10, 1f);

            // Set up a basic effect.
            beffect = new BasicEffect(GraphicsDevice);
            beffect.World = Matrix.CreateWorld(Vector3.Zero, Vector3.Normalize(new Vector3(-.3f,-.3f,-1f)), Vector3.Up);
            beffect.View = Matrix.CreateLookAt(new Vector3(0f, 0f, 2f), Vector3.Forward, Vector3.Up);
            beffect.Projection = Matrix.CreatePerspectiveFieldOfView(1.2f, 1f, .1f, 100f);
            beffect.TextureEnabled = true;
            beffect.Texture = BasicTextures.checkerBoard;
            //beffect.EnableDefaultLighting();
        }

        protected override void UnloadContent()
        {
            BasicTextures.Dispose();
        }

        protected override void Update(GameTime gameTime)
        {
            if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed || Keyboard.GetState().IsKeyDown(Keys.Escape))
                Exit();

            base.Update(gameTime);
        }

        protected override void Draw(GameTime gameTime)
        {
            GraphicsDevice.Clear(Color.CornflowerBlue);

            sphere.DrawWithBasicEffect(GraphicsDevice, beffect, BasicTextures.green, BasicTextures.blue, BasicTextures.red, BasicTextures.orange, BasicTextures.aqua, BasicTextures.moccasin);

            base.Draw(gameTime);
        }
    }
}