Title: Draw a filled spiral in C#
My post Draw an Archimedes spiral in C# explained how to draw multiple Archimedes spirals starting at a common center point. This post shows how you can fill the spaces between the spirals with colors.
The basic idea is to make a polygon that follows the points along one spiral and then comes back along the reversed points from an adjacent spiral. That much would be interesting enough, but there are three details involving alignment, ending distance, and filling that make this extra tricky.
Alignment
In the previous example, each spiral expands until it its radius was larger than the distance from the spiral's center to the farthest corner of the drawing area. Because the spirals use different offsets, they span different angles. That means the end of one spiral may be far from the beginning of the next. The picture on the right shows the result. It's interesting, but not what we're after.
One solution is to calculate the largest angle max_theta that any spiral must reach to surround the drawing area. Then you can generate points on each spiral until the variable theta plus the spiral's offset exceeds max_theta.
The equation for an Archimedes spiral is r = A * θ, so solving for θ gives θ = r / A. If we plug in the distance to the farthest corner of the drawing area, we get max_theta = max_distance / A.
Now if each spiral ends when its theta + offset values exceeds max_theta, then the spiral ends will be near each other so they should form usable polygons. That leads to the second problem.
Ending Distance
In the earlier example, a spiral expanded until its radius was larger than the distance from the spiral's center to the farthest corner of the drawing area. The picture on the right shows what happens if the spirals are only extended that far. This problem actually has two features.
First, some of the spirals don't go far enough. If you look closely at the picture, you can probably guess that purple band should extend and cut into the drawing rectangle again in its lower left corner. The problem is that this spiral starts pointing upward, so doesn't go as far around the circle as the red band, which starts pointing to the right.
A second potential problem is that the filled areas must include the area outside of the outermost spiral. If not only need to extend the second spiral to define the outer
edge of the purple band, we also need to extend the second and possibly third spirals to define the outer edges of the other bands.
We can do all of that quite easily by adding 2 π to max_theta to make every spiral take another lap around the center.
Filling
The final problem deals with the way the bands are filled. The obvious approach is to fill the area between spiral i and spiral i + 1. You can use the modulus operator (%) to wrap around to the beginning when you reach the last spiral. Unfortunately, even though the spirals all start at the same center, the first spiral isn't adjacent to the last one. If you fill the area between those two spirals, you get the picture on the right.
The example solves this problem by simply creating one extra spiral outside all of the others. For example, if you want to draw three spirals, then the program creates four. Then, instead of filling the area between the first and last spirals, it fills the area between spiral number three and spiral number four.
The Program
This example uses the GetSpiralPoints method to generate the points on a spiral. That method is similar to the one used by the previous example. The only difference is that the previous version expands the spiral until r is big enough to cover the drawing rectangle, while the new version expands the spiral until theta reaches max_theta. See the previous example for a description of that method.
This example's other main piece of code is the following Paint event handler.
// Draw the spiral(s).
private void picSpiral_Paint(object sender, PaintEventArgs e)
{
e.Graphics.Clear(picSpiral.BackColor);
e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
try
{
float A = float.Parse(txtA.Text);
int num_spirals = int.Parse(txtNumSpirals.Text);
// Angular spacing between different spirals.
float d_start = (float)(2 * Math.PI / num_spirals);
// The angle where the next spiral starts.
float start_angle = 0;
// Center point.
PointF center = new PointF(
picSpiral.ClientSize.Width / 2,
picSpiral.ClientSize.Height / 2);
// Draw axes.
e.Graphics.DrawLine(Pens.Black,
center.X, 0,
center.X, picSpiral.ClientSize.Height);
e.Graphics.DrawLine(Pens.Black,
0, center.Y,
picSpiral.ClientSize.Width, center.Y);
// Draw the spiral on only part of the PictureBox.
Rectangle rect = new Rectangle(25, 50, 150, 150);
// Find the maximum distance to the rectangle's corners.
float max_dist = Distance(center, rect);
// Calculate the maximum theta value that we need to go to.
float max_theta = max_dist / A + 2 * (float)Math.PI;
// Get points defining the spirals.
List<List<PointF>> spiral_points = new List<List<PointF>>();
// Get the spirals' points.
for (int i = 0; i < num_spirals + 1; i++)
{
spiral_points.Add(GetSpiralPoints(
center, A, start_angle, max_theta));
start_angle += d_start;
}
// Fill the areas between the spirals.
for (int i = 0; i <= num_spirals; i++)
{
// Make a list holding the next spiral's points.
List<PointF> points = new List<PointF>(spiral_points[i]);
// Add the following spiral's points reversed.
List<PointF> points2 =
new List<PointF>(spiral_points[i + 1]);
points2.Reverse();
points.AddRange(points2);
e.Graphics.FillPolygon(
SpiralBrushes[i % SpiralBrushes.Length],
points.ToArray());
// Optional: Outline the spiral's polygon.
using (Pen pen = new Pen(Color.Black, 1))
{
e.Graphics.DrawLines(pen, points.ToArray());
}
}
// Draw the target rectangle.
e.Graphics.DrawRectangle(Pens.Black, rect);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
This method reads the parameters that you entered and calculates the angular offsets between the spirals as in the previous example. It also finds the drawing area's center point, draws axes, and creates a drawing rectangle as before.
The program then finds the maximum distance from the spiral's center to the corners of the drawing area. Next, it plugs r = max_dist in the equation r = a * theta and solves for theta. That gives it the largest value of theta that it needs to use. It saves that value in max_theta.
Next, the code creates a list of lists to hold the points for the spirals. It then enters a loop where it calls GetSpiralPoints to find each spiral's points and it adds them to the list. Notice that in this loop i runs from 0 to num_spirals so it creates num_spirals + 1 spirals.
After it has all of the spirals' points, the method uses a loop to fill the spaces between them as. As described earlier, it fills the areas between adjacent spirals up to the extra spiral that it created at the end of the others.
To fill an area, the code makes a new points list initialized with the points for spiral number i. It then creates another list points2 holding the next spiral's points, reverses that list, and add it to the end of the points list. The code then fills that area and optionally outlines it.
The program includes a few other details that are relatively straightforward. Download the example program to see those details.
Download the example to experiment with it and to see additional details.
|