/*********************************************************************NVMH3****

Copyright NVIDIA Corporation 2003
TO THE MAXIMUM EXTENT PERMITTED BY APPLICABLE LAW, THIS SOFTWARE IS PROVIDED
*AS IS* AND NVIDIA AND ITS SUPPLIERS DISCLAIM ALL WARRANTIES, EITHER EXPRESS
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS FOR A PARTICULAR PURPOSE.  IN NO EVENT SHALL NVIDIA OR ITS SUPPLIERS
BE LIABLE FOR ANY SPECIAL, INCIDENTAL, INDIRECT, OR CONSEQUENTIAL DAMAGES
WHATSOEVER (INCLUDING, WITHOUT LIMITATION, DAMAGES FOR LOSS OF BUSINESS PROFITS,
BUSINESS INTERRUPTION, LOSS OF BUSINESS INFORMATION, OR ANY OTHER PECUNIARY
LOSS) ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE, EVEN IF
NVIDIA HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.

******************************************************************************/

//
// interfaces example app
//

#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <string.h>
#ifdef WIN32
# include <windows.h>
#endif
#ifdef __APPLE__
# include <OpenGL/gl.h>
# include <OpenGL/glext.h>
# include <GLUT/glut.h>
#else
# include <GL/gl.h>
# include <GL/glut.h>
# include <GL/glext.h>
#endif
#include <Cg/cg.h>
#include <Cg/cgGL.h>


#ifndef M_PI
#define M_PI 3.141592
#endif
#define TWOPI       (2.0*M_PI)
#define DEG2RAD(x)  ((x)*M_PI/180.)
#define GROUNDWIDTH 20

typedef struct {
    float ambient[3], diffuse[3], specular[3], exp;
} BlinnProperties;

static BlinnProperties teapotProperties = {
    {.05, .05, .05}, {.8, .4, .1}, {.4, .2, .2}, 10.
};

static BlinnProperties brightProperties = {
    {.05, .05, .05}, {1.9, 1.9, 1.9}, {.1, .1, .1}, 20.
};

static BlinnProperties darkProperties = {
    {.05, .05, .05}, {.1, .1, .1}, {.4, .4, .4}, 40.
};

typedef struct {
    float r, theta, phi;
} PolarCoord;

typedef struct {
    PolarCoord from;
    float target[3];
    char *name;
    bool rotateMode;
} PolarView;

PolarView Eye = {{45., DEG2RAD(30.), DEG2RAD(60.)}, {0,0,0}, "Camera"};

typedef struct {
    PolarView view;
    float color[3];
    bool enabled;
    char *name;
    CGparameter handle, posHandle, targetHandle;
} LightInfo;

LightInfo lights[] = {
    {{{ 9, DEG2RAD(-20), DEG2RAD(60)}, {0,0,0}, "Point"}, {.2,.2,.25}, false, "PointLight"},
    {{{16, DEG2RAD( 40), DEG2RAD(40)}, {0,0,0}, "Spot"}, {.6,.1,.1}, false, "SpotLight"}
};

static int numLights = sizeof(lights)/sizeof(LightInfo);
static int numSelections = numLights + 1;

PolarView *motionSelections[] = { &Eye, &lights[0].view, &lights[1].view };

int currentSelection;

static int screenWidth = 300;
static int screenHeight = 300;
static int mouseX, mouseY;
static int numRotating = 0;
static bool moveMode, zoomMode, retargetMode;
static bool drawLightsMode = true;
static bool literalMode = true;
static bool printMode = false;

static const char *progName;
static const char **compilerArgs;
static bool gotError = false;

static CGcontext context;
static CGprofile vertexProfile = CG_PROFILE_UNKNOWN;
static CGprofile fragmentProfile = CG_PROFILE_UNKNOWN;
static CGprogram vertexProg, teapotProg, groundProg, lightProg;

// Forward declarations of functions
static void display();
static void initPrograms();
static void idle();
static void keyboard(unsigned char key, int x, int y);
static void reshape(int, int);
static void motion(int, int);
static void mouse(int, int, int, int);
static void handleCgError();
static void LoadCgPrograms();
static void updateLights(void);

static const char **
parseArgs(int argc, const char **argv)
{
    progName = argv[0];
    argv++;
    while (--argc) {
        if (0 == strcmp(*argv, "-nv3x")) {
            vertexProfile = CG_PROFILE_VP30;
            fragmentProfile = CG_PROFILE_FP30;
        } else if (0 == strcmp(*argv, "-arb")) {
            vertexProfile = CG_PROFILE_ARBVP1;
            fragmentProfile = CG_PROFILE_ARBFP1;
        } else if (0 == strcmp(*argv, "-nv4x")) {
            vertexProfile = CG_PROFILE_VP30;
            fragmentProfile = CG_PROFILE_FP40;
        } else if (0 == strcmp(*argv, "-d")) {
            printMode = true;
        } else if (0 == strcmp(*argv, "-l")) {
            literalMode = !literalMode;
        } else {
            // return pointer to first non-recognized option
            return argv;
        }
        argv++;
    }
    return argv;
}

static void
printProgram(CGprogram prog)
{
    if (printMode) {
        printf("%s\n", cgGetProgramString(prog, CG_COMPILED_PROGRAM));
        fflush(stdout);
    }
}

///////////////////////////////////////////////////////////////////////////
// Main program; do basic GLUT and Cg setup, but leave most of the work
// to the display() function.

int main(int argc, char *argv[]) {
    char winTitle[BUFSIZ];
    compilerArgs = parseArgs(argc, (const char **)argv);
    glutInit(&argc, argv);
    glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
    glutInitWindowSize(screenWidth, screenHeight);
    sprintf(winTitle, "cgGL interfaces example");
    glutCreateWindow(winTitle);

    glutMouseFunc(mouse);
    glutReshapeFunc(reshape);
    glutKeyboardFunc(keyboard);
    glutMotionFunc(motion);
    glutDisplayFunc(display);
    if (numRotating > 0)
        glutIdleFunc(idle);

    // Basic Cg setup; register a callback function for any errors
    // and create an initial context
    cgSetErrorCallback(handleCgError);
    context = cgCreateContext();

    // initialize programs, etc.
    initPrograms();

    // and all the rest happens in the display function...
    glutMainLoop();

    return 0;
}

static void toggleLight(int which)
{
    lights[which].enabled = !lights[which].enabled;
    CGparameter param = cgGetNamedStructParameter(lights[which].handle, "enabled");
    cgSetParameter1f(param, lights[which].enabled ? 1.f : 0.f);
}

void idle()
{
    if (numRotating > 0)
        glutPostRedisplay();
}

static void
bindPrograms(CGprogram vert, CGprogram frag)
{
    CGparameter param;
    cgGLEnableProfile(vertexProfile);
    cgGLEnableProfile(fragmentProfile);
    cgGLBindProgram(vert);
    cgGLBindProgram(frag);
    param = cgGetNamedProgramParameter(vert, CG_PROGRAM, "ModelView");
    cgGLSetStateMatrixParameter(param, 
                                  CG_GL_MODELVIEW_MATRIX,
                                  CG_GL_MATRIX_IDENTITY);
    param = cgGetNamedProgramParameter(vert, CG_PROGRAM, "ModelViewIT");
    cgGLSetStateMatrixParameter(param, 
                                  CG_GL_MODELVIEW_MATRIX,
                                  CG_GL_MATRIX_INVERSE_TRANSPOSE);
    param = cgGetNamedProgramParameter(vert, CG_PROGRAM, "ModelViewProj");
    cgGLSetStateMatrixParameter(param, 
                                  CG_GL_MODELVIEW_PROJECTION_MATRIX,
                                  CG_GL_MATRIX_IDENTITY);
}
   
static void
polarToCartesian(PolarCoord p, float c[3])
{
    c[0] = p.r*sin(p.phi)*sin(p.theta);
    c[1] = p.r*sin(p.phi)*cos(p.theta);
    c[2] = p.r*cos(p.phi);
}

void
transformPoint(float p[3], float m[16], float ov[3])
{
    ov[0] = m[0]*p[0] + m[4]*p[1] + m[8]*p[2] + m[12];
    ov[1] = m[1]*p[0] + m[5]*p[1] + m[9]*p[2] + m[13];
    ov[2] = m[2]*p[0] + m[6]*p[1] + m[10]*p[2] + m[14];
    float w = m[3]*p[0] + m[7]*p[1] + m[11]*p[2] + m[15];
    if (w != 1.) {
        ov[0] /= w; ov[1] /= w; ov[2] /= w;
    }
}

void
transformPoint(PolarCoord p, float m[16], float ov[3])
{
    float c[3];
    polarToCartesian(p, c);
    transformPoint(c, m, ov);
}

static void
glPuts(const char *str, void *font)
{
    int len = strlen(str);
    for (int i = 0; i < len; i++)
        glutBitmapCharacter(font, str[i]);
}

static void
drawLights(void)
{
    CGparameter param;
    static GLUquadricObj *quad = 0;
    float vec[3];

    if (quad == 0) {
        quad = gluNewQuadric();
        gluQuadricOrientation(quad, GLU_OUTSIDE);
    }

    for (int i = 0; i < numLights; i++) {
        glPushMatrix();
        polarToCartesian(lights[i].view.from, vec);
        glTranslatef(vec[0], vec[1], vec[2]);
        bindPrograms(vertexProg, lightProg);
        param = cgGetNamedProgramParameter(lightProg, CG_PROGRAM, "color");
        vec[0] = lights[i].color[0];
        vec[1] = lights[i].color[1];
        vec[2] = lights[i].color[2];
        if (motionSelections[currentSelection] != &lights[i].view) {
            vec[0] *= 0.2; vec[1] *= 0.2; vec[2] *= 0.2;
        }
        cgSetParameter3fv(param, vec);
        gluSphere(quad, .2, 5, 5);
        glPopMatrix();
    }
}

static void
drawGround(void)
{
    static float quadVerts[] = {
        -GROUNDWIDTH/2., -GROUNDWIDTH/2., 0.0,
         GROUNDWIDTH/2., -GROUNDWIDTH/2., 0.0,
         GROUNDWIDTH/2.,  GROUNDWIDTH/2., 0.0,
        -GROUNDWIDTH/2.,  GROUNDWIDTH/2., 0.0,
    };
    static float quadNorms[] = {
        0,0,1, 0,0,1, 0,0,1, 0,0,1
    };
    glPushMatrix();
    bindPrograms(vertexProg, groundProg);
    glVertexPointer(3, GL_FLOAT, 0, quadVerts);
    glNormalPointer(GL_FLOAT, 0, quadNorms);
    glDrawArrays(GL_QUADS, 0, 4); 
    glPopMatrix();
}


static void
drawText(void)
{
    char buf[BUFSIZ];

    cgGLDisableProfile(vertexProfile);
    cgGLDisableProfile(fragmentProfile);

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glOrtho(-1,1,-1,1,-1,1);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    glDisable(GL_LIGHTING);
    glDisable(GL_DEPTH_TEST);
    glColor3f(1,1,1);
    glPushMatrix();
    glRasterPos2f(-.98,-.98);
    sprintf(buf, "%s %s", motionSelections[currentSelection]->name,
            moveMode ? "(move)" :
           (zoomMode ? "(zoom)" :
           (retargetMode ? "(retarget)" :  " ")));
    glPuts(buf, GLUT_BITMAP_9_BY_15);
    glEnable(GL_DEPTH_TEST);
    glEnable(GL_LIGHTING);
    glPopMatrix();
}

static void
display(void)
{
    static bool init = false;

    if (!init) {
        glEnable(GL_DEPTH_TEST);
        glDisable(GL_CULL_FACE);
        glEnableClientState(GL_VERTEX_ARRAY);
        glEnableClientState(GL_NORMAL_ARRAY);
        glClearColor(.1, .1, .1, 1.);
        init = true;
    }

    for (int i = 0; i < numSelections; i++) {
        if (motionSelections[i]->rotateMode)
            motionSelections[i]->from.theta += 0.02;
    }

    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    glMatrixMode(GL_PROJECTION);
    glPushMatrix();
    glLoadIdentity();
    gluPerspective(25,(float)screenWidth/(float)screenHeight,.1,1000);

    glMatrixMode(GL_MODELVIEW);

    glLoadIdentity();
    glPushMatrix();
    glLoadIdentity();

    float eyeCoords[3];
    polarToCartesian(Eye.from, eyeCoords);
    gluLookAt(eyeCoords[0], eyeCoords[1], eyeCoords[2],
              Eye.target[0], Eye.target[1], Eye.target[2],
              -cos(Eye.from.phi)*sin(Eye.from.theta),
              -cos(Eye.from.phi)*cos(Eye.from.theta),
               sin(Eye.from.phi));

    // Compute eye space light positions
    updateLights();
    if (drawLightsMode)
        drawLights();

    // Draw teapot
    glPushMatrix();
    bindPrograms(vertexProg, teapotProg);
    glutSolidTeapot(2.0f);
    glPopMatrix();

    // Draw ground plane
    drawGround();

    glPopMatrix();
    glMatrixMode(GL_PROJECTION);
    glPopMatrix();

    drawText();
    glEnd();
    glutSwapBuffers();
}

static void initPrograms()
{
    // Do one-time setup only once; setup Cg programs and textures
    // and set up OpenGL state.
    if (vertexProfile == CG_PROFILE_UNKNOWN ||
        fragmentProfile == CG_PROFILE_UNKNOWN) {
        vertexProfile   = cgGLGetLatestProfile(CG_GL_VERTEX);
        fragmentProfile = cgGLGetLatestProfile(CG_GL_FRAGMENT);
    }

    cgGLSetOptimalOptions(vertexProfile);
    cgGLSetOptimalOptions(fragmentProfile);

    LoadCgPrograms();
    if (gotError) {
        fprintf(stderr, "Failed to load programs.\n");
        exit(1);
    }
    if (printMode)
        fprintf(stderr, "Warnings:\n%s\n", cgGetLastListing(context));
}

static CGparameter createLightSource(CGprogram prog, LightInfo &linfo)
{
    // Get handle to user-defined type
    CGtype lightType = cgGetNamedUserType(prog, linfo.name);
    // Create parameter of that type
    CGparameter light = cgCreateParameter(context, lightType);
    if (light == 0)
        return 0;
    // Stash handles to position, target parameters
    linfo.posHandle = cgGetNamedStructParameter(light, "Plight");
    linfo.targetHandle = cgGetNamedStructParameter(light, "target");
    // Set light color
    CGparameter param = cgGetNamedStructParameter(light, "Clight");
    if (param == 0)
        return 0;
    cgSetParameter3fv(param, linfo.color);
    if (literalMode)
        cgSetParameterVariability(param, CG_LITERAL);
    return light;
}

static void updateLights(void)
{
    float modelViewMat[16], lightPosEye[3], lightTargetEye[3];
    glGetFloatv(GL_MODELVIEW_MATRIX, modelViewMat);
    // Compute eye-space coords of each light source's
    // position and (if applicable) target
    for (int i = 0; i < numLights; i++) {
        transformPoint(lights[i].view.from, modelViewMat, lightPosEye);
        cgSetParameter3fv(lights[i].posHandle, lightPosEye);
        if (lights[i].targetHandle) {
            transformPoint(lights[i].view.target, modelViewMat, lightTargetEye);
            cgSetParameter3fv(lights[i].targetHandle, lightTargetEye);
        }
    }
}

static void setBlinnProperties(CGparameter b, BlinnProperties &p)
{
    cgSetParameter3fv(cgGetNamedStructParameter(b, "ambient"), p.ambient);
    cgSetParameter3fv(cgGetNamedStructParameter(b, "diffuse"), p.diffuse);
    cgSetParameter3fv(cgGetNamedStructParameter(b, "specular"), p.specular);
    cgSetParameter1f(cgGetNamedStructParameter(b, "sexp"), p.exp);
    if (literalMode)
        cgSetParameterVariability(b, CG_LITERAL);
}

static void LoadCgPrograms()
{
    // Create & wire up up a handful of programs.
    CGparameter param;
    assert(cgIsContext(context));

    // First, create programs that don't make use
    // of shared instances, and thus can be compiled
    // immediately.

    // Create vertex program.  This is the vertex program
    // used with all of the fragment shaders.
    vertexProg = cgCreateProgramFromFile(context, CG_SOURCE, "vertex.cg",
                                         vertexProfile, "main", compilerArgs);
    assert(vertexProg);
    printProgram(vertexProg);
    cgGLLoadProgram(vertexProg);

    // Create constant shader, used for light source visualization
    lightProg = cgCreateProgramFromFile(context, CG_SOURCE, "constant.cg",
                                        fragmentProfile, "main", compilerArgs);
    assert(lightProg);
    printProgram(lightProg);
    cgGLLoadProgram(lightProg);

    // Next, create shared instances and the programs that use them.

    // Turn on manual compilation so the runtime won't try to compile
    // incomplete programs.
    cgSetAutoCompile(context, CG_COMPILE_MANUAL);

    // Create teapot fragment program
    teapotProg = cgCreateProgramFromFile(context, CG_SOURCE, "materialmain.cg",
                                         fragmentProfile, "main", compilerArgs);
    // Create metal material for teapot.
    CGparameter metal = cgCreateParameter(context, cgGetNamedUserType(teapotProg, "Blinn"));
    setBlinnProperties(metal, teapotProperties);

    // Connect material parameter of teapot program to metal material
    cgConnectParameter(metal, param = cgGetNamedProgramParameter(teapotProg, CG_PROGRAM, "material"));

    // Create ground fragment program
    groundProg = cgCreateProgramFromFile(context, CG_SOURCE, "materialmain.cg",
                                         fragmentProfile, "main", compilerArgs);
    // Create checker material
    CGparameter checker = cgCreateParameter(context,
                                cgGetNamedUserType(teapotProg, "CheckerBlinn"));

    param = cgGetNamedStructParameter(checker, "csize");
    cgSetParameter1f(param, 1.);
    if (literalMode)
        cgSetParameterVariability(param, CG_LITERAL);

    param = cgGetNamedStructParameter(checker, "mats");
    setBlinnProperties(cgGetArrayParameter(param, 0), brightProperties);
    setBlinnProperties(cgGetArrayParameter(param, 1), darkProperties);

    // Associate checker material with material parameter of ground program
    cgConnectParameter(checker, cgGetNamedProgramParameter(groundProg, CG_PROGRAM, "material"));

    // Next, create shared light sources
    // Get type handles from any program that includes the
    // appropriate type definition.

    CGparameter lArray = cgCreateParameterArray(context, cgGetNamedUserType(teapotProg, "Light"), numLights);
    for (int i = 0; i < numLights; i++) {
        // We pass teapotProg here, but could pass any program (compiled or uncompiled) whose
        // source defines the types.
        lights[i].handle = createLightSource(teapotProg, lights[i]);
        if (lights[i].handle == 0)
            exit(2);
        cgConnectParameter(lights[i].handle, cgGetArrayParameter(lArray, i));
    }

    // Connect the light source array to each material.
    cgConnectParameter(lArray, cgGetNamedProgramParameter(teapotProg, CG_PROGRAM, "lights"));
    cgConnectParameter(lArray, cgGetNamedProgramParameter(groundProg, CG_PROGRAM, "lights"));

    // Set the 'enabled' member variable of each light source
    toggleLight(0); toggleLight(1);

    // Finally, compile and load the programs
    cgCompileProgram(teapotProg);
    printProgram(teapotProg);
    cgGLLoadProgram(teapotProg);
    cgCompileProgram(groundProg);
    printProgram(groundProg);
    cgGLLoadProgram(groundProg);
}

static void motion(int x, int y)
{
    PolarCoord *p = &motionSelections[currentSelection]->from;
    if (moveMode) {
        float dtheta = (float)(mouseX - x)*TWOPI/(float)screenWidth;
        float dphi = (float)(y - mouseY)*TWOPI/(float)screenHeight;
        if (p == &Eye.from) {
            p->theta -= dtheta;
            p->phi   -= dphi;
        } else {
            p->theta += dtheta;
            p->phi   += dphi;
        }
        if (p->theta < -TWOPI) p->theta += TWOPI;
        else if (p->theta >  TWOPI) p->theta -= TWOPI;
        if (p->phi > M_PI) p->phi = M_PI;
        else if (p->phi < 0.) p->phi = 0.;
    }
    if (zoomMode) {
        p->r += 0.25*(y-mouseY);
        if (p->r < 0.) p->r = 0.;
    }
    if (retargetMode) {
        float *t = motionSelections[currentSelection]->target;
        if (t == Eye.target) {
            t[0] -= 0.1*(mouseX - x);
            t[1] += 0.1*(mouseY - y);
        } else {
            t[0] += 0.1*(mouseX - x);
            t[1] -= 0.1*(mouseY - y);
        }
    }
    mouseX = x;
    mouseY = y;
    glutPostRedisplay();
}

static void mouse(int button, int state, int x, int y)
{
    bool release = (state == GLUT_UP);

    switch (button) {
    case GLUT_LEFT_BUTTON:
        if (release) {
            moveMode = zoomMode = retargetMode = false;
        } else {
            int modif = glutGetModifiers();
            if (modif & GLUT_ACTIVE_SHIFT) {
                zoomMode = true;
            } else if (modif & GLUT_ACTIVE_CTRL) {
                retargetMode = true;
            } else {
                moveMode = true;
            }
            mouseX = x;
            mouseY = y;
            motion(x, y);
        }
        glutPostRedisplay();
        break;
    }
}

static void reshape(int x, int y)
{
    glViewport(0,0,x,y);
    screenWidth = x;
    screenHeight = y;
}

static void keyboard(unsigned char key, int x, int y)
{
    switch (key) {
    case 'q':
    case 'Q':
    case 27:
        cgDestroyContext(context);
        exit(0);
    case 'r':
    case 'R':
        motionSelections[currentSelection]->rotateMode = !
            motionSelections[currentSelection]->rotateMode;
        if (motionSelections[currentSelection]->rotateMode)
            numRotating++;
        else
            numRotating--;
        if (numRotating)
            glutIdleFunc(idle);
        else
            glutIdleFunc(0);
        glutPostRedisplay();
        break;
    case '1':
        toggleLight(0);
        glutPostRedisplay();
        break;
    case '2':
        toggleLight(1);
        glutPostRedisplay();
        break;
    case 'l':
    case 'L':
        drawLightsMode = !drawLightsMode;
        glutPostRedisplay();
        break;
    case ' ':
        // Cycle through motion selection
        currentSelection = (++currentSelection)%numSelections;
        glutPostRedisplay();
        break;
    case 'c':
    case 'C':
        // recenter current motion target
        motionSelections[currentSelection]->target[0] =
        motionSelections[currentSelection]->target[1] =
        motionSelections[currentSelection]->target[2] = 0.;
        glutPostRedisplay();
    }

}

static void handleCgError() 
{
    CGerror err = cgGetError();
    fprintf(stderr, "Cg error: %s\n", cgGetErrorString(err));
    if (err == 1)
        fprintf(stderr, "last listing:\n%s\n", cgGetLastListing(context));
    fflush(stderr);
    exit(2);
}
