Icosahedron Factory

/// <summary>
/// Creates new subdivided Icosahedron objects.
/// </summary>
public class IcosahedronFactory : Singleton<IcosahedronFactory>, IIcosahedronFactory
{
    #region Instance Members

    #region IIcosahedronFactory Members

    /// <summary>
    /// Creates a new Icosahedron subdivided based on the frequency provided.
    /// </summary>
    /// <param name="frequency">
    /// The frequency with which to subdivide the icosahedron.
    /// </param>
    /// <returns>A subdivided Icosahedron.</returns>
    public Icosahedron CreateIcosahedron(byte frequency)
    {
        Icosahedron ico = new Icosahedron();
        List<Triangle3D> pptList = new List<Triangle3D>();

        for (int i = 0; i < BaseIndices.Length; i += 3)
        {
            Triangle3D tri = new Triangle3D()
            {
                Corner1 = BasePositions[BaseIndices[i]],
                Corner2 = BasePositions[BaseIndices[i + 1]],
                Corner3 = BasePositions[BaseIndices[i + 2]],
            };

            pptList.Add(tri);
        }

        List<Triangle3D> subTriangles = GetSubTriangles(pptList, frequency);

        List<Vector3> vertices = new List<Vector3>();
        foreach (Triangle3D sub in subTriangles)
        {
            vertices.AddRange(sub.Corners);
        }

        List<int> indices = new List<int>();
        for (int i = 0; i < vertices.Count; i++)
            indices.Add(i);

        IndexVertices(vertices, out ico.Vertices, out ico.Indices);

        ico.Wireframe = IcosahedronWireframeFactory.Instance.CreateWireframe(ico);
        ico.Frequency = frequency;

        return ico;
    }

    #endregion

    #endregion

    #region Static Members

    #region Fields & Properties

    #region Private

    /// <summary>
    /// The golden ratio number.
    /// </summary>
    static readonly float Golden = (float)((1 + Math.Sqrt(5)) / 2);

    /// <summary>
    /// The indices of an icosahedron.
    /// </summary>
    static readonly int[] BaseIndices =
    {  
        0,  8,  1,      0,  1,  9,      0,  4,  8,      0,  5,  4,  
        0,  9,  5,      2, 11,  3,      2,  5, 11,      2,  4,  5,  
        2, 10,  4,      2,  3, 10,      1,  7,  9,      1,  6,  7,  
        1,  8,  6,      3,  6, 10,      3,  7,  6,      3, 11,  7,  
        4, 10,  8,      5,  9, 11,      6,  8, 10,      7, 11,  9
    };

    /// <summary>
    /// The vertices of an icosahedron.
    /// </summary>
    static readonly Vector3[] BasePositions =
    {  
        Vector3.Normalize(new Vector3(0, 1, Golden)),  
        Vector3.Normalize(new Vector3(0, -1, Golden)),  
        Vector3.Normalize(new Vector3(0, 1, -Golden)), 
        Vector3.Normalize(new Vector3(0, -1, -Golden)),  
        Vector3.Normalize(new Vector3(1, Golden, 0)),   
        Vector3.Normalize(new Vector3(-1, Golden, 0)),  
        Vector3.Normalize(new Vector3(1, -Golden, 0)),  
        Vector3.Normalize(new Vector3(-1, -Golden, 0)),  
        Vector3.Normalize(new Vector3(Golden, 0, 1)),   
        Vector3.Normalize(new Vector3(-Golden, 0, 1)),  
        Vector3.Normalize(new Vector3(Golden, 0, -1)),  
        Vector3.Normalize(new Vector3(-Golden, 0, -1))  
    };

    #endregion

    #endregion

    #region Methods

    #region Private

    #region Index Vertices

    /// <summary>
    /// Takes a list of Vector3 positions and indexes them while removing 
    /// duplicates.
    /// </summary>
    /// <param name="originalVertices">
    /// The list of Vector3 positions to index.
    /// </param>
    /// <param name="outVertices">
    /// The indexed and unduplicated vertices.
    /// </param>
    /// <param name="outIndices">
    /// Indices of the unduplicated vertices.
    /// </param>
    private static void IndexVertices(List<Vector3> originalVertices,
                                      out VertexPositionNormal[] outVertices, 
                                      out int[] outIndices)
    {
        Dictionary<Vector3, int> vertexIndices = new Dictionary<Vector3, int>();
        List<VertexPositionNormal> vertices = new List<VertexPositionNormal>();
        int indexCounter = 0;

        foreach (Vector3 position in originalVertices)
        {
            if (!vertexIndices.ContainsKey(position))
            {
                vertexIndices.Add(position, indexCounter++);
                vertices.Add(new VertexPositionNormal(position, Vector3.Normalize(position)));
            }
        }

        List<int> indices = new List<int>();
        foreach (Vector3 position in originalVertices)
        {
            indices.Add(vertexIndices[position]);
        }

        outVertices = vertices.ToArray();
        outIndices = indices.ToArray();
    }

    #endregion

    #region Get Sub Triangles

    /// <summary>
    /// Gets the subdivided triangles from a list of Principle Polyhedral 
    /// Triangles.
    /// </summary>
    /// <param name="pptList">
    /// List of Principle Polyhedral Triangles.
    /// </param>
    /// <param name="frequency">
    /// The frequency with which to subdivide the triangles.
    /// </param>
    /// <returns>
    /// List of triangles that are the result of subdivision.
    /// </returns>
    static List<Triangle3D> GetSubTriangles(List<Triangle3D> pptList, byte frequency)
    {
        List<Triangle3D> subTriangles = new List<Triangle3D>();
        Dictionary<LineSegment3D, List<Vector3>> splitLines = new Dictionary<LineSegment3D, List<Vector3>>();

        foreach (Triangle3D ppt in pptList)
        {
            LineSegment3D lineA = new LineSegment3D(ppt.Corner1, ppt.Corner2);
            List<Vector3> aPoints = GetPointsOfSplitLine(ref lineA, frequency, splitLines);

            LineSegment3D lineB = new LineSegment3D(ppt.Corner2, ppt.Corner3);
            // Split side B
            List<Vector3> bPoints = GetPointsOfSplitLine(ref lineB, frequency, splitLines);

            LineSegment3D lineC = new LineSegment3D(ppt.Corner1, ppt.Corner3);
            // Split side C
            List<Vector3> cPoints = GetPointsOfSplitLine(ref lineC, frequency, splitLines);

            for (byte j = 0; j < frequency; j++)
            {
                LineSegment3D topLine = new LineSegment3D(cPoints[j], aPoints[j]);
                LineSegment3D botLine = new LineSegment3D(cPoints[j + 1], aPoints[j + 1]);
                List<Vector3> topPoints = GetPointsOfSplitLine(ref topLine, j, splitLines);
                List<Vector3> botPoints = GetPointsOfSplitLine(ref botLine, (byte)(j + 1), splitLines);

                for (int k = 0; k < topPoints.Count; k++)
                {
                    Triangle3D tri = new Triangle3D()
                    {
                        Corner1 = topPoints[k],
                        Corner2 = botPoints[k + 1],
                        Corner3 = botPoints[k],
                    };
                    subTriangles.Add(tri);
                }

                if (j > 0)
                {
                    for (int k = 0; k < topPoints.Count - 1; k++)
                    {
                        Triangle3D tri = new Triangle3D()
                        {
                            Corner1 = topPoints[k],
                            Corner2 = topPoints[k + 1],
                            Corner3 = botPoints[k + 1],
                        };
                        subTriangles.Add(tri);
                    }
                }
            }
        }

        return subTriangles;
    }

    #endregion

    #region Split Line

    /// <summary>
    /// Given two points, returns a number of points from beginning to end.
    /// Approximates a path along the outside of a unit sphere.
    /// </summary>
    /// <param name="p1">Source value.</param>
    /// <param name="p2">Source value.</param>
    /// <param name="segments">The number of segments between points.</param>
    /// <returns>
    /// A list of points (totaling [segments + 1]) from one point to another.
    /// </returns>
    static List<Vector3> SplitLine(Vector3 p1, Vector3 p2, byte segments)
    {
        List<Vector3> points = new List<Vector3>();
        points.Add(p1);

        for (int i = 1; i < segments; i++)
        {
            Vector3 cross = Vector3.Cross(p1, p2);
            float crossLength = cross.Length();
            Vector3 crossNorm = Vector3.Normalize(cross);
            float angle = (float)Math.Asin(crossLength);
            Matrix rot = Matrix.CreateFromAxisAngle(crossNorm, (float)i / (float)segments * angle);
            Vector3 newP = Vector3.TransformNormal(p1, rot);
            points.Add(newP);
         }

        if (segments > 0)
            points.Add(p2);

        return points;
    }

    #endregion

    #region Get Points Of Split Line

    /// <summary>
    /// Gets a list of Vector3 positions organized from the line's Point1
    /// to the line's Point2.
    /// </summary>
    /// <param name="line">
    /// The LineSegment to get points between.
    /// </param>
    /// <param name="frequency">
    /// The frequency at which to subdivide the line.
    /// </param>
    /// <param name="splitLines">
    /// The Dictionary of previously subdivided lines.
    /// </param>
    /// <returns>
    /// List of Vector3 positions of the subdivided LineSegment.
    /// </returns>
    static List<Vector3> GetPointsOfSplitLine(ref LineSegment3D line, byte frequency, 
                                              Dictionary<LineSegment3D, List<Vector3>> splitLines)
    {
        List<Vector3> points;

        if (splitLines.ContainsKey(line))
        {
            if (splitLines[line][0] == line.Point1)
                points = splitLines[line];
            else
            {
                points = new List<Vector3>(splitLines[line].Count);
                for (int i = splitLines[line].Count - 1; i >= 0; i--)
                {
                    points.Add(splitLines[line][i]);
                }
            }
        }
        else
        {
            // Split side
            points = SplitLine(line.Point1, line.Point2, frequency);
            splitLines.Add(line, points);
        }

        return points;
    }

    #endregion

    #endregion

    #endregion

    #endregion
}

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: