Title: Draw a Fibonacci word fractal in C#
This example uses the Fibonacci word to draw a fractal curve. For information on the Fibonacci word, see the post Calculate Fibonacci words in C#.
To draw a curve with level N, use Fibonacci word number N. Loop through the word's digits and apply the following rules to digit number K.
- If the digit is 0, draw a segment in the current direction.
- If the digit is 1, draw a segment after turning 90° in the following direction.
- If K is even, turn 90° to the right.
- If K is odd, turn 90° to the left.
To get the same curve shown by the Fibonacci word fractal page at Wikipedia, you need to modify the definition of the word slightly. Instead of starting with S0 = 0 and S1 = 01, you need to start with the following.
S0 = 0
S1 = 1
The result is almost the same sequence of digits.
It's strange that Wikipedia has different definitions of the Fibonacci word in its posts for the word and the fractal. Instead of trying to make the two consistent in my posts, I have made them match the versions used by Wikipedia.
The following code shows how the main program handles drawing the fractal.
// Directions. Oriented so adding 1 turns right.
private enum Directions
{
Up,
Right,
Down,
Left,
};
private void btnGo_Click(object sender, EventArgs e)
{
// Get the desired length.
int term_num = int.Parse(txtNumTerms.Text);
if (term_num > 30)
{
if (MessageBox.Show(
"This may take a long time. Continue anyway?",
"Continue?", MessageBoxButtons.YesNo)
== DialogResult.No)
return;
}
// Get the Fibonacci word.
string word = FiboWord(term_num);
char[] chars = word.ToCharArray();
// Calculate the points.
Point[] points = new Point[word.Length + 1];
points[0] = new Point(0, 0);
Directions dir = Directions.Up;
for (int i = 0; i < word.Length; i++)
{
AddPoint(chars, points, i, ref dir);
}
Console.WriteLine("# Chars: " + chars.Length);
Console.WriteLine("# Points: " + points.Length);
// Find the points' bounds.
Rectangle rect = GetBounds(points);
// Draw.
int wid = picDrawing.ClientSize.Width;
int hgt = picDrawing.ClientSize.Height;
Bitmap bm = new Bitmap(wid, hgt);
using (Graphics gr = Graphics.FromImage(bm))
{
// Scale to fit.
RectangleF target_rect =
new RectangleF(5, 5, wid - 10, hgt - 10);
MapDrawing(gr, rect, target_rect, false);
// Draw.
gr.DrawLines(Pens.Black, points);
}
// Display the result.
picDrawing.Image = bm;
}
The code first makes you verify if you want to draw a curve with depth greater than 30 because it may take a long time. It then uses the method described in the previous post to get the appropriate Fibonacci number (modified to produce the correct version).
The code then loops through the word's digits and calls AddPoint for each to add an appropriate point to the points list.
Next the code calls the GetBounds method to get the points' bounding rectangle. It then creates a Bitmap to fit the program's PictureBox. It calls the MapDrawing method to scale the points to fit in the Bitmap and draws a curve connecting the points.
The following code shows the AddPoint method.
// Add the next point in the indicated direction.
private void AddPoint(char[] chars, Point[] points,
int i, ref Directions dir)
{
// If the character is odd, turn 90 degrees.
if (chars[i] == '1')
{
if (i % 2 == 0)
dir = (Directions)(((int)dir + 1) % 4);
else
dir = (Directions)(((int)dir + 3) % 4);
}
// Find the next point.
const int dist = 5;
int x = points[i].X;
int y = points[i].Y;
if (dir == Directions.Up) y -= dist;
else if (dir == Directions.Down) y += dist;
else if (dir == Directions.Left) x -= dist;
else if (dir == Directions.Right) x += dist;
points[i + 1] = new Point(x, y);
}
This method simply implements the odd-even drawing rules described earlier.
The following code shows the GetBounds method.
// Find the bounds of the points.
private Rectangle GetBounds(Point[] points)
{
int xmin = points[0].X;
int xmax = xmin;
int ymin = points[0].Y;
int ymax = ymin;
foreach (Point point in points)
{
if (point.X < xmin) xmin = point.X;
if (point.X > xmax) xmax = point.X;
if (point.Y < ymin) ymin = point.Y;
if (point.Y > ymax) ymax = point.Y;
}
return new Rectangle(xmin, ymin, xmax - xmin, ymax - ymin);
}
This method loops through the points and updates minimum and maximum X and Y values. When it finishes, it returns a Rectangle defining the points' bounds.
Finally, the following code shows the MapDrawing method.
// Map a drawing coordinate rectangle to
// a graphics object rectangle.
private void MapDrawing(Graphics gr, RectangleF drawing_rect,
RectangleF target_rect, bool stretch)
{
gr.ResetTransform();
// Center the drawing area at the origin.
float drawing_cx = (drawing_rect.Left + drawing_rect.Right) / 2;
float drawing_cy = (drawing_rect.Top + drawing_rect.Bottom) / 2;
gr.TranslateTransform(-drawing_cx, -drawing_cy);
// Scale.
// Get scale factors for both directions.
float scale_x = target_rect.Width / drawing_rect.Width;
float scale_y = target_rect.Height / drawing_rect.Height;
if (!stretch)
{
// To preserve the aspect ratio,
// use the smaller scale factor.
scale_x = Math.Min(scale_x, scale_y);
scale_y = scale_x;
}
gr.ScaleTransform(scale_x, scale_y, MatrixOrder.Append);
// Translate to center over the drawing area.
float graphics_cx = (target_rect.Left + target_rect.Right) / 2;
float graphics_cy = (target_rect.Top + target_rect.Bottom) / 2;
gr.TranslateTransform(graphics_cx, graphics_cy, MatrixOrder.Append);
}
This method creates three transformations to map a drawing rectangle onto a target rectangle. It first translates to center the drawing rectangle at the origin.
Next, the code calculates the scale factors it would need to use to stretch the drawing rectangle to fit the target rectangle. If the stretch parameter is not false, the code uses the smaller of the two scale factors so the result isn't distorted out of shape. The code uses the scale factors to make a scale transformation to resize the drawing.
The method then translates the result so it is centered over the target rectangle.
Download the example to experiment with it and to see additional details.
|