/*
*  A Simple Springy Drawing Program
*  By Nicholas Zambetti
*
*  left mouse  - used for drawing
*  'q' key     - exits
*  'c' key     - clears page
*
*  To Compile On Mac OS X
*  gcc -ansi -Wall drawSpringGL.c -o drawSpringGL -lobjc -framework OpenGL -framework GLUT
*
*/

#pragma mark ---- Includes ----

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <GLUT/glut.h>
#include <OpenGL/gl.h>
#include <OpenGL/glu.h>

#pragma mark ---- Definitions ----

#define FPS 80
#define WINDOW_WIDTH 560
#define WINDOW_HEIGHT 380
#define RATIO 0.08
#define FRICTION 0.92

#pragma mark ---- Type Declarations ----

typedef struct {
    float x;
    float y;
} vector;

#pragma mark ---- Variable Declarations ----

int redrawDelay = 1000 / FPS;
int windowWidth;
int windowHeight;
int leftButtonDown = 0;
int updateControl = 0;
vector speed;
vector mouse;
vector filteredMouse;
vector prevFilteredMouse;
vector prevFilteredMouse2;

#pragma mark ---- Functions ----

/* bezier curve interpolator */
void bezier3(vector p1, vector p2, vector puller, int divisions)
{
    double percent = 0.0;
    double percent2, percentInv, percentInv2;
    double step = 1.0 / (double) divisions;
    vector p;
    while (percent <= 1.0) {
        percent2 = percent * percent;
        percentInv = 1 - percent;
        percentInv2 = percentInv * percentInv;
        p.x = p1.x * percentInv2 + 2 * puller.x * percentInv * percent + p2.x * percent2;
        p.y = p1.y * percentInv2 + 2 * puller.y * percentInv * percent + p2.y * percent2;
        glVertex2f(p.x, p.y);
        percent += step;
    }
}

#pragma mark ---- GLUT Callbacks ----

void onDisplay(void)
{
    if(leftButtonDown) {
        if(updateControl < 1) {
            glBegin(GL_LINE_STRIP);
            glVertex2f(prevFilteredMouse2.x, prevFilteredMouse2.y);
            bezier3(prevFilteredMouse2, filteredMouse, prevFilteredMouse, 20);
            glVertex2f(filteredMouse.x, filteredMouse.y);
            glEnd();
            ++updateControl;
        } else {
            updateControl = 0;
        }
    }
    glutSwapBuffers();
}

void onResize(int width, int height)
{
    windowWidth = width;
    windowHeight = height;
    glViewport(0, 0, windowWidth, windowHeight);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glOrtho(0, windowWidth, 0, windowHeight, -1, 1);
    glScalef(1, -1, 1);
    glTranslatef(0, -windowHeight, 0);
    glMatrixMode(GL_MODELVIEW);
    glClear(GL_COLOR_BUFFER_BIT);
    glutPostRedisplay();
}

void onMouseButton(int button, int state, int xPos, int yPos)
{
    if(button == GLUT_LEFT_BUTTON) {
        switch(state) {
            case GLUT_UP:
                leftButtonDown = 0;
                break;
            case GLUT_DOWN:
                leftButtonDown = 1;
                break;
        }
    }
}

void onMouseMove(int x, int y)
{
    mouse.x = (float) x;
    mouse.y = (float) y;
}

void onTick(int timerId)
{
    if(leftButtonDown) {
        prevFilteredMouse2.x = prevFilteredMouse.x;
        prevFilteredMouse2.y = prevFilteredMouse.y;
        prevFilteredMouse.x = filteredMouse.x;
        prevFilteredMouse.y = filteredMouse.y;
        filteredMouse.x += speed.x = ((mouse.x - filteredMouse.x) * RATIO) + (speed.x * FRICTION);
        filteredMouse.y += speed.y = ((mouse.y - filteredMouse.y) * RATIO) + (speed.y * FRICTION);
    } else {
        prevFilteredMouse2.x = prevFilteredMouse.x = filteredMouse.x = mouse.x;
        prevFilteredMouse2.y = prevFilteredMouse.y = filteredMouse.y = mouse.y;
    }
    glutPostRedisplay();
    glutTimerFunc(redrawDelay, onTick, 0);
}

void onKeyChar(unsigned char inKey, int x, int y)
{
    switch(inKey) {
        case 27:
        case 'q':
        case 'Q':
            exit(0);
            break;
        case 'c':
        case 'C':
            glClear(GL_COLOR_BUFFER_BIT);
            break;
    }
}

#pragma mark ---- main ----

int main(int argc, const char *argv[])
{
    /* glut init */
    glutInit(&argc, (char **) argv);
    glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB);
    glutInitWindowPosition(50, 50);
    glutInitWindowSize(WINDOW_WIDTH, WINDOW_HEIGHT);
    glutCreateWindow("Draw");

    /* gl init */
    glShadeModel(GL_FLAT);
    glClearColor(0.5, 0.08, 0.38, 1.0);
    onResize(WINDOW_WIDTH, WINDOW_HEIGHT);
    glColor3f(1.0, 1.0, 1.0);

    /* register glut event callbacks */
    glutReshapeFunc(onResize);
    glutDisplayFunc(onDisplay);
    glutKeyboardFunc(onKeyChar);
    glutMouseFunc(onMouseButton);
    glutMotionFunc(onMouseMove);
    glutPassiveMotionFunc(onMouseMove);
    glutTimerFunc(redrawDelay, onTick, 0);

    /* begin event loop */
    glutMainLoop();

    return 0;
}

