Introduction
This simple program draws hypotrochoids, the patterns the toy spirograph makes.
As we all know, to draw a circle using a radius and an angle, we use the formula:
x=radius * cos(angle);
y=radius * sin(angle);
So if we draw points at x,y
as we change the angle from 0
to 2PI
, we will draw a circle. This is equivalent pinning a string
to a point, tying a pencil to the far end then carefully moving the pen in a circle with the string
taut. Or instead of string
, we could pin something solid to the table. What if we pinned a second rod to the end of the first, stuck a pen through a hole in the 2nd rod then moved the 1st circle at one speed when we moved the second circle faster, all the while keeping pen on paper. You would be adding two rotating circles together. The math is:
x=R1 * cos(speed1*A1) + R2 * cos(speed2*A2);
y=R1 * sin(speed1*A1) + R2 * sin(speed2*A2);
To add a third circle, just add a third term. Next, we don't always want to neatly line up all the rods in a row straight up. To let each rod start at any angle, add a phase to the angle:
x=R1 * cos(speed1*A1 + phase1);
y=R1 * sin(speed1*A1 + phase1);
This little program lets you enter as many wheels as you want in a grid, and draw the pattern.
Points of Interest
Here is the main drawing loop. It draws short line segments from each calculated point. To make sure the ends connect, it saves the location of the first point calculated and joins it after the main loop is completed.
private void DrawHypotrochoid(PaintEventArgs e)
{
Graphics g = e.Graphics;
if ( dataGridViewParms.RowCount > 0 )
{
int i;
int max;
double angle;
double step;
double twoPI;
double rx, ry; double scale; int x, y, cx, cy;
int fromX=0, fromY=0; int finalX = 0, finalY = 0;
Boolean firstLine = true;
Pen pen=new Pen(mainColour);
double[] radius = new double[dataGridViewParms.RowCount];
double[] speed = new double[dataGridViewParms.RowCount];
double[] phase = new double[dataGridViewParms.RowCount];
g.FillRectangle(Brushes.White, 0, 0, panelDraw.Width, panelDraw.Height);
Cursor.Current = Cursors.WaitCursor;
max = dataGridViewParms.RowCount - 1; for ( i=0; i < max; i++ )
{
radius[i]=double.Parse(dataGridViewParms.Rows[i].Cells[0].Value as string);
speed[i] = double.Parse(dataGridViewParms.Rows[i].Cells[1].Value as string);
phase[i] = double.Parse(dataGridViewParms.Rows[i].Cells[2].Value as string);
}
pen.Width = int.Parse(textBoxLineWidth.Text);
scale = double.Parse(textBoxScale.Text);
twoPI=Math.PI * 2.0;
step = twoPI / double.Parse(textBoxSteps.Text);
cx = panelDraw.Width / 2;
cy = panelDraw.Height / 2;
for (angle = 0.0; angle < twoPI; angle += step)
{
rx = 0.0;
ry = 0.0;
for (i = 0; i < max; i++)
{
rx += radius[i] * Math.Cos(speed[i] * (angle + phase[i]));
ry += radius[i] * Math.Sin(speed[i] * (angle + phase[i]));
}
rx *= scale;
ry *= scale;
x = (int)rx + cx;
y = (int)ry + cy;
if (firstLine)
{
finalX=fromX = x;
finalY=fromY = y;
firstLine = false;
}
else
{
g.DrawLine(pen, fromX, fromY, x, y);
fromX = x;
fromY = y;
}
}
g.DrawLine(pen, fromX, fromY, finalX, finalY);
Cursor.Current = Cursors.Default;
}
}
History