// 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 "WSceneBuilder.h"
#include "WUnimaterialMesh.h"
#include <set>

//----------------------------------------------------------------------------
TriObject* WSceneBuilder::GetTriObject (INode* pkNode, bool* pbNeedDel)
{
    // Determine whether or not a Max node has an associated mesh object.
    //
    // pkNode:
    //     Node to test.
    // pbNeedDel:
    //     True, if the caller should delete the returned mesh pointer after
    //     use, false otherwise.
    // Returns pointer to mesh object, or null, if the node has no associated
    // mesh.

    Object* pkObj = pkNode->EvalWorldState(m_iTimeStart).obj;

    // See if the object associated with this node can be converted to a
    // trimesh.
    if (!pkObj->CanConvertToType(Class_ID(TRIOBJ_CLASS_ID,0)))
    {
        return 0;
    }

    // do the conversion
    TriObject* pkTriObj = (TriObject*)pkObj->ConvertToType(m_iTimeStart,
        Class_ID(TRIOBJ_CLASS_ID,0));
    if (pkTriObj == 0)
    {
        return 0;
    }

    // See whether or not the caller is responsible for deleting the mesh
    // pointer.
    *pbNeedDel = (pkTriObj != pkObj ? true : false);
    return pkTriObj;
}
//----------------------------------------------------------------------------
Wm4::Spatial* WSceneBuilder::BuildMesh (INode* pkMaxNode,
    Wm4::Spatial* pkWm4Node)
{
    // Convert a Max trimesh to one or more equivalent Magic trimeshes.
    //
    // pkMaxNode:
    //     Mesh node in the Max hierarchy.
    // pkWm4Node:
    //     Parent node in Magic scene to which newly created.
    // Returns pointer to new child node in the Magic scene.  This will point
    // directly to a trimesh object, if there is only one Magic mesh, or to
    // a "link" node, whose children are the mulitple trimeshes needed to
    // represent the Max mesh.

    bool bNeedDel = false;
    TriObject* pkTriObj = GetTriObject(pkMaxNode,&bNeedDel);
    if (!pkTriObj) 
    {
        return 0;
    }
    Mesh* pkMaxMesh = &pkTriObj->GetMesh();

    Mtl* pkMtl = pkMaxNode->GetMtl();
    assert(pkMtl);
    int iMtlID = m_kMtlList.GetID(pkMtl);
    assert(iMtlID >= 0);

    Wm4::Spatial* pkLink;
    bool bHasLink = (pkWm4Node->GetName().length() > 0 &&
        strcmp(pkMaxNode->GetName(),pkWm4Node->GetName().c_str()) == 0);
    char* acMaxName = pkMaxNode->GetName();
    int i;

    // If only a single Magic mesh is required to represent the Max mesh,
    // then link it directly to the parent node in the Magic scene, otherwise
    // create a "link" node, whose children will be the individual Magic
    // meshes needed to represent the Max mesh.
    std::vector<WUnimaterialMesh*> kUMeshes;
    SplitGeometry(pkMaxMesh,iMtlID,kUMeshes);
    if (kUMeshes.size() > 1)
    {
        if (!bHasLink)
        {
            pkLink = BuildSpatial(pkMaxNode,pkWm4Node);
        }
        else 
        {
            pkLink = pkWm4Node; 
        }

        assert(pkLink->IsDerived(Wm4::Node::TYPE));

        for (i = 0; i < (int)kUMeshes.size(); i++)
        {
            Wm4::TriMesh* pkTriMesh = kUMeshes[i]->ToTriMesh();
            char acMeshNumber[6];
            Wm4::System::Sprintf(acMeshNumber,6,"_%d",i + 1);
            size_t uiSize = strlen(acMaxName) + strlen(acMeshNumber) + 1;
            char* acWm4Name = WM4_NEW char[uiSize];
            Wm4::System::Strcpy(acWm4Name,uiSize,acMaxName);
            Wm4::System::Strcat(acWm4Name,uiSize,acMeshNumber);         
            pkTriMesh->SetName(acWm4Name);
            WM4_DELETE[] acWm4Name;
            ((Wm4::Node*)pkLink)->AttachChild(pkTriMesh);
        }
    }
    else
    {
        Wm4::TriMesh* pkTriMesh = kUMeshes[0]->ToTriMesh();
        if (!bHasLink)
        {
            pkTriMesh->SetName(acMaxName);
            pkTriMesh->Local = GetLocalTransform(pkMaxNode,m_iTimeStart);
        }
        else
        {
            size_t uiSize = strlen(acMaxName) + 3;
            char* acWm4Name = WM4_NEW char[uiSize];
            Wm4::System::Strcpy(acWm4Name,uiSize,acMaxName);
            Wm4::System::Strcat(acWm4Name,uiSize,"_1");
            pkTriMesh->SetName(acWm4Name);
            WM4_DELETE[] acWm4Name;
        }
        assert(pkWm4Node->IsDerived(Wm4::Node::TYPE));
        ((Wm4::Node*)pkWm4Node)->AttachChild(pkTriMesh);
        pkLink = pkTriMesh;
    }
    for (i = 0; i < (int)kUMeshes.size(); i++)
    {
        WM4_DELETE kUMeshes[i];
    }

    if (bNeedDel) 
    {
        WM4_DELETE pkTriObj;
    }

    return pkLink;
}
//----------------------------------------------------------------------------
void WSceneBuilder::SplitGeometry (Mesh* pkMaxMesh, int iMtlID, 
    std::vector<WUnimaterialMesh*>& rkUMeshes)
{
    // If a mesh is encountered that has multi-submaterials *and* uses two
    // or more submaterials, that mesh needs to be split since Wild Magic
    // only supports one material per object.
    //
    // pkMaxMesh:
    //     Pointer to Max mesh to be split.
    // rkUMeshes:
    //     Vector, which will receive the individual WUnimaterialMesh's
    //     required to represent the Max mesh.
    // bHasReflection:
    //     True, if the node's transform contains a relection, false
    //     otherwise.

    int i, j;

    // compute vertex normals
    Wm4::Vector3f* akNormal = 0;
    if (m_pkSettings->IncludeNormals)
    {
        pkMaxMesh->buildNormals();
        akNormal = WM4_NEW Wm4::Vector3f[pkMaxMesh->numVerts];
        for (i = 0; i < pkMaxMesh->numFaces; i++)
        {
            Face& rkFace = pkMaxMesh->faces[i];
            for (j = 0; j < 3; j++)
            {
                int iVertex = rkFace.getVert(j);
                akNormal[iVertex] = GetVertexNormal(pkMaxMesh,i,iVertex);
            }
        }       
    }

    // number of submaterials
    WMaterialTree& rkTree = m_kMtlTree[iMtlID];
    int iSubquantity = 0;
    if (iMtlID >= 0)
    {
        iSubquantity = rkTree.GetMChildQuantity();
    }

    // compute the maximum submaterial index used by the geometry object
    int iFace, iSubID, iMaxSubID = -1;
    for (iFace = 0; iFace < pkMaxMesh->numFaces; iFace++)
    {
        iSubID = pkMaxMesh->faces[iFace].getMatID();
        if (iSubID >= iSubquantity)
        {
            if (iSubquantity > 0)
            {
                iSubID = iSubID % iSubquantity;
            }
            else
            {
                iSubID = 0;
            }
        }
        if (iSubID > iMaxSubID)
        {
            iMaxSubID = iSubID;
        }
    }

    // partition the faces by material ID
    std::vector<int>* akPartition = WM4_NEW std::vector<int>[iMaxSubID+1];
    for (iFace = 0; iFace < pkMaxMesh->numFaces; iFace++)
    {
        iSubID = pkMaxMesh->faces[iFace].getMatID();
        if (iSubID >= iSubquantity)
        {
            if (iSubquantity > 0)
            {
                iSubID = iSubID % iSubquantity;
            }
            else
            {
                iSubID = 0;
            }
        }
        akPartition[iSubID].push_back(iFace);
    }

    // allocate the unimaterial meshes
    for (iSubID = 0; iSubID <= iMaxSubID; iSubID++)
    {
        if (akPartition[iSubID].size() == 0)
        {
            // submaterial ID not used
            continue;
        }

        WUnimaterialMesh* pkMesh = WM4_NEW WUnimaterialMesh;
        if (iMtlID >= 0)
        {
            // TO DO.  For now, just grab the first nonnull texture.  Later
            // we should see multiple textures for special effects.
            if (iSubquantity > 0)
            {
                WMaterialTree& rkSubtree = rkTree.GetMChild(iSubID);
                pkMesh->MState() = rkSubtree.GetMaterial();
                for (i = 0; i < (int)rkSubtree.GetTChildQuantity(); i++)
                {
                    if (rkSubtree.GetEffect(i))
                    {
                        pkMesh->TState() = rkSubtree.GetEffect(i);
                        break;
                    }
                }
            }
            else
            {
                pkMesh->MState() = rkTree.GetMaterial();
                for (i = 0; i < (int)rkTree.GetTChildQuantity(); i++)
                {
                    if (rkTree.GetEffect(i))
                    {
                        pkMesh->TState() = rkTree.GetEffect(i);
                        break;
                    }
                }
            }
        }

        PackVertices(pkMesh,pkMaxMesh,akPartition[iSubID],akNormal);

        if (m_pkSettings->IncludeVertexColors && pkMaxMesh->numCVerts > 0)
        {
            PackColors(pkMesh,pkMaxMesh,akPartition[iSubID]);
        }

        if (m_pkSettings->IncludeTexCoords
        &&  pkMaxMesh->numTVerts > 0
        &&  pkMesh->TState())
        {
            PackTextures(pkMesh,pkMaxMesh,akPartition[iSubID]);
        }

        rkUMeshes.push_back(pkMesh);
    }

    WM4_DELETE[] akPartition;
    WM4_DELETE[] akNormal;

    // Duplicate vertex data to meet Wild Magic constraints on one
    // color/normal/uv per vertex position.
    for (i = 0; i < (int)rkUMeshes.size(); i++)
    {
        rkUMeshes[i]->DuplicateGeometry();  
    }
}
//----------------------------------------------------------------------------
void WSceneBuilder::PackVertices (WUnimaterialMesh* pkUniMesh,
    Mesh* pkMaxMesh, std::vector<int>& rkPartition, Wm4::Vector3f* akNormal)
{
    // Take a partition of a Max mesh (represented by a vector of face
    // indices), and pack the corresponding vertices and normals into a
    // WUnimaterialMesh.
    //
    // pkUniMesh:
    //     WUnimaterialMesh to pack.
    // pkMaxMesh:
    //     Max mesh from which to extract vertices.
    // rkPartition:
    //     Vector of face indices representing the mesh partition.
    // akNormal:
    //     Array of mesh normal vectors, from which to extract the partition's
    //     normals.

    // get vertex indices used by the partition
    std::set<int> kVIndex;
    int i, j;
    for (i = 0; i < (int)rkPartition.size(); i++)
    {
        Face& rkFace = pkMaxMesh->faces[rkPartition[i]];
        for (j = 0; j < 3; j++)
        {
            kVIndex.insert(rkFace.v[j]);
        }
    }

    // Pack into contiguous arrays.  The packing relies on the STL set having
    // elements in increasing order.  If kVIndex[i] = j, then aiVMap[j] = i.
    // The aiVMap array is made large enough to hold all possible j, but
    // aiVMap[j] = -1 if there is no corresponding i.
    int iVMax = *kVIndex.rbegin();
    int* aiVMap = WM4_NEW int[iVMax+1];
    memset(aiVMap,0xFF,(iVMax+1)*sizeof(int));

    pkUniMesh->VQuantity() = kVIndex.size();
    pkUniMesh->Vertex() = WM4_NEW Wm4::Vector3f[pkUniMesh->VQuantity()];
    pkUniMesh->Normal() = WM4_NEW Wm4::Vector3f[pkUniMesh->VQuantity()];

    std::set<int>::iterator kIter = kVIndex.begin();
    for (i = 0; i < (int)kVIndex.size(); i++, kIter++)
    {
        j = *kIter;
        aiVMap[j] = i;

        pkUniMesh->Vertex()[i].X() = pkMaxMesh->verts[j].x;
        pkUniMesh->Vertex()[i].Y() = pkMaxMesh->verts[j].y;
        pkUniMesh->Vertex()[i].Z() = pkMaxMesh->verts[j].z;
        pkUniMesh->Normal()[i] = akNormal[j];
    }

    // remap vertex face connectivity
    pkUniMesh->FQuantity() = rkPartition.size();
    pkUniMesh->Face() = WM4_NEW int[3*pkUniMesh->FQuantity()];
    for (i = 0; i < (int)rkPartition.size(); i++)
    {
        Face& rkFace = pkMaxMesh->faces[rkPartition[i]];
        for (j = 0; j < 3; j++)
        {
            pkUniMesh->Face()[3*i+j] = aiVMap[rkFace.v[j]];
        }
    }
    WM4_DELETE[] aiVMap;
}
//----------------------------------------------------------------------------
void WSceneBuilder::PackColors (WUnimaterialMesh* pkUniMesh, Mesh* pkMaxMesh, 
    std::vector<int>& rkPartition)
{
    // Take a partition of a Max mesh (represented by a vector of face
    // indices), and pack the corresponding vertex colors into a
    // WUnimaterialMesh.
    //
    // pkUniMesh:
    //     WUnimaterialMesh to pack.
    // pkMaxMesh:
    //     Max mesh from which to extract colors.
    // rkPartition:
    //     Vector of face indices representing the mesh partition.

    // get color indices used by the partition
    std::set<int> kCIndex;
    int i, j;
    for (i = 0; i < (int)rkPartition.size(); i++)
    {
        TVFace& rkCFace = pkMaxMesh->vcFace[rkPartition[i]];
        for (j = 0; j < 3; j++)
        {
            kCIndex.insert(rkCFace.t[j]);
        }
    }

    // Pack into contiguous arrays.  The packing relies on the STL set having
    // elements in increasing order.  If kCIndex[i] = j, then aiCMap[j] = i.
    // The aiCMap array is made large enough to hold all possible j, but
    // aiCMap[j] = -1 if there is no corresponding i.
    int iCMax = *kCIndex.rbegin();
    int* aiCMap = WM4_NEW int[iCMax+1];
    memset(aiCMap,0xFF,(iCMax+1)*sizeof(int));

    pkUniMesh->CQuantity() = kCIndex.size();
    pkUniMesh->Color() = WM4_NEW Wm4::ColorRGB[pkUniMesh->CQuantity()];

    std::set<int>::iterator kIter = kCIndex.begin();
    for (i = 0; i < (int)kCIndex.size(); i++, kIter++)
    {
        j = *kIter;
        aiCMap[j] = i;

        pkUniMesh->Color()[i].R() = pkMaxMesh->vertCol[j].x;
        pkUniMesh->Color()[i].G() = pkMaxMesh->vertCol[j].y;
        pkUniMesh->Color()[i].B() = pkMaxMesh->vertCol[j].z;
    }

    // remap color face connectivity
    pkUniMesh->CFace() = WM4_NEW int[3*pkUniMesh->FQuantity()];
    for (i = 0; i < (int)rkPartition.size(); i++)
    {
        TVFace& rkCFace = pkMaxMesh->vcFace[rkPartition[i]];
        for (j = 0; j < 3; j++)
        {
            pkUniMesh->CFace()[3*i+j] = aiCMap[rkCFace.t[j]];
        }
    }
    WM4_DELETE[] aiCMap;
}
//----------------------------------------------------------------------------
void WSceneBuilder::PackTextures (WUnimaterialMesh* pkUniMesh,
    Mesh* pkMaxMesh, std::vector<int>& rkPartition)
{
    // Take a partition of a Max mesh (represented by a vector of face
    // indices), and pack the corresponding texture coordinates into a
    // WUnimaterialMesh.
    //
    // pkUniMesh:
    //     WUnimaterialMesh to pack.
    // pkMaxMesh:
    //     Max mesh from which to extract texture coords.
    // rkPartition:
    //     Vector of face indices representing the mesh partition.

    // get texture indices used by the partition
    std::set<int> kTIndex;
    int i, j;
    for (i = 0; i < (int)rkPartition.size(); i++)
    {
        TVFace& rkTFace = pkMaxMesh->tvFace[rkPartition[i]];
        for (j = 0; j < 3; j++)
        {
            kTIndex.insert(rkTFace.t[j]);
        }
    }

    // Pack into contiguous arrays.  The packing relies on the STL set having
    // elements in increasing order.  If kTIndex[i] = j, then aiTMap[j] = i.
    // The aiTMap array is made large enough to hold all possible j, but
    // aiTMap[j] = -1 if there is no corresponding i.
    int iTMax = *kTIndex.rbegin();
    int* aiTMap = WM4_NEW int[iTMax+1];
    memset(aiTMap,0xFF,(iTMax + 1)*sizeof(int));

    pkUniMesh->TQuantity() = kTIndex.size();
    pkUniMesh->Texture() = WM4_NEW Wm4::Vector2f[pkUniMesh->TQuantity()];

    std::set<int>::iterator kIter = kTIndex.begin();
    for (i = 0; i < (int)kTIndex.size(); i++, kIter++)
    {
        j = *kIter;
        aiTMap[j] = i;

        pkUniMesh->Texture()[i].X() = pkMaxMesh->tVerts[j].x;
        pkUniMesh->Texture()[i].Y() = pkMaxMesh->tVerts[j].y;
    }

    // remap texture face connectivity
    pkUniMesh->TFace() = WM4_NEW int[3*pkUniMesh->FQuantity()];
    for (i = 0; i < (int)rkPartition.size(); i++)
    {
        TVFace& rkTFace = pkMaxMesh->tvFace[rkPartition[i]];
        for (j = 0; j < 3; j++)
        {
            pkUniMesh->TFace()[3*i+j] = aiTMap[rkTFace.t[j]];
        }
    }
    WM4_DELETE[] aiTMap;
}
//----------------------------------------------------------------------------
Wm4::Vector3f WSceneBuilder::GetVertexNormal (Mesh* pkMaxMesh, int iFace,
    int iVertex)
{
    // Get a vertex normal, for a specified Max mesh vertex index.
    //
    // pkMaxMesh:
    //     Max mesh containing the vertex.
    // iFace:
    //     Index of face to which vertex belongs.
    // iVertex:
    //     Index of vertex for which to retrieve normal.
    // returns the normal vector

    Point3 kNormal;

    RVertex* pkRV = pkMaxMesh->getRVertPtr(iVertex);
    
    if (pkRV->rFlags & SPECIFIED_NORMAL)
    {
        kNormal = pkRV->rn.getNormal();
    }
    else
    {
        // If RVertex does not contain a specified normal vector, the normal
        // is calculated from a smoothing group, if it exists.  If no such
        // group exists, the face normal is used.
        Face& rkFace = pkMaxMesh->faces[iFace];
        DWORD dwSmoothingGroup = rkFace.smGroup;
        int iNumNormals = pkRV->rFlags & NORCT_MASK;
        if (iNumNormals && dwSmoothingGroup)
        {
            if (iNumNormals == 1)
            {
                // only one normal exists in group, rkRV already stored it
                kNormal = pkRV->rn.getNormal();
            }
            else
            {
                // Lookup the normal from the smoothing group that contains
                // the face.
                for (int i = 0; i < iNumNormals; i++)
                {
                    if (pkRV->ern[i].getSmGroup() & dwSmoothingGroup)
                    {
                        kNormal = pkRV->ern[i].getNormal();
                        break;
                    }
                }
            }
        }
        else
        {
            // use the face normal when no smoothing group exists
            kNormal = pkMaxMesh->FaceNormal(iFace,true);
        }
    }

    return Wm4::Vector3f(kNormal.x,kNormal.y,kNormal.z);
}
//----------------------------------------------------------------------------
