/*
 * rasonly.c -
 *      Demonstrates the use of OpenGL for rasterization-only, with
 *      perspective-correct texture mapping.
 *
 * Michael I. Gold <gold@sgi.com>
 * Silicon Graphics Computer Systems, May 1997
 *
 * Since current low-end 3D accelerators support only rasterization in
 * hardware, a number of developers have expressed interested in using
 * OpenGL as an interface to rasterization hardware while retaining
 * control of transformations and lighting in the application code.
 * Many OpenGL implementations detect and optimize for identity xforms,
 * so this approach is entirely reasonable.
 *
 * Setting up rasterization-only is fairly straightforward.  The projection
 * matrix is set up as a one-to-one mapping between eye and clip coordinates,
 * and the modelview matrix is set up as identity, e.g. object coordinates
 * map directly to eye coordinates.  This can be achieved as follows:
 *
 *      glMatrixMode(GL_PROJECTION);
 *      glLoadIdentity();
 *      glOrtho(0.0f, (GLfloat) width, 0.0f, (GLfloat) height, -1.0f, 1.0f);
 *      glMatrixMode(GL_MODELVIEW);
 *      glLoadIdentity();
 *      glViewport(0, 0, width, height);
 *
 * where (width, height) represent the window dimensions.
 *
 * Now transformed geometry may be specified directly through the standard
 * interfaces (e.g. glVertex*()).  The only tricky part that remains is
 * specifying texture coordinates such that perspective correction may
 * occur.  The answer is to use glTexCoord4*(), and perform the perspective
 * divide on the texture coordinates directly.
 */

#include <stdlib.h>
#include <string.h>
#include <GL/glut.h>

GLboolean motion = GL_TRUE;

/* Matrices */
GLfloat rot = 0.0f;
GLfloat ModelView[16];
GLfloat Projection[16];
GLfloat Viewport[4];

/* Sample geometry */
GLfloat quadV[][4] = {
    { -1.0f, 0.0f, -1.0f, 1.0f },
    {  1.0f, 0.0f, -1.0f, 1.0f },
    {  1.0f, 0.5f, -0.2f, 1.0f },
    { -1.0f, 0.5f, -0.2f, 1.0f },
};

GLfloat quadC[][3] = {
    { 1.0f, 0.0f, 0.0f },
    { 0.0f, 1.0f, 0.0f },
    { 0.0f, 0.0f, 1.0f },
    { 1.0f, 1.0f, 1.0f },
};

GLfloat quadT[][2] = {
    { 0.0f, 0.0f },
    { 0.0f, 1.0f },
    { 1.0f, 1.0f },
    { 1.0f, 0.0f },
};

/*********************************************************************
 * Utility functions
 */

int texWidth = 128;
int texHeight = 128;

/* Create and download the application texture map */
static void
setCheckedTexture(void)
{
    int texSize;
    void *textureBuf;
    GLubyte *p;
    int i,j;

    /* malloc for rgba as worst case */
    texSize = texWidth*texHeight*4;

    textureBuf = malloc(texSize);
    if (NULL == textureBuf) return;

    p = (GLubyte *)textureBuf;
    for (i=0; i < texWidth; i++) {
        for (j=0; j < texHeight; j++) {
            if ((i ^ j) & 8) {
                p[0] = 0xff; p[1] = 0xff; p[2] = 0xff; p[3] = 0xff;
            } else {
                p[0] = 0x08; p[1] = 0x08; p[2] = 0x08; p[3] = 0xff;
            }
            p += 4;
        }
    }
    gluBuild2DMipmaps(GL_TEXTURE_2D, 4, texWidth, texHeight, 
                 GL_RGBA, GL_UNSIGNED_BYTE, textureBuf);
    free(textureBuf);
}

/* Perform one transform operation */
static void
Transform(GLfloat *matrix, GLfloat *in, GLfloat *out)
{
    int ii;

    for (ii=0; ii<4; ii++) {
        out[ii] = 
            in[0] * matrix[0*4+ii] +
            in[1] * matrix[1*4+ii] +
            in[2] * matrix[2*4+ii] +
            in[3] * matrix[3*4+ii];
    }
}

/* Transform a vertex from object coordinates to window coordinates.
 * Lighting is left as an exercise for the reader.
 */
static void
DoTransform(GLfloat *in, GLfloat *out)
{
    GLfloat tmp[4];
    GLfloat invW;       /* 1/w */

    /* Modelview xform */
    Transform(ModelView, in, tmp);

    /* Lighting calculation goes here! */

    /* Projection xform */
    Transform(Projection, tmp, out);

    if (out[3] == 0.0f) /* do what? */
        return;

    invW = 1.0f / out[3];

    /* Perspective divide */
    out[0] *= invW;
    out[1] *= invW;
    out[2] *= invW;

    /* Map to 0..1 range */
    out[0] = out[0] * 0.5f + 0.5f;
    out[1] = out[1] * 0.5f + 0.5f;
    out[2] = out[2] * 0.5f + 0.5f;

    /* Map to viewport */
    out[0] = out[0] * Viewport[2] + Viewport[0];
    out[1] = out[1] * Viewport[3] + Viewport[1];

    /* Store inverted w for performance */
    out[3] = invW;
}

/*********************************************************************
 * Application code begins here
 */

/* For the sake of brevity, I'm use OpenGL to compute my matrices. */
void UpdateModelView(void)
{
    glPushMatrix();
    glLoadIdentity();
    gluLookAt(0.0f, 1.0f, -4.0f,
              0.0f, 0.0f, 0.0f,
              0.0f, 1.0f, 0.0f);
    glRotatef(rot, 0.0f, 1.0f, 0.0f);
    /* Retrieve the matrix */
    glGetFloatv(GL_MODELVIEW_MATRIX, ModelView);
    glPopMatrix();
}

void InitMatrices(void)
{
    /* Calculate projection matrix */
    glMatrixMode(GL_PROJECTION);
    glPushMatrix();
    glLoadIdentity();
    gluPerspective(45.0f, 1.0f, 1.0f, 100.0f);
    /* Retrieve the matrix */
    glGetFloatv(GL_PROJECTION_MATRIX, Projection);
    glPopMatrix();
    glMatrixMode(GL_MODELVIEW);

    UpdateModelView();
}

void Init(void)
{
    glClearColor(0.2f, 0.2f, 0.6f, 1.0f);

    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);
    glEnable(GL_TEXTURE_2D);
    glEnable(GL_DEPTH_TEST);
    setCheckedTexture();

    InitMatrices();
}

void Redraw(void)
{
    GLfloat tmp[4];
    int ii;

    glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);

    glBegin(GL_QUADS);

    for (ii = 0; ii < 4; ii++) {

        /* Transform a vertex from object to window coordinates.
         * 1/w is returned as tmp[3] for perspective-correcting
         * the texture coordinates.
         */
        DoTransform(quadV[ii], tmp);

        /* Ideally the colors will be computed by the lighting equation,
         * but I've hard-coded values for this example.
         */
        glColor3fv(quadC[ii]);

        /* Scale by 1/w (stored in tmp[3]) */
        glTexCoord4f(quadT[ii][0] * tmp[3],
                     quadT[ii][1] * tmp[3], 0.0f, tmp[3]);

        /* Note I am using Vertex3, not Vertex4, since we have already
         * performed the perspective divide.
         */
        glVertex3fv(tmp);
    }

    glEnd();

    glutSwapBuffers();
}

void Motion(void)
{
    rot += 3.0f;
    if (rot >= 360.0f) rot -= 360.0f;
    UpdateModelView();
    Redraw();
}

/* ARGSUSED1 */
void Key(unsigned char key, int x, int y)
{
    switch (key) {
    case 27:
        exit(0);
    case 'm':
        motion = !motion;
        glutIdleFunc(motion ? Motion : NULL);
        break;
    }
}

/* ARGSUSED1 */
void Button(int button, int state, int x, int y)
{
    switch (button) {
    case GLUT_LEFT_BUTTON:
        if (state == GLUT_DOWN) {
            rot -= 15.0f;
            UpdateModelView();
            Redraw();
        }
        break;
    case GLUT_RIGHT_BUTTON:
        if (state == GLUT_DOWN) {
            rot += 15.0f;
            UpdateModelView();
            Redraw();
        }
        break;
    }
}

void Reshape(int width, int height)
{
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glOrtho(0.0f, (GLfloat) width, 0.0f, (GLfloat) height, -1.0f, 1.0f);

    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();

    glViewport(0, 0, width, height);

    Viewport[0] = Viewport[1] = 0.0f;
    Viewport[2] = (GLfloat) width;
    Viewport[3] = (GLfloat) height;
}

int
main(int argc, char *argv[])
{
    char *t;
    glutInit(&argc, argv);
    glutInitDisplayMode(GLUT_RGB|GLUT_DOUBLE|GLUT_DEPTH);
    glutInitWindowSize(400, 400);
    glutCreateWindow((t=strrchr(argv[0], '\\')) != NULL ? t+1 : argv[0]);

    Init();

    glutDisplayFunc(Redraw);
    glutReshapeFunc(Reshape);
    glutKeyboardFunc(Key);
    glutMouseFunc(Button);
    glutIdleFunc(Motion);

    glutMainLoop();

    return 0;
}
