/* spirograph.c 1.2 - Jon McCormack, August 2007 */ /* 1.0 - Jon McCormack, March 2005 */ /* Program to to create spirograph patterns. * Spirograph shapes are hypotrochoids generated by a fixed point on a circle * rolling inside another fixed circle. * * The spirograph has two constants p and q representing the radii of the two * circles. To simplify things the outer circle is drawn with radius 1 and * the rotating circle radius p/q. For details on the equations used see: * http://mathworld.wolfram.com/Spirograph.html * * The spirograph also has two other parameters theta (starting angle) and a * starting radius. You can animate both these parameters using keyboard * commands. You can also turn off clearing the frame buffer after each frame * is drawn, which gives nice coloured patterns. * * The mouse is used to control the animation speed. * * To complile on Mac OS X: * gcc -Wall -o spirograph -framework OpenGL -framework GLUT -lobjc -lm spirograph.c * To complie on Linux/X11 * gcc simple.c -o simple -I/usr/include/GL -I/usr/X11R6/include/ -L/usr/X11R6/lib -lX11 -lXi -lXmu -lglut -lGL -lGLU * */ /* Copyright 2005-2007 Jon McCormack * Last modified 9 August 2007 */ #include #include #include #include #ifdef __APPLE__ #include #else #include #endif #define A_INC 0.001 /* how much to change a each frame */ #define THETA_INC 0.001 /* how much to increment theta each frame */ #define min(X, Y) ((X) < (Y) ? (X) : (Y)) #define max(X, Y) ((X) > (Y) ? (X) : (Y)) int g_x, g_y; const char * programName = "SPIROGRAPH 1.2 by Jon McCormack"; /* * Spirograph data structure * Defines the key parameters needed to draw the spirograph */ typedef struct { float p; /* p and q define the ratio between */ float q; /* the two circles */ float a; /* distance from the centre for "pen" placement */ int n; /* number of steps */ float theta; /* start theta for the pen */ } Spirograph; /* * Spirograph drawing parameters */ typedef struct { bool clearMode; /* clear the framebuffer after each frame if true */ bool echoMode; /* trick from processing */ bool animate_a; /* animate the value of a if true */ bool animate_theta; /* animate the value of theta if true */ bool transp; /* use transparency */ bool pointMode; /* draw points rather than lines */ bool menuMode; /* show menu overlay */ float cur_a; /* current value of a */ float inc_a; /* value to increment a by */ float inc_theta; /* value to increment theta by */ } SpirographDrawing; /* * local static variables * g_spirograph is the global variable with the spirograph data * g_spirodraw is the global vairable with the current drawing and animation * options. */ static Spirograph g_spirograph = { /* p q a n theta */ 3.0, 7.0, 1.0, 600, 0.0 }; static SpirographDrawing g_spirodraw = { /* clear echo animate_a animate_theta transp point menu cur_a inc_a inc_theta */ true, false, false, false, true, false, false, 1.0, A_INC, THETA_INC }; /* * hsvTOrgb * converts an hsv (hue, saturation, value) colour value to rgb * (red, green, blue) * Created by Jon McCormack on Sat Jul 10 2004. * */ void hsvTOrgb(float hsv[3], float rgb[3]) { /* hsv and rgb values normalised from 0 - 1 */ int k; float aa, bb, cc, f, h = hsv[0], s = hsv[1], v = hsv[2]; if (s <= 0.0) rgb[0] = rgb[1] = rgb[2] = v; else { if (h >= 1.0) h = 0.0; h *= 6.0; k = floor(h); f = h - k; aa = v * (1.0 - s); bb = v * (1.0 - (s * f)); cc = v * (1.0 - (s * (1.0 - f))); switch(k) { case 0: rgb[0] = v; rgb[1] = cc; rgb[2] = aa; break; case 1: rgb[0] = bb; rgb[1] = v; rgb[2] = aa; break; case 2: rgb[0] = aa; rgb[1] = v; rgb[2] = cc; break; case 3: rgb[0] = aa; rgb[1] = bb; rgb[2] = v; break; case 4: rgb[0] = cc; rgb[1] = aa; rgb[2] = v; break; case 5: rgb[0] = v; rgb[1] = aa; rgb[2] = bb; break; } } } /* * initSpirograph * * set up default spirograph values */ void initSpirograph(Spirograph * s, SpirographDrawing * sd) { /* reset spirograph */ s->p = 3.0; s->q = 7.0; s->a = 1.0; s->n = 600; s->theta = 0; /* reset spirograph drawing parameters */ sd->clearMode = true; sd->echoMode = false; sd->pointMode = false; sd->animate_a = false; sd->animate_theta = false; sd->cur_a = 1.0; sd->inc_a = A_INC; sd->inc_theta = THETA_INC; } /* * drawSpirograph * * function to draw the spirograph using lines. * The number of lines is adapted to the circle radii to ensure smooth curves */ void drawSpirograph(Spirograph * s) { register int i; float t = 0.0; /* parametric distance (radians) */ float cm = (s->q - s->p)/s->q; float cn = (s->q - s->p)/s->p; float x0 = cos(s->theta); float y0 = sin(s->theta); float step; float cos_t, sin_t, xt, yt; float hsv[3], rgb[3]; /* calculate total number of points on the curve and step size */ s->n = max(s->p, s->q) * 300; step = (max(s->p, s->q) * 2.0 * M_PI)/s->n; /* set the line colour based on a and theta * here we use an hsv colour model as it makes it easier to * visualize changes in a and theta. */ hsv[0] = (s->a * cos(s->theta) + 1.0)/2.0; hsv[1] = fabs(s->a); hsv[2] = sin(s->theta) * 0.1 + 0.8; hsvTOrgb(hsv,rgb); // glColor3fv(rgb); glColor4f(rgb[0], rgb[1], rgb[2], 0.5); /* for a simpler rgb colour change use the commented code below */ /* glColor3f( (cos(s->theta) + 1.0)/2.0, (s->a + 1.0)/2.0, (-s->a * sin(s->theta) + 1.0)/2.0 ); */ /* draw the spirograph */ glBegin(g_spirodraw.pointMode ? GL_POINTS : GL_LINE_STRIP); for (i = 0, t = 0.0; i < s->n; ++i, t += step) { cos_t = cm * cos(t) + s->a * cos(cn * t); sin_t = cm * sin(t) - s->a * sin(cn * t); xt = x0 * cos_t - y0 * sin_t; yt = y0 * cos_t + x0 * sin_t; glVertex2f(xt, yt); } glEnd(); /* end drawing */ } void printSpirograph(Spirograph * s) { printf("p = %.0f, q = %.0f, a = %6.4f, theta = %6.4f\n", s->p, s->q, s->a, s->theta); } void drawSquare(int l, int r, int xlen, int ylen) { glBegin(GL_QUADS); glVertex2i(l, r); glVertex2i( l + xlen, r); glVertex2i( l + xlen, r + ylen); glVertex2i(l, r + ylen); glEnd(); } void drawText(const char * text, int xpos, int ypos) { glWindowPos2i(xpos, ypos); while (*text) glutBitmapCharacter(GLUT_BITMAP_8_BY_13, *text++); } void drawMenu() { static char buffer[255]; int tx = g_x/10; int ty = g_y - g_y/10; int tinc = glutBitmapWidth(GLUT_BITMAP_8_BY_13, 'M') * 2; /* save the current matrices */ glMatrixMode(GL_MODELVIEW); glPushMatrix(); glLoadIdentity(); glMatrixMode(GL_PROJECTION); glPushMatrix(); glLoadIdentity(); gluOrtho2D(0, g_x, 0, g_y); /* set projection to map pixels */ glColor4ub(0x80, 0x80, 0x80, 0x40); drawSquare(g_x/20, g_y/20, g_x - g_x/10, g_y - g_y/10); glColor4f(1.0, 0.9, 0.78, 1.0); drawText(programName, tx, ty); drawText(" x - exit program", tx, ty -= tinc * 2); sprintf(buffer, "p/P - dec/inc radius circle 1. Current: %.1f", g_spirograph.p); drawText(buffer, tx, ty -= tinc); sprintf(buffer, "q/Q - dec/inc radius circle 2. Current: %.1f", g_spirograph.q); drawText(buffer, tx, ty -= tinc); sprintf(buffer, " a - toggle animate mode. Current: %s", g_spirodraw.animate_a ? "ON" : "OFF"); drawText(buffer, tx, ty -= tinc); sprintf(buffer, " . - toggle point display mode. Current: %s", g_spirodraw.pointMode ? "ON" : "OFF"); drawText(buffer, tx, ty -= tinc); sprintf(buffer, " e - toggle echo display mode. Current: %s", g_spirodraw.echoMode ? "ON" : "OFF"); drawText(buffer, tx, ty -= tinc); sprintf(buffer, " t - toggle theta animation mode. Current: %s", g_spirodraw.animate_theta ? "ON" : "OFF"); sprintf(buffer, " c - toggle screen clear. Current: %s", g_spirodraw.clearMode ? "ON" : "OFF"); drawText(buffer, tx, ty -= tinc); sprintf(buffer, "s/S - manual dec/inc a. Current: %.2f", g_spirograph.a); drawText(buffer, tx, ty -= tinc); sprintf(buffer, "y/Y - manual dec/inc theta. Current: %.2f", g_spirograph.theta); drawText(buffer, tx, ty -= tinc); drawText(" d - print spirograph", tx, ty -= tinc); drawText(" r - reset spirograph parameters", tx, ty -= tinc); drawText(" - (space) display this menu", tx, ty -= tinc); drawText(" LM - (left mouse click) decrease animation speed", tx, ty -= tinc); drawText(" RM - (right mouse click) increase animation speed", tx, ty -= tinc); glPopMatrix(); glMatrixMode(GL_MODELVIEW); glPopMatrix(); } /* * display * * This function is called by the GLUT to display the graphics * */ void display(void) { /* set matrix mode to modelview */ glMatrixMode(GL_MODELVIEW); glLoadIdentity(); if (g_spirodraw.transp) glEnable(GL_BLEND); /* if clearMode is ture then clear the frame buffer before drawing */ if (!g_spirodraw.echoMode && g_spirodraw.clearMode) glClear( GL_COLOR_BUFFER_BIT ); /* this is a little trick to gradually fade the * last frame each pass. This is done by drawing a * black rectangle over the entire screen with an * alpha value equal to the LSB being set */ if (g_spirodraw.echoMode) { glColor4ub(0x00, 0x00, 0x00, 0x01); drawSquare(-g_x/2, -g_y/2, g_x, g_y); } /* set the default drawing colour */ // glColor3f(1.0, 0.0, 1.0); /* draw the spirograph for this frame */ drawSpirograph(&g_spirograph); if (g_spirodraw.menuMode) drawMenu(); glFlush(); /* force OpenGL output */ } /* * myReshape * * This function is called whenever the user (or OS) reshapes the * OpenGL window. The GLUT sends the new window dimensions (x,y) * */ void myReshape(int w, int h) { /* set viewport to new width and height */ /* note that this command does not change the CTM */ glViewport(0, 0, w, h); /* * set viewing window in world coordinates */ glMatrixMode(GL_PROJECTION); glLoadIdentity(); /* init projection matrix */ if (w <= h) glOrtho(-2.0, 2.0, -2.0 * (GLfloat) h / (GLfloat) w, 2.0 * (GLfloat) h / (GLfloat) w, -1.0, 1.0); else glOrtho(-2.0 * (GLfloat) w / (GLfloat) h, 2.0 * (GLfloat) w / (GLfloat) h, -2.0, 2.0, -1.0, 1.0); g_x = w; g_y = h; /* if echo or not clear mode, then we need to clear the screen * during a resize to prevent random garbage appearing on the screen */ if (g_spirodraw.echoMode || !g_spirodraw.clearMode) glClear( GL_COLOR_BUFFER_BIT ); /* set matrix mode to modelview */ glMatrixMode(GL_MODELVIEW); } /* * myKey * * responds to key presses from the user * The current keyboard commands are: * p - decrement the value of p * P - increment the value of p * q - decrement the value of q * Q - increment the value of q * c - toggle clearMode * a - toggle animate_a * e - toggle echoMode * . - toggle point mode * s - manual decrement of a (s is next to a on the keyboard) * S - manual increment of a * t - toggle animate_theta * y - manual decrement of theta (y is next to t on the keyboard) * Y - manual increment of theta * [space] - display help text/status * x,X - exit the program */ void myKey(unsigned char k, int x, int y) { switch (k) { case 'x': case 'X': exit(0); break; case 'p' : if ((g_spirograph.p -= 1) <= 0) g_spirograph.p = 1; break; case 'P' : g_spirograph.p += 1; break; case 'q' : if ((g_spirograph.q -= 1) <= 0) g_spirograph.q = 1; break; case 'Q' : g_spirograph.q += 1; break; case 'a' : g_spirodraw.animate_a = !g_spirodraw.animate_a; break; case '.' : g_spirodraw.pointMode = !g_spirodraw.pointMode; break; case 'e' : g_spirodraw.echoMode = !g_spirodraw.echoMode; break; case 's' : g_spirograph.a -= 0.1; break; case 'S' : g_spirograph.a += 0.1; break; case 't' : g_spirodraw.animate_theta = !g_spirodraw.animate_theta; break; case 'y' : g_spirograph.theta -= 0.1; break; case 'Y' : g_spirograph.theta += 0.1; break; case 'c' : g_spirodraw.clearMode = ! g_spirodraw.clearMode; break; case 'd' : printSpirograph(&g_spirograph); break; case 'r' : initSpirograph(&g_spirograph, &g_spirodraw); break; case ' ' : g_spirodraw.menuMode = ! g_spirodraw.menuMode; if (!g_spirodraw.menuMode && !g_spirodraw.clearMode) glClear( GL_COLOR_BUFFER_BIT ); break; default: printf("Unknown keyboard command \'%c\'.\n", k); break; } glutPostRedisplay(); } /* * myMouse * * function called by the GLUT when the user presses a mouse button * * Here we increment the global rotation rate with each press - left to do a * positive increment, right for negative, middle to reset */ void myMouse(int btn, int state, int x, int y) { float factor = 1.0; if(btn==GLUT_LEFT_BUTTON && state == GLUT_DOWN) factor = 0.5; if(btn==GLUT_MIDDLE_BUTTON && state == GLUT_DOWN) factor = 0.0; if(btn==GLUT_RIGHT_BUTTON && state == GLUT_DOWN) factor = 2.0; if (g_spirodraw.animate_a) { if( (g_spirodraw.inc_a *= factor) == 0 ) g_spirodraw.inc_a = A_INC; /* reset to default */ } if (g_spirodraw.animate_theta) { if( (g_spirodraw.inc_theta *= factor) == 0 ) g_spirodraw.inc_theta = THETA_INC; /* reset to default */ } /* force redisplay */ glutPostRedisplay(); } /* * myIdleFunc * * increments the rotation variable within glutMainLoop */ void myIdleFunc(void) { if (g_spirodraw.animate_a) { g_spirodraw.cur_a += g_spirodraw.inc_a; g_spirograph.a = sin(g_spirodraw.cur_a); } if (g_spirodraw.animate_theta) { g_spirograph.theta += g_spirodraw.inc_theta; } if (g_spirodraw.animate_a || g_spirodraw.animate_theta) { /* force glut to call the display function */ glutPostRedisplay(); } } /* * main * * Initialization and sets graphics callbacks * */ int main(int argc, char **argv) { /* glutInit MUST be called before any other GLUT/OpenGL calls */ glutInit(&argc, argv); /* set global window size */ g_x = g_y = 500; glutInitDisplayMode(GLUT_RGB); glutInitWindowSize(g_x, g_y); glutCreateWindow("Spirograph"); /* set callback functions */ glutReshapeFunc(myReshape); glutDisplayFunc(display); glutIdleFunc(myIdleFunc); glutKeyboardFunc(myKey); glutMouseFunc(myMouse); /* set clear colour */ glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glEnable(GL_BLEND); glEnable(GL_LINE_SMOOTH); glEnable(GL_POINT_SMOOTH); glLineWidth(2.0); glPointSize(2.0); // glBlendFunc (GL_ONE, GL_SRC_ALPHA); glClearColor(0.0, 0.0, 0.0, 0.0); /* set current colour to white */ /* glColor3f(1.0, 1.0, 1.0); */ printf("%s\n", programName); printf("Press spacebar for menu\n"); glutMainLoop(); return 0; }