/**
* @file
* @author [Krishna Vedala](https://github.com/kvedala)
* @brief Implementation of
* [Spirograph](https://en.wikipedia.org/wiki/Spirograph)
*
* @details
* Implementation of the program is based on the geometry shown in the figure
* below:
*
*
*/
#define _USE_MATH_DEFINES /**< required for MSVC compiler */
#include
#include
#include
#include
#include
/** Generate spirograph curve into arrays `x` and `y` such that the i^th point
* in 2D is represented by `(x[i],y[i])`. The generating function is given by:
* \f{eqnarray*}{
* x &=& R\left[ (1-k) \cos (t) + l\cdot k\cdot\cos \left(\frac{1-k}{k}t\right)
* \right]\\
* y &=& R\left[ (1-k) \sin (t) - l\cdot k\cdot\sin \left(\frac{1-k}{k}t\right)
* \right] \f}
* where
* * \f$R\f$ is the scaling parameter that we will consider \f$=1\f$
* * \f$l=\frac{\rho}{r}\f$ is the relative distance of marker from the centre
* of inner circle and \f$0\le l\le1\f$
* * \f$\rho\f$ is physical distance of marker from centre of inner circle
* * \f$r\f$ is the radius of inner circle
* * \f$k=\frac{r}{R}\f$ is the ratio of radius of inner circle to outer circle
* and \f$0 // include path on Macs is different
#else
#include
#endif
static bool paused = 0; /**< flag to set pause/unpause animation */
static const int animation_speed = 25; /**< animation delate in ms */
static const double step = 0.01; /**< animation step size */
static double l_ratio = 0.1; /**< the l-ratio defined in docs */
static double k_ratio = 0.1; /**< the k-ratio defined in docs */
static const double num_rot = 20.; /**< number of rotations to simulate */
/** A wrapper that is not available in all GLUT implementations.
*/
static inline void glutBitmapString(void *font, char *string)
{
for (char *ch = string; *ch != '\0'; ch++) glutBitmapCharacter(font, *ch);
}
/**
* @brief Function to graph (x,y) points on the OpenGL graphics window.
*
* @param x array containing absicca of points (must be pre-allocated)
* @param y array containing ordinates of points (must be pre-allocated)
* @param N number of points in the arrays
*/
void display_graph(const double *x, const double *y, size_t N, double l,
double k)
{
glClearColor(1.0f, 1.0f, 1.0f,
0.0f); // Set background color to white and opaque
glClear(GL_COLOR_BUFFER_BIT); // Clear the color buffer (background)
if (x && y)
{
glBegin(GL_LINES); // draw line segments
glColor3f(0.f, 0.f, 1.f); // blue
glPointSize(2.f); // point size in pixels
for (size_t i = 1; i < N; i++)
{
glVertex2f(x[i - 1], y[i - 1]); // line from
glVertex2f(x[i], y[i]); // line to
}
glEnd();
}
glColor3f(0.f, 0.f, 0.f);
char buffer[20];
snprintf(buffer, 20, "l = %.3f", l);
glRasterPos2f(-.85, .85);
glutBitmapString(GLUT_BITMAP_HELVETICA_18, buffer);
snprintf(buffer, 20, "k = %.3f", k);
glRasterPos2f(-.85, .75);
glutBitmapString(GLUT_BITMAP_HELVETICA_18, buffer);
glutSwapBuffers();
}
/**
* @brief Test function with animation
*
*/
void test2(void)
{
const size_t N = 1000; // number of samples
static bool direction1 = true; // increment if true, otherwise decrement
static bool direction2 = true; // increment if true, otherwise decrement
double *x = (double *)malloc(N * sizeof(double));
double *y = (double *)malloc(N * sizeof(double));
spirograph(x, y, l_ratio, k_ratio, N, num_rot);
display_graph(x, y, N, l_ratio, k_ratio);
free(x); // free dynamic memories
free(y);
if (paused)
// if paused, do not update l_ratio and k_ratio
return;
if (direction1) // increment k_ratio
{
if (k_ratio >= (1.f - step)) // maximum limit
direction1 = false; // reverse direction of k_ratio
else
k_ratio += step;
}
else // decrement k_ratio
{
if (k_ratio <= step) // minimum limit
{
direction1 = true; // reverse direction of k_ratio
if (direction2) // increment l_ratio
{
if (l_ratio >= (1.f - step)) // max limit of l_ratio
direction2 = false; // reverse direction of l_ratio
else
l_ratio += step;
}
else // decrement l_ratio
{
if (l_ratio <= step) // minimum limit of l_ratio
direction2 = true; // reverse direction of l_ratio
else
l_ratio -= step;
}
}
else // no min limit of k_ratio
k_ratio -= step;
}
}
/**
* @brief GLUT timer callback function to add animation delay.
*/
void timer_cb(int id)
{
glutPostRedisplay();
glutTimerFunc(animation_speed, timer_cb, 0);
}
/**
* @brief Keypress event call back function.
*
* @param key ID of the key pressed
* @param x mouse pointer position at event
* @param y mouse pointer position at event
*/
void keyboard_cb(unsigned char key, int x, int y)
{
switch (key)
{
case ' ': // spacebar toggles pause
paused = !paused; // toggle
break;
case '+': // up arrow key
k_ratio += step;
display_graph(NULL, NULL, 1, l_ratio, k_ratio);
break;
case '_': // down arrow key
k_ratio -= step;
display_graph(NULL, NULL, 1, l_ratio, k_ratio);
break;
case '=': // left arrow key
l_ratio += step;
display_graph(NULL, NULL, 1, l_ratio, k_ratio);
break;
case '-': // right arrow key
l_ratio -= step;
display_graph(NULL, NULL, 1, l_ratio, k_ratio);
break;
case 0x1B: // escape key exits
exit(EXIT_SUCCESS);
}
}
#endif
/** Main function */
int main(int argc, char **argv)
{
test();
#ifdef USE_GLUT
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE);
glutCreateWindow("Spirograph");
glutInitWindowSize(400, 400);
// glutIdleFunc(glutPostRedisplay);
glutTimerFunc(animation_speed, timer_cb, 0);
glutKeyboardFunc(keyboard_cb);
glutDisplayFunc(test2);
glutMainLoop();
#endif
return 0;
}