/* $Id: stereo.c,v 1.11 2008/04/17 00:13:42 gregcouch Exp $ */

/* 
 * Togl - a Tk OpenGL widget
 * Copyright (C) 1996-1997  Brian Paul and Ben Bederson
 * Copyright (C) 2006-2007  Greg Couch
 * See the LICENSE file for copyright details.
 */

#define USE_TOGL_STUBS

#include "togl.h"
#include <stdlib.h>
#include <string.h>

#undef TCL_STORAGE_CLASS
#define TCL_STORAGE_CLASS DLLEXPORT


static Tcl_Obj *toglFont;
static double xAngle = 0, yAngle = 0, zAngle = 0;
static GLfloat CornerX, CornerY, CornerZ;       /* where to print strings */
static double coord_scale = 1;



/* 
 * Togl widget create callback.  This is called by Tcl/Tk when the widget has
 * been realized.  Here's where one may do some one-time context setup or
 * initializations.
 */
static int
create_cb(ClientData clientData, Tcl_Interp *interp, int objc,
        Tcl_Obj *const *objv)
{
    Togl   *togl;

    if (objc != 2) {
        Tcl_WrongNumArgs(interp, 1, objv, "pathName");
        return TCL_ERROR;
    }

    if (Togl_GetToglFromObj(interp, objv[1], &togl) != TCL_OK) {
        return TCL_ERROR;
    }

    toglFont = Togl_LoadBitmapFont(togl, "Helvetica");
    if (!toglFont) {
        printf("Couldn't load font!\n");
        exit(1);
    }
    return TCL_OK;
}


/* 
 * Togl widget reshape callback.  This is called by Tcl/Tk when the widget
 * has been resized.  Typically, we call glViewport and perhaps setup the
 * projection matrix.
 */
static int
reshape_cb(ClientData clientData, Tcl_Interp *interp, int objc,
        Tcl_Obj *const *objv)
{
    int     width;
    int     height;
    float   aspect;
    Togl   *togl;

    if (objc != 2) {
        Tcl_WrongNumArgs(interp, 1, objv, "pathName");
        return TCL_ERROR;
    }

    if (Togl_GetToglFromObj(interp, objv[1], &togl) != TCL_OK) {
        return TCL_ERROR;
    }

    width = Togl_Width(togl);
    height = Togl_Height(togl);
    aspect = (float) width / (float) height;

    glViewport(0, 0, width, height);

    /* Set up projection transform */
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glFrustum(-aspect, aspect, -1, 1, 1, 10);

    CornerX = -aspect;
    CornerY = -1;
    CornerZ = -1.1f;

    /* Change back to model view transform for rendering */
    glMatrixMode(GL_MODELVIEW);
    return TCL_OK;
}


static void
print_string(Togl *togl, const char *s)
{
    Togl_WriteChars(togl, toglFont, s, 0);
}

static void
draw_eye(Togl *togl)
{
    Togl_Clear(togl, GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    Togl_Frustum(togl, -1, 1, -1, 1, 1, 10);
    glMatrixMode(GL_MODELVIEW);

    /* Front face */
    glBegin(GL_QUADS);
    glColor3f(0.4f, 0.8f, 0.4f);        /* Green-ish */
    glVertex3f(-1, 1, 1);
    glVertex3f(1, 1, 1);
    glVertex3f(1, -1, 1);
    glVertex3f(-1, -1, 1);
    /* Back face */
    glColor3f(0.8f, 0.8f, 0.4f);        /* Yellow-ish */
    glVertex3f(-1, 1, -1);
    glVertex3f(1, 1, -1);
    glVertex3f(1, -1, -1);
    glVertex3f(-1, -1, -1);
    /* Top side face */
    glColor3f(0.4f, 0.4f, 0.8f);        /* Blue-ish */
    glVertex3f(-1, 1, 1);
    glVertex3f(1, 1, 1);
    glVertex3f(1, 1, -1);
    glVertex3f(-1, 1, -1);
    /* Bottom side face */
    glColor3f(0.8f, 0.4f, 0.4f);        /* Red-ish */
    glVertex3f(-1, -1, 1);
    glVertex3f(1, -1, 1);
    glVertex3f(1, -1, -1);
    glVertex3f(-1, -1, -1);
    glEnd();
}

/* 
 * Togl widget display callback.  This is called by Tcl/Tk when the widget's
 * contents have to be redrawn.  Typically, we clear the color and depth
 * buffers, render our objects, then swap the front/back color buffers.
 */
static int
display_cb(ClientData clientData, Tcl_Interp *interp, int objc,
        Tcl_Obj *const *objv)
{
    Togl   *togl;

    if (objc != 2) {
        Tcl_WrongNumArgs(interp, 1, objv, "pathName");
        return TCL_ERROR;
    }

    if (Togl_GetToglFromObj(interp, objv[1], &togl) != TCL_OK) {
        return TCL_ERROR;
    }

    /* setup modelview matrix */
    glLoadIdentity();           /* Reset modelview matrix to the identity
                                 * matrix */
    glTranslatef(0, 0, -3.0);   /* Move the camera back three units */
    glScaled(coord_scale, coord_scale, coord_scale);    /* Zoom in and out */
    glRotated(xAngle, 1, 0, 0); /* Rotate by X, Y, and Z angles */
    glRotated(yAngle, 0, 1, 0);
    glRotated(zAngle, 0, 0, 1);

    glEnable(GL_DEPTH_TEST);

    if (Togl_NumEyes(togl) == 1) {
        /* single eye */
        Togl_DrawBuffer(togl, GL_BACK);
        draw_eye(togl);
    } else {
        /* stereo left eye */
        Togl_DrawBuffer(togl, GL_BACK_LEFT);
        draw_eye(togl);

        /* stereo right eye */
        Togl_DrawBuffer(togl, GL_BACK_RIGHT);
        draw_eye(togl);
    }

    glDisable(GL_DEPTH_TEST);
    glLoadIdentity();
    glColor3f(1, 1, 1);
    glRasterPos3f(CornerX, CornerY, CornerZ);
    print_string(togl, Togl_Ident(togl));
    Togl_SwapBuffers(togl);
    return TCL_OK;
}


static int
setXrot_cb(ClientData clientData, Tcl_Interp *interp, int objc,
        Tcl_Obj *const *objv)
{
    Togl   *togl;

    if (objc != 3) {
        Tcl_WrongNumArgs(interp, 1, objv, "pathName angle");
        return TCL_ERROR;
    }

    if (Togl_GetToglFromObj(interp, objv[1], &togl) != TCL_OK) {
        return TCL_ERROR;
    }

    if (Tcl_GetDoubleFromObj(interp, objv[2], &xAngle) != TCL_OK) {
        return TCL_ERROR;
    }

    /* printf( "before %f ", xAngle ); */

    if (xAngle < 0) {
        xAngle += 360;
    } else if (xAngle > 360) {
        xAngle -= 360;
    }

    /* printf( "after %f \n", xAngle ); */

    Togl_PostRedisplay(togl);

    /* Let result string equal value */
    Tcl_SetObjResult(interp, objv[2]);
    return TCL_OK;
}


static int
setYrot_cb(ClientData clientData, Tcl_Interp *interp, int objc,
        Tcl_Obj *const *objv)
{
    Togl   *togl;

    if (objc != 3) {
        Tcl_WrongNumArgs(interp, 1, objv, "pathName angle");
        return TCL_ERROR;
    }

    if (Togl_GetToglFromObj(interp, objv[1], &togl) != TCL_OK) {
        return TCL_ERROR;
    }

    if (Tcl_GetDoubleFromObj(interp, objv[2], &yAngle) != TCL_OK) {
        return TCL_ERROR;
    }

    if (yAngle < 0) {
        yAngle += 360;
    } else if (yAngle > 360) {
        yAngle -= 360;
    }

    Togl_PostRedisplay(togl);

    /* Let result string equal value */
    Tcl_SetObjResult(interp, objv[2]);
    return TCL_OK;
}


int
getXrot_cb(ClientData clientData, Tcl_Interp *interp,
        int argc, CONST84 char *argv[])
{
    Tcl_SetObjResult(interp, Tcl_NewDoubleObj(xAngle));
    return TCL_OK;
}


int
getYrot_cb(ClientData clientData, Tcl_Interp *interp,
        int argc, CONST84 char *argv[])
{
    Tcl_SetObjResult(interp, Tcl_NewDoubleObj(yAngle));
    return TCL_OK;
}


static int
coord_scale_cb(ClientData clientData, Tcl_Interp *interp, int objc,
        Tcl_Obj *const *objv)
{
    Togl   *togl;

    if (objc != 3) {
        Tcl_WrongNumArgs(interp, 1, objv, "pathName value");
        return TCL_ERROR;
    }

    if (Togl_GetToglFromObj(interp, objv[1], &togl) != TCL_OK) {
        return TCL_ERROR;
    }

    if (Tcl_GetDoubleFromObj(interp, objv[2], &coord_scale) != TCL_OK) {
        return TCL_ERROR;
    }

    Togl_PostRedisplay(togl);

    /* Let result string equal value */
    Tcl_SetObjResult(interp, objv[2]);
    return TCL_OK;
}


EXTERN int
Stereo_Init(Tcl_Interp *interp)
{
    /* 
     * Initialize Tcl and the Togl widget module.
     */
    if (Tcl_InitStubs(interp, "8.1", 0) == NULL
            || Togl_InitStubs(interp, "2.0", 0) == NULL) {
        return TCL_ERROR;
    }

    /* 
     * Specify the C callback functions for widget creation, display,
     * and reshape.
     */
    Tcl_CreateObjCommand(interp, "create_cb", create_cb, NULL, NULL);
    Tcl_CreateObjCommand(interp, "display_cb", display_cb, NULL, NULL);
    Tcl_CreateObjCommand(interp, "reshape_cb", reshape_cb, NULL, NULL);

    Tcl_CreateObjCommand(interp, "setXrot", setXrot_cb, NULL, NULL);
    Tcl_CreateObjCommand(interp, "setYrot", setYrot_cb, NULL, NULL);
    Tcl_CreateObjCommand(interp, "coord_scale", coord_scale_cb, NULL, NULL);

    /* 
     * Call Tcl_CreateCommand for application-specific commands, if
     * they weren't already created by the init procedures called above.
     */

    Tcl_CreateCommand(interp, "getXrot", getXrot_cb, NULL, NULL);
    Tcl_CreateCommand(interp, "getYrot", getYrot_cb, NULL, NULL);

    return TCL_OK;
}
