Title: Draw a recursive snowflake fractal in C#
This program draws a recursive snowflake fractal by using an initiator and a generator to define the fractal's shape.
The initiator is a curve that represent the fractal's basic shape. In this case, the initiator is the triangle shown in the second picture above where the depth of recursion is zero.
The generator is a curve that shows how to break up a line segment to go to the next level of recursion. In this example, the generator is shown across the top of the third picture above. To draw a line segment, the program draws 1/3 of the distance along the segment, turns -60 degrees, draws 1/3 of the original distance, turns 120 degrees, draws 1/3 of the original distance, turns -60 degrees, and finishes by drawing another 1/3 of the original distance. The result is a series of line segments that start and end at the same points as the original line segment (which would have been drawn directly for a smaller depth of recursion) but that includes a copy of the generator. To go to greater depths of recursion, all of the line segments just drawn would be replaced by smaller copies of the generator.
You can make a less complicated snowflake generator that simply turns as required. By using a separate initiator and generator, this program is more general and can draw other fractals, which I'll show in later posts.
The following code shows how the program declares its initiator and generator variables. Look at the code to see how these are initialized.
// Coordinates of the points in the initiator.
private List<PointF> Initiator;
// Angles and distances for the generator.
private float ScaleFactor;
private List<float> GeneratorDTheta;
The following code shows the DrawSnowflake method that starts drawing the snowflake fractal.
// Draw the complete snowflake.
private void DrawSnowflake(Graphics gr, int depth)
{
gr.Clear(picCanvas.BackColor);
// Draw the snowflake.
for (int i = 1; i < Initiator.Count; i++)
{
PointF p1 = Initiator[i - 1];
PointF p2 = Initiator[i];
float dx = p2.X - p1.X;
float dy = p2.Y - p1.Y;
float length = (float)Math.Sqrt(dx * dx + dy * dy);
float theta = (float)Math.Atan2(dy, dx);
DrawSnowflakeEdge(gr, depth, ref p1, theta, length);
}
}
This code loops through the points in the initiator and calls DrawSnowflakeEdge to draw each of the edges. Note that in this example to make a closed curve the first initiator point should be repeated as the last point.
The following code shows the recursive DrawSnowflakeEdge method.
// Recursively draw a snowflake edge starting at
// (x1, y1) in direction theta and distance dist.
// Leave the coordinates of the endpoint in
// (x1, y1).
private void DrawSnowflakeEdge(Graphics gr, int depth,
ref PointF p1, float theta, float dist)
{
if (depth == 0)
{
PointF p2 = new PointF(
(float)(p1.X + dist * Math.Cos(theta)),
(float)(p1.Y + dist * Math.Sin(theta)));
gr.DrawLine(Pens.Blue, p1, p2);
p1 = p2;
return;
}
// Recursively draw the edge.
dist *= ScaleFactor;
for (int i = 0; i < GeneratorDTheta.Count; i++)
{
theta += GeneratorDTheta[i];
DrawSnowflakeEdge(gr, depth - 1, ref p1, theta, dist);
}
}
If the depth of recursion is 0, the code simply draws a line segment starting at the start point with the indicated direction and distance.
For higher depths of recursion, the method multiples the length that it should draw by the scale factor (in this example 1/3) and then loops through the angles in the generator. For each angle, DrawSnowflakeEdge recursively calls itself to draw a segment in the new direction with the new length.
Download the example to experiment with it and to see additional details.
|