Friday, November 22, 2013

Open TK Tutorial 4 Addendum 2: The Sierpinski Tetrahedron, a Basic Fractal

Let's add a more complex shape to the program now. Cubes are nice, but unless we're making a Minecraft clone, they're not much to show off. In this example we'll have a class for making a Sierpinski Tetrahedron.

The Sierpinski Tetrahedron is a 3-dimensional version of the Sierpinski Triangle. In simple terms, the Sierpinski Triangle is a fractal created by cutting a triangle out of another triangle, and then repeating for the triangles you make doing so.
(public domain image from Wikimedia Commons)
The Sierpinski Tetrahedron is based on a similar idea, but with a 3-dimensional shape.

For this, we'll need to make two classes. The first is a tetrahedron, which I called Tetra:

    class Tetra : Volume
    {
        Vector3 PointApex;
        Vector3 PointA;
        Vector3 PointB;
        Vector3 PointC;

        public Tetra(Vector3 apex, Vector3 a, Vector3 b, Vector3 c)
        {
            PointApex = apex;
            PointA = a;
            PointB = b;
            PointC = c;

            VertCount = 4;
            IndiceCount = 12;
            ColorDataCount = 4;
        }

        public List<Tetra> Divide(int n = 0)
        {
            if (n == 0)
            {
                return new List<Tetra>(new Tetra[] { this });
            }
            else
            {

                Vector3 halfa = (PointApex + PointA) / 2.0f;
                Vector3 halfb = (PointApex + PointB) / 2.0f;
                Vector3 halfc = (PointApex + PointC) / 2.0f;

                // Calculate points half way between base points
                Vector3 halfab = (PointA + PointB) / 2.0f;
                Vector3 halfbc = (PointB + PointC) / 2.0f;
                Vector3 halfac = (PointA + PointC) / 2.0f;

                Tetra t1 = new Tetra(PointApex, halfa, halfb, halfc);
                Tetra t2 = new Tetra(halfa, PointA, halfab, halfac);
                Tetra t3 = new Tetra(halfb, halfab, PointB, halfbc);
                Tetra t4 = new Tetra(halfc, halfac, halfbc, PointC);

                List<Tetra> output = new List<Tetra>();

                output.AddRange(t1.Divide(n - 1));
                output.AddRange(t2.Divide(n - 1));
                output.AddRange(t3.Divide(n - 1));
                output.AddRange(t4.Divide(n - 1));

                return output;

            }
        }

        public override Vector3[] GetVerts()
        {
            return new Vector3[] { PointApex, PointA, PointB, PointC };
        }

        public override int[] GetIndices(int offset = 0)
        {
            int[] inds = new int[] { //bottom
                                    1,3,2,
                                    //other sides
                                    0,1,2,
                                    0,2,3,
                                    0,3,1
            };

            if (offset != 0)
            {
                for (int i = 0; i < inds.Length; i++)
                {
                    inds[i] += offset;
                }
            }

            return inds;
        }

        public override Vector3[] GetColorData()
        {
            return new Vector3[] { new Vector3(1f, 0f, 0f), new Vector3(0f, 1f, 0f), new Vector3(0f, 0f, 1f), new Vector3(1f, 1f, 0f) };
        }

        public override void CalculateModelMatrix()
        {
            ModelMatrix = Matrix4.Scale(Scale) * Matrix4.CreateRotationX(Rotation.X) * Matrix4.CreateRotationY(Rotation.Y) * Matrix4.CreateRotationZ(Rotation.Z) * Matrix4.CreateTranslation(Position);
        }
    }

In this class we have four vertices defined and have indices to make a tetrahedron out of them (if you want individually colored sides, you'll need to add more indices). It also has a function to divide the tetrahedron, making the smaller ones for the fractal.

Next we have a class for the fractal itself:

    class Sierpinski : Volume
    {

        public Sierpinski(int numSubdivisions = 1)
        {
            int NumTris = (int)Math.Pow(4,numSubdivisions + 1);

            VertCount = NumTris;
            ColorDataCount = NumTris;
            IndiceCount = 3 * NumTris;

            Tetra twhole =  new Tetra(new Vector3(0.0f, 0.0f, 1.0f),  // Apex center 
                            new Vector3(0.943f, 0.0f, -0.333f),  // Base center top
                            new Vector3(-0.471f, 0.816f, -0.333f),  // Base left bottom
                            new Vector3(-0.471f, -0.816f, -0.333f));

            List<Tetra> allTets = twhole.Divide(numSubdivisions);

            int offset = 0;
            foreach (Tetra t in allTets)
            {
                verts.AddRange(t.GetVerts());
                indices.AddRange(t.GetIndices(offset * 4));
                colors.AddRange(t.GetColorData());
                offset++;
            }

        }

        private List<Vector3> verts = new List<Vector3>();
        private List<int> indices = new List<int>();
        private List<Vector3> colors = new List<Vector3>();

        public override Vector3[] GetVerts()
        {
            return verts.ToArray();
        }

        public override Vector3[] GetColorData()
        {
            return colors.ToArray();
        }

        public override int[] GetIndices(int offset = 0)
        {
            int[] inds = indices.ToArray();

            if (offset != 0)
            {
                for (int i = 0; i < inds.Length; i++)
                {
                    inds[i] += offset;
                }
            }

            return inds;
        }

        public override void CalculateModelMatrix()
        {
            ModelMatrix = Matrix4.Scale(Scale) * Matrix4.CreateRotationX(Rotation.X) * Matrix4.CreateRotationY(Rotation.Y) * Matrix4.CreateRotationZ(Rotation.Z) * Matrix4.CreateTranslation(Position);
        }

    }
The constructor takes an argument for the number of times the tetrahedron is split up, an the rest of it is pretty standard. With this class and some code to place them randomly (and with random iterations of the fractal), I got something that looks like this:

2 comments:

  1. fun way to get out of memory exception...

    ReplyDelete
  2. This comment has been removed by a blog administrator.

    ReplyDelete