#include "glvoxelview.h"


// OpenGL rendered voxel view
GLVoxelView::GLVoxelView( QWidget *parent, const char *name )
    : QGLWidget( parent, name )
{
    object = 0;
    wire_object = 0;
    point_object = 0;
    voxelData = 0;
    currentLayer = 0;
    xAxisAngle = yAxisAngle = zAxisAngle = 0;
}


void GLVoxelView::setVoxelModel( VoxelData *data )
{
    voxelData = data;
    glMatrixMode( GL_PROJECTION );
    glLoadIdentity();
    glOrtho(-voxelData->width(), voxelData->height(), -voxelData->depth(),
	    voxelData->width(), -voxelData->height(), voxelData->depth());
    glMatrixMode( GL_MODELVIEW );
    updateGL();
}


void GLVoxelView::setViewAngles( int xAngle, int yAngle, int zAngle )
{
    xAxisAngle = xAngle;
    yAxisAngle = yAngle;
    zAxisAngle = zAngle;
    updateGL();
}


void GLVoxelView::setCurrentLayer( int layer )
{
    currentLayer = layer;
    updateGL();
}


void GLVoxelView::initializeGL()
{
    qglClearColor( Qt::black );

    glShadeModel( GL_FLAT );
    glEnable( GL_CULL_FACE );
    glEnable(GL_DEPTH_TEST);

    glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);

    GLfloat specular [] = { 1.0, 1.0, 1.0, 1.0 };
    GLfloat shininess [] = { 90.0 };
    glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, specular);
    glMaterialfv(GL_FRONT_AND_BACK, GL_SHININESS, shininess);

    glColorMaterial(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE);
    glEnable(GL_COLOR_MATERIAL);

    GLfloat position [] = { 0.0, 1.0, -1.0, 0.0 };
    glLightfv(GL_LIGHT0, GL_POSITION, position);
    glEnable(GL_LIGHTING);
    glEnable(GL_LIGHT0);

    object = makeObject();
    wire_object = makeWireObject();
    point_object = makePointObject();
}


void GLVoxelView::paintGL()
{
    GLfloat scale = 1.0;
    GLfloat xRot = xAxisAngle+90;
    GLfloat yRot = yAxisAngle;
    GLfloat zRot = zAxisAngle;

    glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );

    glMatrixMode( GL_MODELVIEW );
    glLoadIdentity();

    glRotatef( xRot, 1.0, 0.0, 0.0 );
    glRotatef( yRot, 0.0, 1.0, 0.0 );
    glRotatef( zRot, 0.0, 0.0, 1.0 );
    glTranslatef( -voxelData->width()/2, -voxelData->height()/2, -voxelData->depth()/2 );
    glScalef( scale, scale, scale );

    qglColor( Qt::white );

    glLineWidth( 2.0 );

    glBegin( GL_LINES );
    glVertex3f( 0, 0, 0 ); glVertex3f(voxelData->width(), 0, 0 );
    glVertex3f( 0, 0, 0 ); glVertex3f(0, voxelData->height(), 0 );
    glVertex3f( 0, 0, 0 ); glVertex3f(0, 0, voxelData->depth() );
    glEnd();

    if ( !voxelData )
	    return;

    GLfloat trans [] = { 0.8, 0.8, 0.8, 0.1 };

    glTranslatef( 0, voxelData->height(), 0 );

    glTranslatef( 0, 0, voxelData->depth() );
    for ( int z = voxelData->depth()-1; z >= 0; z-- ) {
        QRgb **layer = voxelData->layer(z);
        if ( z < currentLayer ) {
            glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, trans);
            glEnable(GL_BLEND);
            glDepthMask(GL_FALSE);
            glBlendFunc(GL_SRC_ALPHA, GL_ONE);//_MINUS_SRC_ALPHA);
        }
        for ( int x = 0; x < voxelData->width(); x++ ) {
            for ( int y = 0; y < voxelData->height(); y++ ) {
                if ( layer[x][y] ) {
                    QRgb rgb = voxelData->colorOf( x, y, z, int(xRot-90), int(yRot), int(zRot) );
                    glColor4f( qRed(rgb)/255.0, qGreen(rgb)/255.0, qBlue(rgb)/255.0, 0.15 );
                    glCallList(object);
                    if (z == currentLayer) {
                        glEnable(GL_BLEND);
                        glEnable(GL_LINE_SMOOTH);
                        glLineWidth(1.5);
                        glHint(GL_LINE_SMOOTH_HINT, GL_NICEST);
                        glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
                        glColor3f(0.0, 0.0, 0.0);

                        glCallList(wire_object);
                        glDisable(GL_LINE_SMOOTH);
                        glDisable(GL_BLEND);
                    }
                } else if (z == currentLayer) {
                    glEnable(GL_BLEND);
                    glHint(GL_LINE_SMOOTH_HINT, GL_NICEST);
                    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
                    glColor4f(1.0, 1.0, 1.0, 0.5);
                    glCallList(point_object);
                    glDisable(GL_BLEND);
                }

                glTranslatef( 0, -1, 0 );
            }
            glTranslatef( 0, voxelData->height(), 0 );
            glTranslatef( 1, 0, 0 );
        }
        glTranslatef( voxelData->width() * -1, 0, 0 );
        glTranslatef( 0, 0, -1 );
    }
    glDepthMask(GL_TRUE);
    glDisable(GL_BLEND);
}


void GLVoxelView::resizeGL( int w, int h )
{
    glViewport( 0, 0, (GLint)QMIN(w, h), (GLint)QMIN(w, h) );
    glMatrixMode( GL_PROJECTION );
    glLoadIdentity();
    glOrtho(-voxelData->width(), voxelData->height(), -voxelData->depth(),
	    voxelData->width(), -voxelData->height(), voxelData->depth());
    glMatrixMode( GL_MODELVIEW );
}


GLuint GLVoxelView::makePointObject()
{
    GLuint list;

    list = glGenLists( 1 );

    glNewList( list, GL_COMPILE );

    glBegin(GL_POINTS);

    // top
    glVertex3f(0, 0, -0.01);
    glVertex3f(0, 1, -0.01);
    glVertex3f(1, 1, -0.01);
    glVertex3f(1, 0, -0.01);

    glEnd();

    glEndList();

    return list;
}


GLuint GLVoxelView::makeWireObject()
{
    GLuint list;

    list = glGenLists( 1 );

    glNewList( list, GL_COMPILE );

    glBegin(GL_LINE_STRIP);

    // top
    glVertex3f(0, 0, -0.01);
    glVertex3f(0, 1, -0.01);
    glVertex3f(1, 1, -0.01);
    glVertex3f(1, 0, 0.01);
    glVertex3f(0, 0, 0.01);

    glEnd();

    glEndList();

    return list;
}


GLuint GLVoxelView::makeObject()
{
    GLuint list;

    list = glGenLists( 1 );

    glNewList( list, GL_COMPILE );

    glBegin(GL_QUADS);

    // front
    glNormal3f(0, 1, 0);
    glVertex3f(0, 0, 0);
    glVertex3f(1, 0, 0);
    glVertex3f(1, 0, 1);
    glVertex3f(0, 0, 1);

    // top
    glNormal3f(0, 0, -1);
    glVertex3f(0, 0, 0);
    glVertex3f(0, 1, 0);
    glVertex3f(1, 1, 0);
    glVertex3f(1, 0, 0);

    // right
    glNormal3f(-1, 0, 0);
    glVertex3f(1, 0, 0);
    glVertex3f(1, 1, 0);
    glVertex3f(1, 1, 1);
    glVertex3f(1, 0, 1);

    // back
    glNormal3f(0, -1, 0);
    glVertex3f(1, 1, 0);
    glVertex3f(0, 1, 0);
    glVertex3f(0, 1, 1);
    glVertex3f(1, 1, 1);

    // left
    glNormal3f(1, 0, 0);
    glVertex3f(0, 0, 0);
    glVertex3f(0, 0, 1);
    glVertex3f(0, 1, 1);
    glVertex3f(0, 1, 0);

    // bottom
    glNormal3f(0, 0, -1);
    glVertex3f(0, 0, 1);
    glVertex3f(1, 0, 1);
    glVertex3f(1, 1, 1);
    glVertex3f(0, 1, 1);

    glEnd();

    glEndList();

    return list;
}


