Click here to Skip to main content
15,881,882 members
Articles / Programming Languages / C#
Tip/Trick

Hypotrochoid / Spirograph

Rate me:
Please Sign up or sign in to vote.
4.00/5 (1 vote)
21 Sep 2013CPOL1 min read 14K   288   6  
A simple program to draw hypotrochoids with C#

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:

C#
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:

C#
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.

C#
private void DrawHypotrochoid(PaintEventArgs e)
{
Graphics g = e.Graphics;

if ( dataGridViewParms.RowCount > 0 )
	{
	int i;
	int max;
    // current angle we are calculating with,
    // goes all the way around one circle
	double angle;
	double step;
    // how big do we step around the circle.
    // smaller steps make a smoother drawing
	double twoPI;
	double rx, ry; // where the next point is, floating point
	double scale;  // arbitrary scaling factor chosen by user
    // cx and cy are used to move the drawing to the center of the panel
	int x, y, cx, cy;
	int fromX=0, fromY=0;  // where we drew the previous point
    // save the first point so we can neatly connect the
    // last segment of the curve up.
	int finalX = 0, finalY = 0;
    // when calculating the first point we don't draw it, we just save it.
	Boolean firstLine = true;
	Pen pen=new Pen(mainColour);
    // to hold the parms from the grid
	double[] radius = new double[dataGridViewParms.RowCount];
    // don't want to convert them in an inner loop!
	double[] speed = new double[dataGridViewParms.RowCount];
	double[] phase = new double[dataGridViewParms.RowCount];

	// clear the panel
	g.FillRectangle(Brushes.White, 0, 0, panelDraw.Width, panelDraw.Height);
	Cursor.Current = Cursors.WaitCursor;
	// get the parms from the grid into doubles
	max = dataGridViewParms.RowCount - 1; // don't process the blank row at the end of the grid
	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;
    // twoPI radians = 360 degrees = one circle
	step = twoPI / double.Parse(textBoxSteps.Text);
	cx = panelDraw.Width / 2;
	cy = panelDraw.Height / 2;
	// loop around one full circle
	for (angle = 0.0; angle < twoPI; angle += step)
		{
		rx = 0.0;
		ry = 0.0;
		// each line in the grid is like adding a wheel to a spirograph toy,
        // we just add up where each "wheel" move the point
		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]));
			}

		// convert our double to int int that fits nicely on our panel
		rx *= scale;
		ry *= scale;
		x = (int)rx + cx;
		y = (int)ry + cy;
		if (firstLine)
			{
			// save this point
			finalX=fromX = x;
			finalY=fromY = y;
			firstLine = false;
			}
		else
			{
			// draw from the last point to the new point
			g.DrawLine(pen, fromX, fromY, x, y);
			fromX = x;
			fromY = y;
			}
		}

	// and draw the very last connecting segment
	g.DrawLine(pen, fromX, fromY, finalX, finalY);

	Cursor.Current = Cursors.Default;
	}
}

History

  • Initial version

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior)
Canada Canada
Professional Programmer living in Beautiful Vancouver, BC, Canada.

Comments and Discussions

 
-- There are no messages in this forum --