Title: Draw a Sierpinski pentagon in C#
In a Sierpinski pentagon, larger pentagons are recursively divided into five smaller pentagons with a sixth uncolored pentagon in the center. The following picture shows the first four levels of the resulting fractal.
For this example, I decided to think about a pentagon as centered on a point and with a particular radius. The following method finds the corners of a pentagon centered at a particular point and with a given radius.
// Find the pentagon's corners.
private PointF[] GetPentagonPoints(PointF center, float radius)
{
PointF[] points = new PointF[5];
double theta = -Math.PI / 2.0;
double dtheta = 2.0 * Math.PI / 5.0;
for (int i = 0; i < 5; i++)
{
points[i] = new PointF(
center.X + (float)(radius * Math.Cos(theta)),
center.Y + (float)(radius * Math.Sin(theta)));
theta += dtheta;
}
return points;
}
This method simply loops variable theta through the angles that determine the pentagon's points. It uses sines and cosines to find the points' positions, adding an offset so the shape is centered over the center point.
The only trick here is that theta starts at -90° so the pentagon's first point is on the top of the shape.
Drawing the Sierpinski pentagon is actually fairly easy in principle. To subdivide a pentagon, you simply scale the larger pentagon to make a smaller copy and then position it correctly. The following sections explain how to calculate the scale factor and the smaller pentagons' positions.
Scale Factor
To calculate the scale for the smaller pentagons, look at the picture below.
Let S be the side length of the big pentagon and let s be the side length of the smaller pentagons.
Next, look at the smaller pentagon's inner triangle shown in blue. The five inner triangles' central angles add up to 360, so each of those inner angles must be 360 / 5 = 72°.
Because the angles in any triangle must add up to 180°, and because the two other angles in the inner triangle are the same, those angles are (180 - 72) / 2 = 54°.
If you look at the picture, you can see that two instances of that angle 54° plus the pentagon's outer angle are colinear, so they must add up to 180°. That means the outer angle is 180 - 2 * 54 = 180 - 108 = 72° as shown on the picture.
The little horizontal space occupied below the left pentagon is s * cos(72) wide. That means the total width of the big pentagon is the following.
S = 2 * (s + s * cos(72)) = 2 * s * (1 + cos(72))
Now we can solve for s in terms of S to get the following.
s = S / (2 * (1 + cos(72)))
That means when we move from a bigger pentagon to a smaller one, we need to scale the pentagon by a factor of:
scale = 1 / (2 * (1 + cos(72)))
Radius Change
We still need to figure out where to put the smaller pentagons. Because I'm treating them as centered over a point, we need to figure out where the centers of the new pentagons should be.
To make that calculation, consider the following picture.
Suppose the radius of the larger pentagon is R. Then the radius of the smaller pentagon is r = R * scale. That means the distance d from the center of the original pentagon to the center of the smaller pentagons is d = R - r.
Now we can use the GetPentagonPoints method to find the centers for the smaller pentagons.
Drawing the Pentagons
The program uses the following code to draw Sierpinski pentagons.
// Scale factor for moving to smaller pentagons.
private float size_scale =
(float)(1.0 / (2.0 * (1 + Math.Cos(Math.PI/180*72))));
// Draw a sierpinski pentagon.
private void DrawPentagon(int depth,
Graphics gr, Brush brush,
PointF center, float radius)
{
// If we are done recursing, draw the pentagon.
if (depth <= 0)
{
// Find the pentagon's corners.
PointF[] points = GetPentagonPoints(center, radius);
gr.FillPolygon(brush, points);
}
else
{
// Find the smaller pentagons' centers.
float d = radius - radius * size_scale;
PointF[] centers = GetPentagonPoints(center, d);
// Recursively draw the smaller pentagons.
foreach (PointF point in centers)
{
DrawPentagon(depth - 1, gr, brush, point,
radius * size_scale);
}
}
}
The code first uses the formula shown earlier to define the size scale factor.
The DrawPentagon method actually draws a pentagon. The method first checks its depth of recursion. If depth is less than or equal to zero, then the recursion is finished. In that case, the method uses GetPentagonPoints to get the current pentagon's vertices and then uses the Graphics object's FillPolygon method to draw the pentagon.
If depth is greater than zero, the method must recursively draw smaller pentagons. It calculates the distance d from the original pentagon's center to the centers of the smaller pentagons. It then calls GetPentagonPoints to find the centers of the smaller pentagons. The code loops through those points and calls the DrawPentagon method recursively to draw the smaller pentagons centered at those points.
The following code shows how the program redraws the Sierpinski pentagon when you change the depth or resize the form.
// Draw.
private void picPentagon_Paint(object sender, PaintEventArgs e)
{
int depth = (int)nudDepth.Value;
PointF center = new PointF(
picPentagon.ClientSize.Width / 2,
picPentagon.ClientSize.Height / 2);
float radius = (float)Math.Min(center.X, center.Y);
DrawPentagon(depth, e.Graphics, Brushes.Red, center, radius);
}
This code gets the depth that you selected in the form's NumericUpDown control. It finds the PictureBox control's center, and uses the smaller of the center point's coordinates as the pentagon's radius.
The code then calls the DrawPentagon method passing it the center point and radius.
Download the example to experiment with it and to see additional details.
|