// Wild Magic Source Code
// David Eberly
// http://www.geometrictools.com
// Copyright (c) 1998-2007
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation; either version 2.1 of the License, or (at
// your option) any later version.  The license is available for reading at
// either of the locations:
//     http://www.gnu.org/copyleft/lgpl.html
//     http://www.geometrictools.com/License/WildMagicLicense.pdf
//
// Version: 4.0.0 (2006/06/28)

#include "WUnimaterialMesh.h"
#include "Wm4ColorRGBArray.h"
#include "Wm4Vector2Array.h"
using namespace Wm4;

#include <algorithm>
#include <vector>

//----------------------------------------------------------------------------
WUnimaterialMesh::WUnimaterialMesh ()
{
    m_iVQuantity = 0;
    m_akVertex = 0;
    m_akNormal = 0;
    m_iCQuantity = 0;
    m_akColor = 0;
    m_iTQuantity = 0;
    m_akTexture = 0;
    m_iFQuantity = 0;
    m_aiFace = 0;
    m_aiCFace = 0;
    m_aiTFace = 0;
}
//----------------------------------------------------------------------------
WUnimaterialMesh::~WUnimaterialMesh ()
{
    WM4_DELETE[] m_akVertex;
    WM4_DELETE[] m_akNormal;
    WM4_DELETE[] m_akColor;
    WM4_DELETE[] m_akTexture;
    WM4_DELETE[] m_aiFace;
    WM4_DELETE[] m_aiCFace;
    WM4_DELETE[] m_aiTFace;
}
//----------------------------------------------------------------------------
int& WUnimaterialMesh::VQuantity ()
{
    return m_iVQuantity;
}
//----------------------------------------------------------------------------
Wm4::Vector3f*& WUnimaterialMesh::Vertex ()
{
    return m_akVertex;
}
//----------------------------------------------------------------------------
Wm4::Vector3f*& WUnimaterialMesh::Normal ()
{
    return m_akNormal;
}
//----------------------------------------------------------------------------
int& WUnimaterialMesh::CQuantity ()
{
    return m_iCQuantity;
}
//----------------------------------------------------------------------------
Wm4::ColorRGB*& WUnimaterialMesh::Color ()
{
    return m_akColor;
}
//----------------------------------------------------------------------------
int& WUnimaterialMesh::TQuantity ()
{
    return m_iTQuantity;
}
//----------------------------------------------------------------------------
Wm4::Vector2f*& WUnimaterialMesh::Texture ()
{
    return m_akTexture;
}
//----------------------------------------------------------------------------
int& WUnimaterialMesh::FQuantity ()
{
    return m_iFQuantity;
}
//----------------------------------------------------------------------------
int*& WUnimaterialMesh::Face ()
{
    return m_aiFace;
}
//----------------------------------------------------------------------------
int*& WUnimaterialMesh::CFace ()
{
    return m_aiCFace;
}
//----------------------------------------------------------------------------
int*& WUnimaterialMesh::TFace ()
{
    return m_aiTFace;
}
//----------------------------------------------------------------------------
Wm4::MaterialStatePtr& WUnimaterialMesh::MState()
{
    return m_spkMState;
}
//----------------------------------------------------------------------------
Wm4::TextureEffectPtr& WUnimaterialMesh::TState()
{
    return m_spkTState;
}
//----------------------------------------------------------------------------
void WUnimaterialMesh::DuplicateGeometry ()
{
    // build array of vertex+attribute objects
    std::vector<VertexAttr>* akVArray =
        WM4_NEW std::vector<VertexAttr>[m_iVQuantity];
    int i;
    for (i = 0; i < 3*m_iFQuantity; i++)
    {
        VertexAttr kAttr;

        kAttr.V = m_aiFace[i];

        if (m_iCQuantity > 0)
        {
            kAttr.C = m_aiCFace[i];
        }

        if (m_iTQuantity > 0)
        {
            kAttr.T = m_aiTFace[i];
        }

        akVArray[m_aiFace[i]].push_back(kAttr);
    }

    // compute total number of vertices needed
    int iNewVQuantity = 0;
    for (i = 0; i < m_iVQuantity; i++)
    {
        // remove duplicate uvw indices
        sort(akVArray[i].begin(),akVArray[i].end());
        std::vector<VertexAttr>::iterator pkEnd = unique(akVArray[i].begin(),
            akVArray[i].end());
        akVArray[i].erase(pkEnd,akVArray[i].end());
        iNewVQuantity += akVArray[i].size();
    }

    // Wild Magic geometry data
    Vector3f* akNewVertex = WM4_NEW Vector3f[iNewVQuantity];
    Vector3f* akNewNormal = WM4_NEW Vector3f[iNewVQuantity];

    Wm4::ColorRGB* akNewColor = 0;
    if (m_iCQuantity > 0)
    {
        akNewColor = WM4_NEW Wm4::ColorRGB[iNewVQuantity];
    }

    Vector2f* akNewTexture = 0;
    if (m_iTQuantity > 0)
    {
        akNewTexture = WM4_NEW Vector2f[iNewVQuantity];
    }

    int j, k;
    for (i = 0, k = 0; i < m_iVQuantity; i++)
    {
        std::vector<VertexAttr>& rkVArray = akVArray[i];
        int iSize = rkVArray.size();
        for (j = 0; j < iSize; j++, k++)
        {
            akNewVertex[k] = m_akVertex[i];
            akNewNormal[k] = m_akNormal[i];

            VertexAttr kAttr = rkVArray[j];

            if (akNewColor)
            {
                akNewColor[k] = m_akColor[kAttr.C];
            }

            if (akNewTexture)
            {
                akNewTexture[k] = m_akTexture[kAttr.T];
            }

            // save duplicated attributes for later use
            kAttr.V = k;
            rkVArray.push_back(kAttr);
        }
    }

    // modify the connectivity array to reflect the duplication of vertices
    for (i = 0; i < m_iFQuantity; i++)
    {
        int iThreeI = 3*i;
        int* aiVIndex = m_aiFace+iThreeI;
        int* aiCIndex = (m_iCQuantity > 0 ? m_aiCFace+iThreeI : 0);
        int* aiTIndex = (m_iTQuantity > 0 ? m_aiTFace+iThreeI : 0);
        for (j = 0; j < 3; j++)
        {
            VertexAttr kAttr;
            kAttr.V = aiVIndex[j];

            if (aiCIndex)
            {
                kAttr.C = aiCIndex[j];
            }

            if ( aiTIndex )
            {
                kAttr.T = aiTIndex[j];
            }

            // VArray has N original vertices and N duplicates
            std::vector<VertexAttr>& rkVArray = akVArray[aiVIndex[j]];
            int iHalfSize = rkVArray.size()/2;
            for (k = 0; k < iHalfSize; k++)
            {
                if (rkVArray[k] == kAttr)
                {
                    // found correct (vert,color,uv) pair, update vert index
                    aiVIndex[j] = rkVArray[iHalfSize+k].V;
                    break;
                }
            }
        }
    }

    WM4_DELETE[] m_akVertex;
    WM4_DELETE[] m_akNormal;
    WM4_DELETE[] m_akColor;
    WM4_DELETE[] m_akTexture;

    m_iVQuantity = iNewVQuantity;
    m_akVertex = akNewVertex;
    m_akNormal = akNewNormal;
    m_akColor = akNewColor;
    m_akTexture = akNewTexture;

    WM4_DELETE[] akVArray;
}
//----------------------------------------------------------------------------
TriMesh* WUnimaterialMesh::ToTriMesh()
{
    // Create a vertex buffer for the mesh.
    Wm4::Attributes kAttr;
    kAttr.SetPChannels(3);

    if (m_akNormal)
    {
        kAttr.SetNChannels(3);
    }

    if (m_akColor)
    {
        kAttr.SetCChannels(0,3);
    }

    if (m_akTexture)
    {
        assert(m_spkTState);
        kAttr.SetTChannels(0,2);
    }

    Wm4::VertexBuffer* pkVB = WM4_NEW Wm4::VertexBuffer(kAttr,m_iVQuantity);
    for (int i = 0; i < m_iVQuantity; i++)
    {
        pkVB->Position3(i) = m_akVertex[i];

        if (m_akNormal)
        {
            pkVB->Normal3(i) = m_akNormal[i];
        }

        if (m_akColor)
        {
            pkVB->Color3(0,i) = m_akColor[i];
        }

        if (m_akTexture)
        {
            pkVB->TCoord2(0,i) = m_akTexture[i];
        }
    }

    // Create an index buffer for the mesh.
    Wm4::IndexBuffer* pkIB = WM4_NEW Wm4::IndexBuffer(3*m_iFQuantity);
    int* aiIndex = pkIB->GetData();
    size_t uiSize = 3*m_iFQuantity*sizeof(int);
    Wm4::System::Memcpy(aiIndex,uiSize,m_aiFace,uiSize);

    // Create the mesh.
    TriMesh* pkMesh = WM4_NEW TriMesh(pkVB,pkIB);

    // Attach a material, if relevant.
    if (m_spkMState)
    {
        pkMesh->AttachGlobalState(m_spkMState);
    }

    // Attach a texture effect, if relevant.
    if (m_spkTState)
    {
        pkMesh->AttachEffect(m_spkTState);
    }

    return pkMesh;
}
//----------------------------------------------------------------------------
WUnimaterialMesh::VertexAttr::VertexAttr ()
{
    V = -1;
    C = -1;
    T = -1;
}
//----------------------------------------------------------------------------
bool WUnimaterialMesh::VertexAttr::operator== (const VertexAttr& rkAttr) const
{
    return V == rkAttr.V && C == rkAttr.C && T == rkAttr.T;
}
//----------------------------------------------------------------------------
bool WUnimaterialMesh::VertexAttr::operator< (const VertexAttr& rkAttr) const
{
    if (V < rkAttr.V)
    {
        return true;
    }
    if (V > rkAttr.V)
    {
        return false;
    }

    if (C < rkAttr.C)
    {
        return true;
    }
    if (C > rkAttr.C)
    {
        return false;
    }

    return T < rkAttr.T;
}
//----------------------------------------------------------------------------
