DEV Community

Murilo Luis Calvo Neves
Murilo Luis Calvo Neves

Posted on

Creating a stunning fractal tree in C#

Hello, in this article, we are going to build together a program that generates a fractal tree using the C# language and the SimpleDraw interface.

To get started, we need to know what is a fractal tree: a fractal tree is a pattern made by drawing a line (usually vertical),
and, from the end of that line, drawing two (or more) lines with some angle respective to the previous line and half the length (or another factor).

We then repeat this process a few times and the pattern we are left with looks like a tree, hence the name.
The most common way to do this is by using recursion in a function, which is when a function calls itself again, generating a loop.

This loop needs to be constrained (with a final condition) so that our code does not run forever.

To get started, let's make a Windows Forms application in Visual Studio and choose C# as our language.

Then let's add SimpleDraw into our project, we can do this in our 'solutions manager' tab in VS, by right-clicking the 'dependencies' item and selecting our SimpleDraw binary which can be found in here.

Once we have that done, we need to actually make a canvas so we can draw on it. To do that we go to our Form1.cs [design] and

place a PictureBox element from our toolbox into it, we can set it to any size we want.

A pictureBox element in our form

Now, let's make the building blocks of our drawing: a setup and a draw function.

namespace MyNamespace
{
    public partial class Form1 : Form
    {
        public Form1()
        {

        }

        void setup()
        {

        }

        void draw()
        {

        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Then, we need to make an instance of the SimpleDraw class we installed, for that we can do this:

SimpleDraw s = new SimpleDraw();

It's helpful to keep the name short as we will write it every time we want to draw things.

With that over, we can actually start working with our drawing.

In our Form1 method, we need to call s.start(setup, draw, myCanvas); to start the drawing process.

Also we can call in setup():

s.toggleAntiAlias();
s.frameRate(15);
Enter fullscreen mode Exit fullscreen mode

Since we want to draw a fractal tree, we need a recursive function like "drawTree(parameters)" to draw it, so our draw() method code might look something like this:

s.background(Color.White);
drawTree(parameters);
Enter fullscreen mode Exit fullscreen mode

So now we have to get to the meat & gravy of our program: the recursive function.

First, we need to know what we need to pass into our function. Well, we need an starting x and y position, as well as a size and a current angle.

So our parameters might look something like this:

drawTree(double x, double y, double currentSize, double currentAngle);

The first thing that we can do in our function is a base case for stopping. Since we want the size of our lines to get smaller with each call, we can do something like:

if (size < 4)
{
     return;
}
Enter fullscreen mode Exit fullscreen mode

Next, we need to draw the line that we want.
To do that, we need the coordinates of two points. The first one we already know: it's the x,y values passed in as parameters.
The next one we can use a bit of trig. to figure out.

double newX = x + currentSize * Math.Cos(currentAngle);
double newY = y - currentSize * Math.Sin(currentAngle); // Notice that it's minus because positive y is actually down since we start at top-left

Finally, we can draw our line:

s.line((int)x, (int)y, (int)newX, (int)newY);

And to top off the recursion, we can call the function again two times with new parameters:

drawTree(newX, newY, currentSize / 2, currentAngle + Math.PI / 6);
drawTree(newX, newY, currentSize / 2, currentAngle - Math.PI / 6);
Enter fullscreen mode Exit fullscreen mode

Then, all we need to do is call the function for the first time in our draw method. Since we want it to start at mid-bottom we can pass in these argts:

drawTree(s.width / 2, s.height, 200, Math.PI / 2, true); // Since we want to draw our first line vertically, we need the angle to be PI/2

And boom, we've got a fractal tree. You can play around with the parameters a bit to see how it changes the patterns.

A fractal tree pattern

Extra:
For our next step, we are going to make the patterns a bit more customizable.

The parameters that we can change are: angle, size, number of branches, minimum size and resizing factor.
So, we can make some trackbars (sliders) in our form to control these (in this image I also changed zoom and did some rough tranlating when clicking the image, but that's not necessary).

Some sliders for customization

For the size, angle and resizing factor, all we need to do are some variables for them, replace them in the code and update them in draw, and the drawTree()method will update accordingly.

However, for the number of branches, we need to be a bit more careful with how we approach it.

The way we can do it is by dividing the angle we want in equal parts depending on how many branches there are, then start adding them up from the minimal angle.

The total angle is (2 * angle) and the number of parts we need to divide it to is (numberOfBranches - 1), that's our deltaAngle.

The minimum angle is currentAngle - angle.

    double deltaAngle = (2 * angle) / (numberOfBranches - 1);
    double minAngle = currentAngle - angle;
Enter fullscreen mode Exit fullscreen mode

Then, we can use a for loop for calling the functions:

for (int i = 0; i < numberOfBraches; i++)
{
    drawTree(newX, newY, size * resizingFactor, minAngle + i * deltaAngle);
}
Enter fullscreen mode Exit fullscreen mode

We can also do extra stuff for translation and zooming, so that we can navigate in our image a bit, but that's not necessary, also you can play around with the colors to make it even more stunning.

Here's the final code:

using SimpleDrawProject;

namespace FractalTree
{
    public partial class Form1 : Form
    {
        SimpleDraw s = new SimpleDraw();
        public Form1()
        {
            InitializeComponent();
            s.start(setup, draw, myCanvas);
        }

        void draw()
        {
            s.translate(currentTranslate.X,currentTranslate.Y);
            s.zoom(zoomBar.Value * 0.1);
            getData();
            s.background(Color.White);
            drawTree(s.width / 2, s.height, size, Math.PI / 2);
            s.text("angle = " + (angle*360/(Math.PI * 2)).ToString(), 0, 0);
            s.text("size = " + size.ToString(), 0, 15);
            s.text("n branches = " + numberOfBranches.ToString(), 0, 30);
            s.text("resize factor = " + resizeFactor.ToString(), 0, 45);
        }

        void getData()
        {
            numberOfBranches = branchBar.Value;
            size = (int)(sizeBar.Value * 2);
            angle = (angleBar.Value * 2 * Math.PI) / 360;
            minSize = minSizeBar.Value;
            resizeFactor = (double)resizeFactorBar.Value * 0.1;
        }

        int numberOfBranches = 2;
        int size = 200;
        double angle = Math.PI / 2;
        int minSize = 4;
        double resizeFactor = 0.5;

        Point currentTranslate = new Point(0,0);
        void drawTree(double x, double y, double currentSize, double currentAngle)
        {
            if (currentSize < minSize)
            {
                return;
            }

            double newX, newY;

            newX = x + currentSize * Math.Cos(currentAngle);
            newY = y - currentSize * Math.Sin(currentAngle);


            s.line((int)x, (int)y, (int)newX, (int)newY);

            double deltaAngle = (2 * angle) / (numberOfBranches - 1);
            double minAngle = currentAngle - angle;

            for (int i = 0; i < numberOfBranches; i++)
            {
                drawTree(newX, newY, currentSize * resizeFactor, minAngle + i * deltaAngle);
            }

        }

        void setup()
        {
            s.frameRate(15);
            s.toggleAntiAlias();
        }

        private void myCanvas_Click(object sender, EventArgs e)
        {
            Point pos = s.mousePos(this);
            currentTranslate.X += (int)((pos.X - s.width / 2) * 0.1);
            currentTranslate.Y += (int)((pos.Y - s.height / 2) * 0.1);
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Top comments (1)

Collapse
 
thesnowmanndev profile image
Kyle Martin

This is interesting, I am going to bookmark this and give this a shot at another date. Thank you for sharing your idea!