#include "ProceduralTools.h"

#include "Ogre.h"

using namespace Ogre;

//Constants (copied as is from sample)

// Grid sizes (in vertices)
#define X_SIZE_LOG2             6
#define Y_SIZE_LOG2             6
#define Z_SIZE_LOG2             6
#define TOTAL_POINTS    (1<<(X_SIZE_LOG2 + Y_SIZE_LOG2 + Z_SIZE_LOG2))
#define CELLS_COUNT     (((1<<X_SIZE_LOG2) - 1) * ((1<<Y_SIZE_LOG2) - 1) * ((1<<Z_SIZE_LOG2) - 1))

#define SWIZZLE 1

#define MAKE_INDEX(x, y, z, sizeLog2)   (int)((x) | ((y) << sizeLog2[0]) | ((z) << (sizeLog2[0] + sizeLog2[1])))

//--------------------------------------------------------------------------------------
// Fills pPos with x, y, z de-swizzled from index with bitsizes in sizeLog2
//
//  Traversing the grid in a swizzled fashion improves locality of reference,
// and this is very beneficial when sampling a texture.
//--------------------------------------------------------------------------------------
static void UnSwizzle(Ogre::uint index, Ogre::uint sizeLog2[3], Ogre::uint * pPos)
{

    // force index traversal to occur in 2x2x2 blocks by giving each of x, y, and z one
    // of the bottom 3 bits
    pPos[0] = index & 1;
    index >>= 1;
    pPos[1] = index & 1;
    index >>= 1;
    pPos[2] = index & 1;
    index >>= 1;

    // Treat the rest of the index like a row, column, depth array
    // Each dimension needs to grab sizeLog2 - 1 bits
    // This will make the blocks traverse the grid in a raster style order
    index <<= 1;
    pPos[0] = pPos[0] | (index &  ( (1 << sizeLog2[0]) - 2));
    index >>=  sizeLog2[0] - 1;
    pPos[1] = pPos[1] | ( index &  ( (1 << sizeLog2[1]) - 2));
    index >>= sizeLog2[1] - 1;
    pPos[2] = pPos[2] | ( index &  ( (1 << sizeLog2[2]) - 2));
}


MeshPtr ProceduralTools::generateTetrahedra()
{
    MeshPtr tetrahedraMesh = Ogre::MeshManager::getSingleton().createManual
        ("TetrahedraMesh", Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME);

    SubMesh* tetrahedraSubMesh = tetrahedraMesh->createSubMesh();
    tetrahedraSubMesh->operationType = RenderOperation::OT_LINE_LIST_ADJ;
    //tetrahedraSubMesh->operationType = RenderOperation::OT_TRIANGLE_STRIP;
    tetrahedraSubMesh->setMaterialName("Ogre/Isosurf/TessellateTetrahedra");
    //tetrahedraSubMesh->setMaterialName("BaseWhiteNoLighting");
    
    Ogre::uint sizeLog2[3] = { X_SIZE_LOG2, Y_SIZE_LOG2, Z_SIZE_LOG2 };
    Ogre::uint nTotalBits = sizeLog2[0] + sizeLog2[1] + sizeLog2[2];
    Ogre::uint nPointsTotal = 1 << nTotalBits;

    tetrahedraSubMesh->useSharedVertices = false;
    tetrahedraSubMesh->vertexData = new VertexData;
    tetrahedraSubMesh->indexData = new IndexData;

    tetrahedraSubMesh->vertexData->vertexDeclaration->addElement(0, 0, 
                                                                 VET_FLOAT4, VES_POSITION);

    HardwareVertexBufferSharedPtr vertexBuffer = HardwareBufferManager::getSingleton().createVertexBuffer(
        tetrahedraSubMesh->vertexData->vertexDeclaration->getVertexSize(0), 
        nPointsTotal, 
        HardwareBuffer::HBU_STATIC_WRITE_ONLY);

    HardwareIndexBufferSharedPtr indexBuffer = HardwareBufferManager::getSingleton().createIndexBuffer(
        HardwareIndexBuffer::IT_32BIT, 
        CELLS_COUNT * sizeof(Ogre::uint) * 24, 
        HardwareBuffer::HBU_STATIC_WRITE_ONLY);
        
    tetrahedraSubMesh->vertexData->vertexBufferBinding->setBinding(0, vertexBuffer);
    tetrahedraSubMesh->vertexData->vertexCount = nPointsTotal;
    tetrahedraSubMesh->vertexData->vertexStart = 0;
        
    tetrahedraSubMesh->indexData->indexBuffer = indexBuffer;
        
    float* positions = static_cast<float*>(vertexBuffer->lock(HardwareBuffer::HBL_DISCARD));
        
    //Generate positions
    for (Ogre::uint i = 0; i < nPointsTotal; i++) 
    {
        Ogre::uint pos[3];
        pos[0] = i & ((1<<X_SIZE_LOG2)-1);
        pos[1] = (i >> X_SIZE_LOG2) & ((1<<Y_SIZE_LOG2)-1);
        pos[2] = (i >> (X_SIZE_LOG2+Y_SIZE_LOG2)) & ((1<<Z_SIZE_LOG2)-1);

        *positions++ = (float(pos[0]) / float(1<<X_SIZE_LOG2))*2.0f-1.0f;
        *positions++ = (float(pos[1]) / float(1<<Y_SIZE_LOG2))*2.0f-1.0f;
        *positions++ = (float(pos[2]) / float(1<<Z_SIZE_LOG2))*2.0f-1.0f;
        *positions++ = 1.0f;
    }
    vertexBuffer->unlock();
        
    Ogre::uint numIndices = 0;

    // Generate indices
    Ogre::uint32* indices = static_cast<Ogre::uint32*>(indexBuffer->lock(HardwareBuffer::HBL_DISCARD));

    for (Ogre::uint i = 0; i < nPointsTotal; i++) 
    {
        Ogre::uint pos[3];
#if SWIZZLE
        UnSwizzle(i, sizeLog2, pos);    // un-swizzle current index to get x, y, z for the current sampling point
#else
        pos[0] = i & ((1<<X_SIZE_LOG2)-1);
        pos[1] = (i >> X_SIZE_LOG2) & ((1<<Y_SIZE_LOG2)-1);
        pos[2] = (i >> (X_SIZE_LOG2+Y_SIZE_LOG2)) & ((1<<Z_SIZE_LOG2)-1);
#endif
        if ((int)pos[0] == (1 << sizeLog2[0]) - 1 || (int)pos[1] == (1 << sizeLog2[1]) - 1 || (int)pos[2] == (1 << sizeLog2[2]) - 1)
            continue;       // skip extra cells

        numIndices += 24; // Got to this point, adding 24 indices

        // NOTE: order of vertices matters! important for backface culling

        // T0
        *indices++ = MAKE_INDEX(pos[0] + 1, pos[1], pos[2], sizeLog2);
        *indices++ = MAKE_INDEX(pos[0], pos[1], pos[2], sizeLog2);
        *indices++ = MAKE_INDEX(pos[0] + 1, pos[1] + 1, pos[2], sizeLog2);
        *indices++ = MAKE_INDEX(pos[0] + 1, pos[1] + 1, pos[2] + 1, sizeLog2);

        // T1
        *indices++ = MAKE_INDEX(pos[0] + 1, pos[1] + 1, pos[2] + 1, sizeLog2);
        *indices++ = MAKE_INDEX(pos[0], pos[1], pos[2], sizeLog2);
        *indices++ = MAKE_INDEX(pos[0] + 1, pos[1] + 1, pos[2], sizeLog2);
        *indices++ = MAKE_INDEX(pos[0], pos[1] + 1, pos[2], sizeLog2);

        // T2
        *indices++ = MAKE_INDEX(pos[0], pos[1] + 1, pos[2], sizeLog2);
        *indices++ = MAKE_INDEX(pos[0], pos[1], pos[2], sizeLog2);
        *indices++ = MAKE_INDEX(pos[0], pos[1] + 1, pos[2] + 1, sizeLog2);
        *indices++ = MAKE_INDEX(pos[0] + 1, pos[1] + 1, pos[2] + 1, sizeLog2);

        // T3
        *indices++ = MAKE_INDEX(pos[0], pos[1], pos[2], sizeLog2);
        *indices++ = MAKE_INDEX(pos[0], pos[1], pos[2] + 1, sizeLog2);
        *indices++ = MAKE_INDEX(pos[0], pos[1] + 1, pos[2] + 1, sizeLog2);
        *indices++ = MAKE_INDEX(pos[0] + 1, pos[1] + 1, pos[2] + 1, sizeLog2);

        // T4
        *indices++ = MAKE_INDEX(pos[0], pos[1], pos[2] + 1, sizeLog2);
        *indices++ = MAKE_INDEX(pos[0], pos[1], pos[2], sizeLog2);
        *indices++ = MAKE_INDEX(pos[0] + 1, pos[1], pos[2] + 1, sizeLog2);
        *indices++ = MAKE_INDEX(pos[0] + 1, pos[1] + 1, pos[2] + 1, sizeLog2);

        // T5
        *indices++ = MAKE_INDEX(pos[0], pos[1], pos[2], sizeLog2);
        *indices++ = MAKE_INDEX(pos[0] + 1, pos[1], pos[2], sizeLog2);
        *indices++ = MAKE_INDEX(pos[0] + 1, pos[1], pos[2] + 1, sizeLog2);
        *indices++ = MAKE_INDEX(pos[0] + 1, pos[1] + 1, pos[2] + 1, sizeLog2);
    }
        
    indexBuffer->unlock();

    tetrahedraSubMesh->indexData->indexCount = numIndices;
    tetrahedraSubMesh->indexData->indexStart = 0;

    AxisAlignedBox meshBounds;
    meshBounds.setMinimum(-1,-1,-1);
    meshBounds.setMaximum(1,1,1);
    tetrahedraMesh->_setBounds(meshBounds);
    tetrahedraMesh->_setBoundingSphereRadius(2);

    return tetrahedraMesh;
}
