Title: Draw a curly tree fractal in C#
This example uses recursion to build a fractal tree. It starts by drawing the tree's trunk. At the end of the trunk, it creates two branches. At the end of each branch, it creates two new branches A and B. It continues recursively creating two new branches at the ends of each branch until it has reached a maximum depth of recursion.
The parameters that you enter are:
- Level: The depth of recursion.
- Angle A: The angle that branch A turns with respect to its parent branch.
- Length Scale A: During each recursive call, the program reduces the length of the A branch by this factor. For example, if a branch X has length 100 and this value is 0.9, then the A branch from the end of X has length 100 * 0.9 = 90.
- Angle B: The angle that branch B turns with respect to its parent branch.
- Length Scale B: During each recursive call, the program reduces the length of the B branch by this factor.
The following MakeTree method draws the tree.
// Make the tree. The angles are in radians.
private Bitmap MakeTree(int width, int height, int level,
float angleA, float angleB,
float length_scaleA, float length_scaleB)
{
// Make the line segments.
List<PointF> start_points = new List<PointF>();
List<PointF> end_points = new List<PointF>();
float start_length = picCanvas.ClientSize.Height * 0.33f;
const float start_thickness = 10;
const float start_direction = (float)(Math.PI * 0.5);
FindTreePoints(start_points, end_points,
0, 0, start_direction, level, start_length,
angleA, angleB, length_scaleA, length_scaleB);
// Find the tree's bounds.
float xmin = start_points[0].X;
float xmax = xmin;
float ymin = start_points[0].Y;
float ymax = ymin;
foreach (PointF point in end_points)
{
if (xmin > point.X) xmin = point.X;
if (xmax < point.X) xmax = point.X;
if (ymin > point.Y) ymin = point.Y;
if (ymax < point.Y) ymax = point.Y;
}
// Make the bitmap.
Bitmap bm = new Bitmap(
picCanvas.ClientSize.Width,
picCanvas.ClientSize.Height);
using (Graphics gr = Graphics.FromImage(bm))
{
gr.Clear(picCanvas.BackColor);
gr.SmoothingMode = SmoothingMode.AntiAlias;
// Map the tree onto the PictureBox.
xmin -= start_thickness;
xmax += start_thickness;
ymin -= start_thickness;
ymax += start_thickness;
RectangleF world_rect = new RectangleF(
xmin, ymin, xmax - xmin, ymax - ymin);
const int margin = 4;
RectangleF device_rect = new RectangleF(
margin, margin,
picCanvas.ClientSize.Width - 2 * margin,
picCanvas.ClientSize.Height - 2 * margin);
SetTransformationWithoutDisortion(gr,
world_rect, device_rect, false, true);
// Draw the tree.
using (Pen pen = new Pen(Color.Blue, 1))
{
for (int i = 0; i < start_points.Count; i++)
{
pen.Width = Distance(
start_points[i], end_points[i]) / 10f;
gr.DrawLine(pen, start_points[i], end_points[i]);
}
}
}
return bm;
}
The method first creates the points that define the tree's branches. It creates the start_points and end_points lists to keep track of the branches' start and end points.
It calculates a reasonable starting length and thickness for the tree's trunk, and it sets the starting angle to be π / 2 so the trunk points upward.
The code then calls the FindTreePoints method described shortly to generate the tree's points.
Next the method finds the tree's coordinate bounds. It sets variables xmin, xmax, ymin, and ymax to the first point in the start_points list, which is the bottom of the tree's trunk. (Its root, if you will.) It then loops through the end_points list and updates the minimum and maximum X and Y coordinate values as needed.
Note that the program doesn't need to loop through the start_points list because every point in that list except for its first point is also in the end_points list. That's true because every branch's start point is the end point of the branch's parent branch. That's true for every branch except the trunk, which has no parent branch. That point is considered first when the code initializes the xmin, xmax, ymin, and ymax variables.
The end_points list also contains the leaves, branch end points that are not also start points. The leaves are contained in the end_points list but not in the start_points list.
So by considering the trunk's root and the points in the end_points list, the code has considered every point in the tree.
Next the code creates a Bitmap to hold the drawn tree. After some setup, it expands the coordinate bounds a bit to add a margin around the tree. It makes RectangleF structures to represent the area that the tree occupies and the area on the PictureBox where it should be drawn. It then calls SetTransformationWithoutDisortion to map the drawing area onto the PictureBox area. See the post Map drawing coordinates without distortion in C# for information on that method.
That mapping was the reason why the program generated the tree's branches first without drawing them. Depending on the tree's parameters, it can be hard to figure out exactly where the finished tree will be drawn. Rather than trying to do that, the program just generates the points and finds their bounds. It then creates the mapping to make the tree fit the PictureBox nicely.
The method finishes by drawing the tree and returning the resulting bitmap.
The following code shows the recursive FindTreePoints method.
// Generate segments for the tree.
// The direction parameter is in radians.
private void FindTreePoints(
List<PointF> start_points, List<PointF> end_points,
float x, float y, float direction, int level,
float length, float angleA, float angleB,
float length_scaleA, float length_scaleB)
{
// Find the new segment.
if (length < 0.1) return;
start_points.Add(new PointF(x, y));
x += (float)(length * Math.Cos(direction));
y += (float)(length * Math.Sin(direction));
end_points.Add(new PointF(x, y));
if (level > 0)
{
FindTreePoints(start_points, end_points,
x, y, direction + angleA, level - 1,
length * length_scaleA, angleA, angleB,
length_scaleA, length_scaleB);
FindTreePoints(start_points, end_points,
x, y, direction + angleB, level - 1,
length * length_scaleB, angleA, angleB,
length_scaleA, length_scaleB);
}
}
This remarkably simple method first checks the length of the current branch and returns if the length is less than 0.1. Branches that small can make the program do a lot of work to just fill in a single pixel.
Next the method calculates the new branch's end point and adds the start and end points to the start_points and end_points lists.
If the level is greater than zero, the method has not reached the desired depth of recursion so the method calls itself recursively to draw smaller branches. It adds the A and B branch angles to its correct direction and it shortens the new branches.
By changing the drawing parameters, you can generate a nice variety of trees such as the following.
Download the example to experiment with it and to see additional details.
|