/* $Id: texture.c,v 1.14 2007/08/03 16:48:50 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.
 */


/* 
 * An example Togl program demonstrating texture mapping
 */

#define USE_TOGL_STUBS

#include "togl.h"
#include <stdlib.h>
#include <string.h>
#if defined(TOGL_AGL)
#  include <OpenGL/glu.h>
#else
#  include <GL/glu.h>
#endif
#include "image.h"

#undef TCL_STORAGE_CLASS
#define TCL_STORAGE_CLASS DLLEXPORT

#define CHECKER 0
#define FACE 1
#define TREE 2


static GLenum minfilter = GL_NEAREST_MIPMAP_LINEAR;
static GLenum magfilter = GL_LINEAR;
static GLenum swrap = GL_REPEAT;
static GLenum twrap = GL_REPEAT;
static GLenum envmode = GL_MODULATE;
static GLubyte polycolor[4] = { 255, 255, 255, 255 };
static int teximage = CHECKER;
static double coord_scale = 1;
static double xrot = 0;
static double yrot = 0;
static double texscale = 1;

static GLint width, height;

static GLboolean blend = GL_FALSE;


/* 
 * Load a texture image.  n is one of CHECKER, FACE or TREE.
 */
static void
texture_image(int n)
{
    if (n == CHECKER) {
#define WIDTH 64
#define HEIGHT 64
        GLubyte teximage[WIDTH * HEIGHT][4];
        int     i, j;

        for (i = 0; i < HEIGHT; i++) {
            for (j = 0; j < WIDTH; j++) {
                GLubyte value;

                value = ((i / 4 + j / 4) % 2) ? 0xff : 0x00;
                teximage[i * WIDTH + j][0] = value;
                teximage[i * WIDTH + j][1] = value;
                teximage[i * WIDTH + j][2] = value;
                teximage[i * WIDTH + j][3] = value;
            }
        }

        glEnable(GL_TEXTURE_2D);
        gluBuild2DMipmaps(GL_TEXTURE_2D, 4, WIDTH, HEIGHT,
                GL_RGBA, GL_UNSIGNED_BYTE, teximage);
        blend = GL_FALSE;

#undef WIDTH
#undef HEIGHT
    } else if (n == FACE) {
        TK_RGBImageRec *img = tkRGBImageLoad("ben.rgb");

        if (img) {
            glEnable(GL_TEXTURE_2D);
            glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
            gluBuild2DMipmaps(GL_TEXTURE_2D, img->sizeZ, img->sizeX, img->sizeY,
                    img->sizeZ == 3 ? GL_RGB : GL_RGBA,
                    GL_UNSIGNED_BYTE, img->data);

            blend = GL_TRUE;
        }
    } else if (n == TREE) {
        TK_RGBImageRec *img = tkRGBImageLoad("tree2.rgba");

        if (img) {
            glEnable(GL_TEXTURE_2D);
            glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
            gluBuild2DMipmaps(GL_TEXTURE_2D, img->sizeZ, img->sizeX, img->sizeY,
                    img->sizeZ == 3 ? GL_RGB : GL_RGBA,
                    GL_UNSIGNED_BYTE, img->data);

            blend = GL_TRUE;
        }
    } else {
        abort();
    }
}


/* 
 * 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)
{
    glEnable(GL_DEPTH_TEST);    /* Enable depth buffering */

    texture_image(CHECKER);

    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, magfilter);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, minfilter);

    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)
{
    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);

    glViewport(0, 0, width, height);

    return TCL_OK;
}


static void
check_error(char *where)
{
    GLenum  error;

    while (1) {
        error = glGetError();
        if (error == GL_NO_ERROR) {
            break;
        }
        printf("OpenGL error near %s: %s\n", where, gluErrorString(error));
    }
}



/* 
 * 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)
{
    float   aspect = (float) width / (float) height;
    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;
    }

    check_error("begin display\n");

    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    /* Draw background image */
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();

    glDisable(GL_TEXTURE_2D);
    glDisable(GL_DEPTH_TEST);
    glBegin(GL_POLYGON);
    glColor3f(0, 0, 0.3f);
    glVertex2f(-1, -1);
    glColor3f(0, 0, 0.3f);
    glVertex2f(1, -1);
    glColor3f(0, 0, 0.9f);
    glVertex2f(1, 1);
    glColor3f(0, 0, 0.9f);
    glVertex2f(-1, 1);
    glEnd();

    /* draw textured object */
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glFrustum(-aspect, aspect, -1, 1, 2, 10);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    glTranslatef(0, 0, -5);
    glScaled(texscale, texscale, texscale);
    glRotated(yrot, 0, 1, 0);
    glRotated(xrot, 1, 0, 0);

    glEnable(GL_DEPTH_TEST);
    glEnable(GL_TEXTURE_2D);
    glColor4ubv(polycolor);

    if (blend) {
        glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
        glEnable(GL_BLEND);
    }

    glBegin(GL_POLYGON);
    glTexCoord2f(0, 0);
    glVertex2f(-1, -1);
    glTexCoord2d(coord_scale, 0);
    glVertex2f(1, -1);
    glTexCoord2d(coord_scale, coord_scale);
    glVertex2f(1, 1);
    glTexCoord2d(0, coord_scale);
    glVertex2f(-1, 1);
    glEnd();

    glDisable(GL_BLEND);

    Togl_SwapBuffers(togl);

    return TCL_OK;
}


/* 
 * Called when a magnification filter radio button is pressed.
 */
static int
magfilter_cmd(ClientData clientData, Tcl_Interp *interp, int objc,
        Tcl_Obj *const *objv)
{
    static const char *names[] = { "GL_NEAREST", "GL_LINEAR", NULL };
    static const GLenum magfilters[] = { GL_NEAREST, GL_LINEAR };
    int     result, index;
    Togl   *togl;

    if (objc != 3) {
        Tcl_WrongNumArgs(interp, 1, objv, "pathName magnification-filter-type");
        return TCL_ERROR;
    }

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

    result = Tcl_GetIndexFromObj(interp, objv[2], names,
            "magnification filter type", 0, &index);
    if (result == TCL_OK) {
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER,
                magfilters[index]);
        Togl_PostRedisplay(togl);
    }
    return result;
}


/* 
 * Called when a minification filter radio button is pressed.
 */
static int
minfilter_cmd(ClientData clientData, Tcl_Interp *interp, int objc,
        Tcl_Obj *const *objv)
{
    static const char *names[] = {
        "GL_NEAREST", "GL_LINEAR",
        "GL_NEAREST_MIPMAP_NEAREST", "GL_LINEAR_MIPMAP_NEAREST",
        "GL_NEAREST_MIPMAP_LINEAR", "GL_LINEAR_MIPMAP_LINEAR", NULL
    };
    static const GLenum minfilters[] = {
        GL_NEAREST, GL_LINEAR,
        GL_NEAREST_MIPMAP_NEAREST, GL_LINEAR_MIPMAP_NEAREST,
        GL_NEAREST_MIPMAP_LINEAR, GL_LINEAR_MIPMAP_LINEAR
    };
    int     result, index;
    Togl   *togl;

    if (objc != 3) {
        Tcl_WrongNumArgs(interp, 1, objv, "pathName minification-filter-type");
        return TCL_ERROR;
    }

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

    result = Tcl_GetIndexFromObj(interp, objv[2], names,
            "minification filter type", 0, &index);
    if (result == TCL_OK) {
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
                minfilters[index]);
        Togl_PostRedisplay(togl);
    }
    return result;
}


static int
xrot_cmd(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], &xrot) != TCL_OK) {
        return TCL_ERROR;
    }

    Togl_PostRedisplay(togl);

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


static int
yrot_cmd(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], &yrot) != TCL_OK) {
        return TCL_ERROR;
    }

    Togl_PostRedisplay(togl);

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


static int
texscale_cmd(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], &texscale) != TCL_OK) {
        return TCL_ERROR;
    }

    Togl_PostRedisplay(togl);

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


/* 
 * Called when S texture coordinate wrapping is changed.
 */
static int
swrap_cmd(ClientData clientData, Tcl_Interp *interp, int objc,
        Tcl_Obj *const *objv)
{
    static const char *names[] = { "GL_CLAMP", "GL_REPEAT", NULL };
    static const GLenum swraps[] = { GL_CLAMP, GL_REPEAT };
    int     result, index;
    Togl   *togl;

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

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

    result = Tcl_GetIndexFromObj(interp, objv[2], names,
            "wrap mode", 0, &index);
    if (result == TCL_OK) {
        swrap = swraps[index];
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, swrap);
        Togl_PostRedisplay(togl);
        /* Let result string equal value */
        Tcl_SetObjResult(interp, objv[2]);
    }
    return result;
}


/* 
 * Called when T texture coordinate wrapping is changed.
 */
static int
twrap_cmd(ClientData clientData, Tcl_Interp *interp, int objc,
        Tcl_Obj *const *objv)
{
    static const char *names[] = { "GL_CLAMP", "GL_REPEAT", NULL };
    static const GLenum twraps[] = { GL_CLAMP, GL_REPEAT };
    int     result, index;
    Togl   *togl;

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

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

    result = Tcl_GetIndexFromObj(interp, objv[2], names,
            "wrap mode", 0, &index);
    if (result == TCL_OK) {
        twrap = twraps[index];
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, twrap);
        Togl_PostRedisplay(togl);
        /* Let result string equal value */
        Tcl_SetObjResult(interp, objv[2]);
    }
    return result;
}


/* 
 * Called when the texture environment mode is changed.
 */
static int
envmode_cmd(ClientData clientData, Tcl_Interp *interp, int objc,
        Tcl_Obj *const *objv)
{
    static const char *names[] = {
        "GL_MODULATE", "GL_DECAL", "GL_BLEND", NULL
    };
    static const GLenum envmodes[] = { GL_MODULATE, GL_DECAL, GL_BLEND };
    int     result, index;
    Togl   *togl;

    if (objc != 3) {
        Tcl_WrongNumArgs(interp, 1, objv, "pathName texture-env-mode");
        return TCL_ERROR;
    }

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

    result = Tcl_GetIndexFromObj(interp, objv[2], names,
            "texture env mode", 0, &index);
    if (result == TCL_OK) {
        envmode = envmodes[index];
        glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, envmode);
        Togl_PostRedisplay(togl);
        /* Let result string equal value */
        Tcl_SetObjResult(interp, objv[2]);
    }
    return result;
}


/* 
 * Called when the polygon color is changed.
 */
static int
polycolor_cmd(ClientData clientData, Tcl_Interp *interp, int objc,
        Tcl_Obj *const *objv)
{
    Togl   *togl;
    int     r, g, b;

    if (objc != 5) {
        Tcl_WrongNumArgs(interp, 1, objv, "pathName r g b");
        return TCL_ERROR;
    }

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

    if (Tcl_GetIntFromObj(interp, objv[2], &r) != TCL_OK
            || Tcl_GetIntFromObj(interp, objv[3], &g) != TCL_OK
            || Tcl_GetIntFromObj(interp, objv[4], &b) != TCL_OK) {
        return TCL_ERROR;
    }
    polycolor[0] = r;
    polycolor[1] = g;
    polycolor[2] = b;

    Togl_PostRedisplay(togl);

    return TCL_OK;
}


/* 
 * Called when the texture image is to be changed
 */
static int
teximage_cmd(ClientData clientData, Tcl_Interp *interp, int objc,
        Tcl_Obj *const *objv)
{
    static const char *names[] = {
        "CHECKER", "FACE", "TREE", NULL
    };
    static const GLenum teximages[] = {
        CHECKER, FACE, TREE
    };
    int     result, index;
    Togl   *togl;

    if (objc != 3) {
        Tcl_WrongNumArgs(interp, 1, objv, "pathName texture-image-name");
        return TCL_ERROR;
    }

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

    result = Tcl_GetIndexFromObj(interp, objv[2], names,
            "texture image name", 0, &index);
    if (result == TCL_OK) {
        teximage = teximages[index];
        texture_image(teximage);
        Togl_PostRedisplay(togl);
        /* Let result string equal value */
        Tcl_SetObjResult(interp, objv[2]);
    }
    return result;
}


/* 
 * Called when the texture coordinate scale is changed.
 */
static int
coord_scale_cmd(ClientData clientData, Tcl_Interp *interp, int objc,
        Tcl_Obj *const *objv)
{
    double  s;
    Togl   *togl;

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

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

    if (Tcl_GetDoubleFromObj(interp, objv[2], &s) != TCL_OK) {
        return TCL_ERROR;
    }
    if (s > 0 && s < 10) {
        coord_scale = s;
        Togl_PostRedisplay(togl);
    }

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


EXTERN int
Texture_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);

    /* 
     * Make a new Togl widget command so the Tcl code can set a C variable.
     */
    Tcl_CreateObjCommand(interp, "min_filter", minfilter_cmd, NULL, NULL);
    Tcl_CreateObjCommand(interp, "mag_filter", magfilter_cmd, NULL, NULL);
    Tcl_CreateObjCommand(interp, "xrot", xrot_cmd, NULL, NULL);
    Tcl_CreateObjCommand(interp, "yrot", yrot_cmd, NULL, NULL);
    Tcl_CreateObjCommand(interp, "texscale", texscale_cmd, NULL, NULL);
    Tcl_CreateObjCommand(interp, "swrap", swrap_cmd, NULL, NULL);
    Tcl_CreateObjCommand(interp, "twrap", twrap_cmd, NULL, NULL);
    Tcl_CreateObjCommand(interp, "envmode", envmode_cmd, NULL, NULL);
    Tcl_CreateObjCommand(interp, "polycolor", polycolor_cmd, NULL, NULL);
    Tcl_CreateObjCommand(interp, "teximage", teximage_cmd, NULL, NULL);
    Tcl_CreateObjCommand(interp, "coord_scale", coord_scale_cmd, NULL, NULL);

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

    return TCL_OK;
}