#include "voxeldata.h"
#include <qdatastream.h>
#include <QPixmap>
#include <math.h>


bool cosTableInited = false;
int cosTable[720];
int *VoxelData::cosT = &(cosTable[0]);
int *VoxelData::sinT = &(cosTable[90]); // sin(x) is basically same as cos(x + 90) (in degrees)
double sqrt2;               // square root of 2
const int scale = 65536/2;  // fixed point maths constants
const int scaleBits = 15;


// Windows math.h header lacks M_PI definition, it's available almost everywhere else as a #define
#ifndef M_PI
#define M_PI 3.14159265358979323846
#endif


VoxelData::VoxelData( int w, int h, int d )
{
    vw = w;
    vh = h;
    vd = d;
    init();
    if ( !cosTableInited ) { 
        cosTableInited = true;
        for ( int i = 0; i < 720; i++ ) 
            cosT[i] = int(scale*cos(i*M_PI/180.0));
        sqrt2 = sqrt(2);
    }
}


VoxelData::~VoxelData()
{
    for ( int z = 0; z < vd; z++ ) {
        for ( int x = 0; x < vw; x++ ) {
            delete [] (layers[z])[x];
        }
        delete [] layers[z];
    }
}


QRgb **VoxelData::layer( int z )
{
    return layers[z];
}


void VoxelData::init()
{
    layers.resize(vd);
    for ( int z = 0; z < vd; z++ ) {
        layers[z] = new QRgb * [vh];
        for ( int x = 0; x < vw; x++ ) {
            (layers[z])[x] = new QRgb [vh];
            memset( layer(z)[x], 0, sizeof(QRgb)*vh );
        }
    }
}


// clamp a value to between 0 and 255
// probably better if clamp doesn't need to be used as branching code doesn't perform
// well on some processors
#define clamp(x) \
                        ((x) > 255) ? 255 : ((x) < 0) ? 0 : (x)

// Idea for a clamp macro that doesn't use branching code (requires some temporary variables
// which may use more registers on some CPUs)
#define fastClampIdea(x) \
                        b = 0 - ((x & ~0xFF) == 0); \
                        y = (x & b) | (~b & 0xFF);


QPixmap VoxelData::render( int size, int rx, int ry, int rz )
{
    // Use a fixed width and height near to max possible for different rotations
    int imgSize = int((vw + vh + vd) / sqrt2);
    int dimension = imgSize << size; // size used as a power of 2 scale factor

    QImage img(dimension, dimension, 32);
    uint **imgData = (uint**)img.jumpTable();

    // Use DDA algorithm to trace a ray through the voxel data
    int sx = sinT[rx], cx = cosT[rx];
    int sy = sinT[ry], cy = cosT[ry];
    int sz = sinT[rz], cz = cosT[rz];

    // s and c -> sin and cos
    // d -> delta
    // u, v and w are voxel data coordinates
    int sysx = (sy*sx)>>scaleBits;
    int cxsz = (cx*sz)>>scaleBits;
    int cxcz = (cx*cz)>>scaleBits;

    int du0 = cxcz-((sysx*sz)>>scaleBits);
    int du1 = (cy*sz)>>scaleBits;
    int du2 = (-cz*sx-sy*cxsz)>>scaleBits;

    int dv0 = -cxsz-((sysx*cz)>>scaleBits);
    int dv1 = (cz*cy)>>scaleBits;
    int dv2 = (sz*sx-sy*cxcz)>>scaleBits;

    int dw0 = (sx*cy)>>scaleBits;
    int dw1 = sy;
    int dw2 = (cx*cy)>>scaleBits;

    // voxel coordinate deltas for each change in y of image coordinate system
    du0 >>= size;
    dv0 >>= size;
    dw0 >>= size;

    // voxel coordinate deltas for each change in x of image coordinate system
    du1 >>= size;
    dv1 >>= size;
    dw1 >>= size;

    // voxel coordinate deltas for each change in z of image coordinate system
    du2 >>= size;
    dv2 >>= size;
    dw2 >>= size;

    // voxel coordinates when x, y and z are zero
    int u0 = (vw*scale - dimension*(du0+du1+du2))/2;
    int v0 = (vh*scale - dimension*(dv0+dv1+dv2))/2;
    int w0 = (vd*scale - dimension*(dw0+dw1+dw2))/2;

    // x, y and z are in the coordinate system of the output image
    for ( int y = 0; y < dimension; y++ ) {
        uint *imgRow = imgData[y];
        int u1 = u0;
        int v1 = v0;
        int w1 = w0;
        for ( int x = 0; x < dimension; x++ ) {
            int u2 = u1;
            int v2 = v1;
            int w2 = w1;
            bool foundVoxel = false;
            bool haveProbedInsideModel = false; // true when u, v and w have been in range 

            for ( int z = 0; z < dimension; z++ ) {
                int u = u2 >> scaleBits;
                int v = v2 >> scaleBits;
                int w = w2 >> scaleBits;
                // ensure our u, v and w array indexes are within range
                if ( u >= 0 && u < vw && v >= 0 && v < vh && w >= 0 && w < vd ) {
                    haveProbedInsideModel = true; 
                    QRgb pix = layer(w)[u][v];
                    if ( pix ) {
                        // cheap lighting based on distance from one of the corners
                        double light = (x + (dimension - y) + z) / (double)dimension - 1.0;
                        int r = int(light * qRed(pix));
                        int g = int(light * qGreen(pix));
                        int b = int(light * qBlue(pix));
                        // keep the colour components in the range of 0-255 (after applying lighting)
                        //*imgRow = qRgb(clamp(r), clamp(g), clamp(b));
                        *imgRow = qRgb(r, g, b);
                        foundVoxel = true;
                        break; // leave the z for-loop
                    }
                } else if ( haveProbedInsideModel ) {
                    // u, v and w were in range and now are not. Means that we have now stepped
                    // through to the other side of the model as projected on to the image
                    // coordinates and as we keep stepping through we won't find anything else.
                    break; // leave the z for-loop
                } else {
                    // Skip ahead with faster loops to more quickly get u, v and w within range
                    // probably with some algerbra might be able to work it out
                    do {
                        u2 += du2;
                        v2 += dv2;
                        w2 += dw2;
                        u = u2 >> scaleBits;
                        z++;
                    } while ( ( u < 0 || u > vw ) && z < dimension );
                    do {
                        u2 += du2;
                        v2 += dv2;
                        w2 += dw2;
                        v = v2 >> scaleBits;
                        z++;
                    } while ( ( v < 0 || v > vh ) && z < dimension );
                    do {
                        u2 += du2;
                        v2 += dv2;
                        w2 += dw2;
                        w = w2 >> scaleBits;
                        z++;
                    } while ( ( w < 0 || w > vd ) && z < dimension );
                }
                u2 += du2;
                v2 += dv2;
                w2 += dw2;
            }
            // if we don't find a voxel, paint it black
            if (!foundVoxel) 
                *imgRow = qRgb( 0, 0, 0 );
            imgRow++;
            u1 += du1;
            v1 += dv1;
            w1 += dw1;
        }
        u0 += du0;
        v0 += dv0;
        w0 += dw0;
    }

    QPixmap pix;
    pix.convertFromImage( img );    
    return pix;
}


void VoxelData::write( QDataStream &ds )
{
    ds << vw;
    ds << vh;
    ds << vd;
    for ( int z = 0; z < vd; z++ ) {
        QRgb **l = layer(z);
        for ( int y = 0; y < vh; y++ ) {
            for ( int x = 0; x < vw; x+=8 ) {

                // Send a byte mask of the next 8 colors
                unsigned short v = 0;
                for ( int i = 0; i < 8 && x+i<vw; i++, v <<= 1 ) 
                    if ( l[x+i][y] ) 
                        v |= 0x1;
                v >>= 1;
                unsigned char v2 = (unsigned char)v;
                ds << v2;
                for ( int i = 0; i < 8 && x+i<vw; i++ ) 
                    if ( l[x+i][y] ) 
                        ds << l[x+i][y];

            }
        }
    }
}


void VoxelData::read( QDataStream &ds )
{
    ds >> vw;
    ds >> vh;
    ds >> vd;
    init();

    for ( int z = 0; z < vd; z++ ) {
        QRgb **l = layer(z);
        for ( int y = 0; y < vh; y++ ) {
            for ( int x = 0; x < vw; x+=8 ) {

                // Read a byte mask of the next 8 colors
                unsigned char v = 0xFF;
                ds >> v;
                for ( int i = 0; i < 8 && x+i<vw; i++, v <<= 1 ) 
                    if ( v & 0x80 )
                        ds >> l[x+i][y];
                    else
                        l[x+i][y] = 0;

            }
        }
    }
}


QRgb VoxelData::colorOf( int x, int y, int z, int rx, int ry, int rz )
{
    QRgb rgb = 0;

    if ( layer(z)[x][y] ) {
        double cosz = cos(-rz*M_PI/180.0);
        double sinz = sin(-rz*M_PI/180.0);
        double cosy = cos(-ry*M_PI/180.0);
        double siny = sin(-ry*M_PI/180.0);
        double cosx = cos(-rx*M_PI/180.0);
        double sinx = sin(-rx*M_PI/180.0);

        int tx = x-vw/2;
        int ty = y-vh/2;
        int tz = z;

        // y
        int vx = int(tx*cosy + z*siny);
        int vz = int(tx*siny + z*cosy);
        tx = vx;

        // z
        vx = int(tx*cosz - ty*sinz);
        int vy = int(tx*sinz + ty*cosz);
        ty = vy;
        tz = vz;

        // x
        vy = int(ty*cosx - tz*sinx);
        vz = int(ty*sinx + tz*cosx);

        vx += vw/2;
        vy += vh/2;

        rgb = layer(z)[x][y];
        int r = qRed(rgb);
        int g = qGreen(rgb);
        int b = qBlue(rgb);
        r -= r*vy/vh/2;
        g -= g*vy/vh/2;
        b -= b*vy/vh/2;

        rgb = qRgb( QMAX(r,0), QMAX(g,0), QMAX(b,0) );
    }

    return rgb;
}

