// Copyright NVIDIA Corporation 2006 -- Ignacio Castano <icastano@nvidia.com>

#include "nvmesh.h" // pch

#include "Atlas.h"
#include "Util.h"
#include "AtlasBuilder.h"
#include "AtlasPacker.h"
#include "SingleFaceMap.h"
#include "OrthogonalProjectionMap.h"
#include "LeastSquaresConformalMap.h"
#include "ParameterizationQuality.h"

//#include "nvmesh/export/MeshExportOBJ.h"

#include "nvmesh/halfedge/Mesh.h"
#include "nvmesh/halfedge/Face.h"
#include "nvmesh/halfedge/Vertex.h"

#include "nvmesh/MeshBuilder.h"
#include "nvmesh/MeshTopology.h"
#include "nvmesh/param/Util.h"
#include "nvmesh/geometry/Measurements.h"

#include "nvmath/Vector.inl"
#include "nvmath/Fitting.h"
#include "nvmath/Box.inl"
#include "nvmath/ProximityGrid.h"
#include "nvmath/Morton.h"

#include "nvcore/StrLib.h"
#include "nvcore/Array.inl"
#include "nvcore/HashMap.inl"

using namespace nv;


/// Ctor.
Atlas::Atlas()
{
    failed=false;
}

// Dtor.
Atlas::~Atlas()
{
    deleteAll(m_meshChartsArray);
}

uint Atlas::chartCount() const
{
    uint count = 0;
    foreach(c, m_meshChartsArray) {
        count += m_meshChartsArray[c]->chartCount();
    }
    return count;
}

const Chart * Atlas::chartAt(uint i) const
{
    foreach(c, m_meshChartsArray) {
        uint count = m_meshChartsArray[c]->chartCount();

        if (i < count) {
            return m_meshChartsArray[c]->chartAt(i);
        }

        i -= count;
    }

    return NULL;
}

Chart * Atlas::chartAt(uint i) 
{
    foreach(c, m_meshChartsArray) {
        uint count = m_meshChartsArray[c]->chartCount();

        if (i < count) {
            return m_meshChartsArray[c]->chartAt(i);
        }

        i -= count;
    }

    return NULL;
}

// Extract the charts and add to this atlas.
void Atlas::addMeshCharts(MeshCharts * meshCharts)
{
    m_meshChartsArray.append(meshCharts);
}

void Atlas::extractCharts(const HalfEdge::Mesh * mesh)
{
    MeshCharts * meshCharts = new MeshCharts(mesh);
    meshCharts->extractCharts();
    addMeshCharts(meshCharts);
}

void Atlas::computeCharts(const HalfEdge::Mesh * mesh, const SegmentationSettings & settings, const Array<uint> & unchartedMaterialArray)
{
    failed=false;
    MeshCharts * meshCharts = new MeshCharts(mesh);
    meshCharts->computeCharts(settings, unchartedMaterialArray);
    addMeshCharts(meshCharts);
}




#if 0

/// Compute a seamless texture atlas.
bool Atlas::computeSeamlessTextureAtlas(bool groupFaces/*= true*/, bool scaleTiles/*= false*/, uint w/*= 1024*/, uint h/* = 1024*/)
{
    // Implement seamless texture atlas similar to what ZBrush does. See also:
    // "Meshed Atlases for Real-Time Procedural Solid Texturing"
    // http://graphics.cs.uiuc.edu/~jch/papers/rtpst.pdf

    // Other methods that we should experiment with:
    // 
    // Seamless Texture Atlases:
    // http://www.cs.jhu.edu/~bpurnomo/STA/index.html
    // 
    // Rectangular Multi-Chart Geometry Images:
    // http://graphics.cs.uiuc.edu/~jch/papers/rmcgi.pdf
    // 
    // Discrete differential geometry also provide a way of constructing  
    // seamless quadrangulations as shown in:
    // http://www.geometry.caltech.edu/pubs/TACD06.pdf
    // 

#pragma message(NV_FILE_LINE "TODO: Implement seamless texture atlas.")

    if (groupFaces)
    {
        // @@ TODO.
    }
    else
    {
        // @@ Create one atlas per face.
    }

    if (scaleTiles)
    {
        // @@ TODO
    }

    /*
    if (!isQuadMesh(m_mesh)) {
        // Only handle quads for now.
        return false;
    }

    // Each face is a chart.
    const uint faceCount = m_mesh->faceCount();
    m_chartArray.resize(faceCount);

    for(uint f = 0; f < faceCount; f++) {
        m_chartArray[f].faceArray.clear();
        m_chartArray[f].faceArray.append(f);
    }

    // Map each face to a separate square.

    // Determine face layout according to width and height.
    float aspect = float(m_width) / float(m_height);

    uint i = 2;
    uint total = (m_width / (i+1)) * (m_height / (i+1));
    while(total > faceCount) {
        i *= 2;
        total = (m_width / (i+1)) * (m_height / (i+1));
    }

    uint tileSize = i / 2;

    int x = 0;
    int y = 0;

    m_result = new HalfEdge::Mesh();

    // Once you have that it's just matter of traversing the faces.
    for(uint f = 0; f < faceCount; f++) {
        // Compute texture coordinates.
        Vector2 tex[4];
        tex[0] = Vector2(float(x), float(y));
        tex[1] = Vector2(float(x+tileSize), float(y));
        tex[2] = Vector2(float(x+tileSize), float(y+tileSize));
        tex[3] = Vector2(float(x), float(y+tileSize));

        Array<uint> indexArray(4);

        const HalfEdge::Face * face = m_mesh->faceAt(f);

        int i = 0;
        for(HalfEdge::Face::ConstEdgeIterator it(face->edges()); !it.isDone(); it.advance(), i++) {
            const HalfEdge::Edge * edge = it.current();
            const HalfEdge::Vertex * vertex = edge->from();

            HalfEdge::Vertex * newVertex = m_result->addVertex(vertex->id(), vertex->pos());

            newVertex->setTex(Vector3(tex[i], 0));
            newVertex->setNor(vertex->nor());

            indexArray.append(m_result->vertexCount() + 1);
        }

        m_result->addFace(indexArray);

        // Move to the next tile.
        x += tileSize + 1;
        if (x + tileSize > m_width) {
            x = 0;
            y += tileSize + 1;
        }
    }
    */

    return false;
}

#endif


void Atlas::parameterizeCharts()
{
    foreach(i, m_meshChartsArray) {
        m_meshChartsArray[i]->parameterizeCharts();
    }
}


float Atlas::packCharts(int quality, float texelsPerUnit, bool blockAlign, bool conservative)
{
    AtlasPacker packer(this);
    packer.packCharts(quality, texelsPerUnit, blockAlign, conservative);
    if (hasFailed())
        return 0;
    return packer.computeAtlasUtilization();
}




/// Ctor.
MeshCharts::MeshCharts(const HalfEdge::Mesh * mesh) : m_mesh(mesh)
{
}

// Dtor.
MeshCharts::~MeshCharts()
{
    deleteAll(m_chartArray);
}


void MeshCharts::extractCharts()
{
    const uint faceCount = m_mesh->faceCount();

    int first = 0;
    Array<uint> queue(faceCount);

    BitArray bitFlags(faceCount);
    bitFlags.clearAll();

    for (uint f = 0; f < faceCount; f++)
    {
        if (bitFlags.bitAt(f) == false)
        {
            // Start new patch. Reset queue.
            first = 0;
            queue.clear();
            queue.append(f);
            bitFlags.setBitAt(f);

            while (first != queue.count())
            {
                const HalfEdge::Face * face = m_mesh->faceAt(queue[first]);

                // Visit face neighbors of queue[first]
                for (HalfEdge::Face::ConstEdgeIterator it(face->edges()); !it.isDone(); it.advance())
                {
                    const HalfEdge::Edge * edge = it.current();
                    nvDebugCheck(edge->pair != NULL);

                    if (!edge->isBoundary() && /*!edge->isSeam()*/ 
                        //!(edge->from()->tex() != edge->pair()->to()->tex() || edge->to()->tex() != edge->pair()->from()->tex()))
                        !(edge->from() != edge->pair->to() || edge->to() != edge->pair->from())) // Preserve existing seams (not just texture seams).
                    {
                        const HalfEdge::Face * neighborFace = edge->pair->face;
                        nvDebugCheck(neighborFace != NULL);

                        if (bitFlags.bitAt(neighborFace->id) == false)
                        {
                            queue.append(neighborFace->id);
                            bitFlags.setBitAt(neighborFace->id);
                        }
                    }
                }

                first++;
            }

            Chart * chart = new Chart();
            chart->build(m_mesh, queue);

            m_chartArray.append(chart);
        }
    }
}


/*
LSCM:
- identify sharp features using local dihedral angles.
- identify seed faces farthest from sharp features.
- grow charts from these seeds.

MCGIM:
- phase 1: chart growth
  - grow all charts simultaneously using dijkstra search on the dual graph of the mesh.
  - graph edges are weighted based on planarity metric.
  - metric uses distance to global chart normal.
  - terminate when all faces have been assigned.
- phase 2: seed computation:
  - place new seed of the chart at the most interior face.
  - most interior is evaluated using distance metric only.

- method repeates the two phases, until the location of the seeds does not change.
  - cycles are detected by recording all the previous seeds and chartification terminates.

D-Charts:

- Uniaxial conic metric:
  - N_c = axis of the generalized cone that best fits the chart. (cone can a be cylinder or a plane).
  - omega_c = angle between the face normals and the axis.
  - Fitting error between chart C and tringle t: F(c,t) = (N_c*n_t - cos(omega_c))^2

- Compactness metrics:
  - Roundness:
    - C(c,t) = pi * D(S_c,t)^2 / A_c
    - S_c = chart seed.
    - D(S_c,t) = length of the shortest path inside the chart betwen S_c and t.
    - A_c = chart area.
  - Straightness:
    - P(c,t) = l_out(c,t) / l_in(c,t)
    - l_out(c,t) = lenght of the edges not shared between C and t.
    - l_in(c,t) = lenght of the edges shared between C and t.

- Combined metric:
  - Cost(c,t) = F(c,t)^alpha + C(c,t)^beta + P(c,t)^gamma
  - alpha = 1, beta = 0.7, gamma = 0.5




Our basic approach:
- Just one iteration of k-means?
- Avoid dijkstra by greedily growing charts until a threshold is met. Increase threshold and repeat until no faces left.
- If distortion metric is too high, split chart, add two seeds.
- If chart size is low, try removing chart.


Postprocess:
- If topology is not disk:
  - Fill holes, if new faces fit proxy.
  - Find best cut, otherwise.
- After parameterization:
  - If boundary self-intersects: 
    - cut chart along the closest two diametral boundary vertices, repeat parametrization.
    - what if the overlap is on an appendix? How do we find that out and cut appropiately?
      - emphasize roundness metrics to prevent those cases.
  - If interior self-overlaps: preserve boundary parameterization and use mean-value map.

*/


SegmentationSettings::SegmentationSettings()
{
    // Charts have no area or boundary limits right now.
    maxChartArea = NV_FLOAT_MAX;
    maxBoundaryLength = NV_FLOAT_MAX;

    proxyFitMetricWeight = 1.0f;
    roundnessMetricWeight = 0.1f;
    straightnessMetricWeight = 0.25f;
    normalSeamMetricWeight = 1.0f;
    textureSeamMetricWeight = 0.1f;
}



void MeshCharts::computeCharts(const SegmentationSettings & settings, const Array<uint> & unchartedMaterialArray)
{
    Chart * vertexMap = NULL;
    
    if (unchartedMaterialArray.count() != 0) {
        vertexMap = new Chart();
        vertexMap->buildVertexMap(m_mesh, unchartedMaterialArray);

        if (vertexMap->faceCount() == 0) {
            delete vertexMap;
            vertexMap = NULL;
        }
    }
    

    AtlasBuilder builder(m_mesh);

    if (vertexMap != NULL) {
        // Mark faces that do not need to be charted.
        builder.markUnchartedFaces(vertexMap->faceArray());

        m_chartArray.append(vertexMap);
    }

    if (builder.facesLeft != 0) {

        // Tweak these values:
        const float maxThreshold = 2;
        const uint growFaceCount = 32;
        const uint maxIterations = 4;
        
        builder.settings = settings;

        //builder.settings.proxyFitMetricWeight *= 0.75; // relax proxy fit weight during initial seed placement.
        //builder.settings.roundnessMetricWeight = 0;
        //builder.settings.straightnessMetricWeight = 0;

        // This seems a reasonable estimate.
        uint maxSeedCount = max(6U, builder.facesLeft);

        // Create initial charts greedely.
        nvDebug("### Placing seeds\n");
        builder.placeSeeds(maxThreshold, maxSeedCount);
        nvDebug("###   Placed %d seeds (max = %d)\n", builder.chartCount(), maxSeedCount);

        builder.updateProxies();

        builder.mergeCharts();

    #if 1
        nvDebug("### Relocating seeds\n");
        builder.relocateSeeds();

        nvDebug("### Reset charts\n");
        builder.resetCharts();

        if (vertexMap != NULL) {
            builder.markUnchartedFaces(vertexMap->faceArray());
        }

        builder.settings = settings;

        nvDebug("### Growing charts\n");

        // Restart process growing charts in parallel.
        uint iteration = 0;
        while (true)
        {
            if (!builder.growCharts(maxThreshold, growFaceCount))
            {
                nvDebug("### Can't grow anymore\n");

                // If charts cannot grow more: fill holes, merge charts, relocate seeds and start new iteration.

                nvDebug("### Filling holes\n");
                builder.fillHoles(maxThreshold);
                nvDebug("###   Using %d charts now\n", builder.chartCount());

                builder.updateProxies();

                nvDebug("### Merging charts\n");
                builder.mergeCharts();
                nvDebug("###   Using %d charts now\n", builder.chartCount());

                nvDebug("### Reseeding\n");
                if (!builder.relocateSeeds())
                {
                    nvDebug("### Cannot relocate seeds anymore\n");

                    // Done!
                    break;
                }

                if (iteration == maxIterations)
                {
                    nvDebug("### Reached iteration limit\n");
                    break;
                }
                iteration++;

                nvDebug("### Reset charts\n");
                builder.resetCharts();

                if (vertexMap != NULL) {
                    builder.markUnchartedFaces(vertexMap->faceArray());
                }

                nvDebug("### Growing charts\n");
            }
        };
    #endif

        // Make sure no holes are left!
        nvDebugCheck(builder.facesLeft == 0);

        const uint chartCount = builder.chartArray.count();
        for (uint i = 0; i < chartCount; i++)
        {
            Chart * chart = new Chart();
            m_chartArray.append(chart);

            chart->build(m_mesh, builder.chartFaces(i));
        }
    }


    const uint chartCount = m_chartArray.count();

    // Build face indices.
    m_faceChart.resize(m_mesh->faceCount());
    m_faceIndex.resize(m_mesh->faceCount());

    for (uint i = 0; i < chartCount; i++)
    {
        const Chart * chart = m_chartArray[i];

        const uint faceCount = chart->faceCount();
        for (uint f = 0; f < faceCount; f++)
        {
            uint idx = chart->faceAt(f);
            m_faceChart[idx] = i;
            m_faceIndex[idx] = f;
        }
    }

    // Build an exclusive prefix sum of the chart vertex counts.
    m_chartVertexCountPrefixSum.resize(chartCount);
    
    if (chartCount > 0)
    {
        m_chartVertexCountPrefixSum[0] = 0;
        
        for (uint i = 1; i < chartCount; i++)
        {
            const Chart * chart = m_chartArray[i-1];
            m_chartVertexCountPrefixSum[i] = m_chartVertexCountPrefixSum[i-1] + chart->vertexCount();
        }

        m_totalVertexCount = m_chartVertexCountPrefixSum[chartCount - 1] + m_chartArray[chartCount-1]->vertexCount();
    }
    else
    {
        m_totalVertexCount = 0;
    }
}


void MeshCharts::parameterizeCharts()
{
    ParameterizationQuality globalParameterizationQuality;

    // Parameterize the charts.
    uint diskCount = 0;
    const uint chartCount = m_chartArray.count();
    for (uint i = 0; i < chartCount; i++)\
    {
        Chart * chart = m_chartArray[i];

        bool isValid = false;

        if (chart->isVertexMapped()) {
            continue;
        }

        if (chart->isDisk())
        {
            diskCount++;

            ParameterizationQuality chartParameterizationQuality;

            if (chart->faceCount() == 1) {
                computeSingleFaceMap(chart->unifiedMesh());

                chartParameterizationQuality = ParameterizationQuality(chart->unifiedMesh());
            }
            else {
                computeOrthogonalProjectionMap(chart->unifiedMesh());
                ParameterizationQuality orthogonalQuality(chart->unifiedMesh());

                computeLeastSquaresConformalMap(chart->unifiedMesh());
                ParameterizationQuality lscmQuality(chart->unifiedMesh());
                
                // If the orthogonal projection produces better results, just use that.
                // @@ It may be dangerous to do this, because isValid() does not detect self-overlaps.
                // @@ Another problem is that with very thin patches with nearly zero parametric area, the results of our metric are not accurate.
                /*if (orthogonalQuality.isValid() && orthogonalQuality.rmsStretchMetric() < lscmQuality.rmsStretchMetric()) {
                    computeOrthogonalProjectionMap(chart->unifiedMesh());
                    chartParameterizationQuality = orthogonalQuality;
                }
                else*/ {
                    chartParameterizationQuality = lscmQuality;
                }

                // If conformal map failed, 

                // @@ Experiment with other parameterization methods.
                //computeCircularBoundaryMap(chart->unifiedMesh());
                //computeConformalMap(chart->unifiedMesh());
                //computeNaturalConformalMap(chart->unifiedMesh());
                //computeGuidanceGradientMap(chart->unifiedMesh());
            }

            //ParameterizationQuality chartParameterizationQuality(chart->unifiedMesh());

            isValid = chartParameterizationQuality.isValid();

            if (!isValid)
            {
                nvDebug("*** Invalid parameterization.\n");
#if 0
                // Dump mesh to inspect problem:
                static int pieceCount = 0;
            
                StringBuilder fileName;
                fileName.format("invalid_chart_%d.obj", pieceCount++);
                exportMesh(chart->unifiedMesh(), fileName.str()); 
#endif
            }

            // @@ Check that parameterization quality is above a certain threshold.

            // @@ Detect boundary self-intersections.

            globalParameterizationQuality += chartParameterizationQuality;
        }

        if (!isValid)
        {
            //nvDebugBreak();
            // @@ Run the builder again, but only on this chart.
            //AtlasBuilder builder(chart->chartMesh());
        }

        // Transfer parameterization from unified mesh to chart mesh.
        chart->transferParameterization();

    }

    nvDebug("  Parameterized %d/%d charts.\n", diskCount, chartCount);
    nvDebug("  RMS stretch metric: %f\n", globalParameterizationQuality.rmsStretchMetric());
    nvDebug("  MAX stretch metric: %f\n", globalParameterizationQuality.maxStretchMetric());
    nvDebug("  RMS conformal metric: %f\n", globalParameterizationQuality.rmsConformalMetric());
    nvDebug("  RMS authalic metric: %f\n", globalParameterizationQuality.maxAuthalicMetric());
}



Chart::Chart() : m_chartMesh(NULL), m_unifiedMesh(NULL), m_isDisk(false), m_isVertexMapped(false)
{
}

void Chart::build(const HalfEdge::Mesh * originalMesh, const Array<uint> & faceArray)
{
    // Copy face indices.
    m_faceArray = faceArray;

    const uint meshVertexCount = originalMesh->vertexCount();

    m_chartMesh = new HalfEdge::Mesh();
    m_unifiedMesh = new HalfEdge::Mesh();

    Array<uint> chartMeshIndices;
    chartMeshIndices.resize(meshVertexCount, ~0);

    Array<uint> unifiedMeshIndices;
    unifiedMeshIndices.resize(meshVertexCount, ~0);

    // Add vertices.
    const uint faceCount = faceArray.count();
    for (uint f = 0; f < faceCount; f++)
    {
        const HalfEdge::Face * face = originalMesh->faceAt(faceArray[f]);
        nvDebugCheck(face != NULL);

        for(HalfEdge::Face::ConstEdgeIterator it(face->edges()); !it.isDone(); it.advance())
        {
            const HalfEdge::Vertex * vertex = it.current()->vertex;
            const HalfEdge::Vertex * unifiedVertex = vertex->firstColocal();

            if (unifiedMeshIndices[unifiedVertex->id] == ~0)
            {
                unifiedMeshIndices[unifiedVertex->id] = m_unifiedMesh->vertexCount();

                nvDebugCheck(vertex->pos == unifiedVertex->pos);
                m_unifiedMesh->addVertex(vertex->pos);
            }

            if (chartMeshIndices[vertex->id] == ~0)
            {
                chartMeshIndices[vertex->id] = m_chartMesh->vertexCount();
                m_chartToOriginalMap.append(vertex->id);
                m_chartToUnifiedMap.append(unifiedMeshIndices[unifiedVertex->id]);

                HalfEdge::Vertex * v = m_chartMesh->addVertex(vertex->pos);
                v->nor = vertex->nor;
                v->tex = vertex->tex;
            }
        }
    }

    // This is ignoring the canonical map:
    // - Is it really necessary to link colocals?

    m_chartMesh->linkColocals();    
    //m_unifiedMesh->linkColocals();  // Not strictly necessary, no colocals in the unified mesh. # Wrong.

    // This check is not valid anymore, if the original mesh vertices were linked with a canonical map, then it might have
    // some colocal vertices that were unlinked. So, the unified mesh might have some duplicate vertices, because firstColocal()
    // is not guaranteed to return the same vertex for two colocal vertices.
    //nvCheck(m_chartMesh->colocalVertexCount() == m_unifiedMesh->vertexCount());

    // Is that OK? What happens in meshes were that happens? Does anything break? Apparently not...
    


    Array<uint> faceIndices(7);

    // Add faces.
    for (uint f = 0; f < faceCount; f++)
    {
        const HalfEdge::Face * face = originalMesh->faceAt(faceArray[f]);
        nvDebugCheck(face != NULL);

        faceIndices.clear();

        for(HalfEdge::Face::ConstEdgeIterator it(face->edges()); !it.isDone(); it.advance())
        {
            const HalfEdge::Vertex * vertex = it.current()->vertex;
            nvDebugCheck(vertex != NULL);

            faceIndices.append(chartMeshIndices[vertex->id]);
        }

        m_chartMesh->addFace(faceIndices);

        faceIndices.clear();

        for(HalfEdge::Face::ConstEdgeIterator it(face->edges()); !it.isDone(); it.advance())
        {
            const HalfEdge::Vertex * vertex = it.current()->vertex;
            nvDebugCheck(vertex != NULL);

            vertex = vertex->firstColocal();

            faceIndices.append(unifiedMeshIndices[vertex->id]);
        }

        m_unifiedMesh->addFace(faceIndices);
    }

    m_chartMesh->linkBoundary();
    m_unifiedMesh->linkBoundary();

    //exportMesh(m_unifiedMesh.ptr(), "debug_input.obj");

    if (m_unifiedMesh->splitBoundaryEdges()) {
        m_unifiedMesh = unifyVertices(m_unifiedMesh.ptr());
    }

    //exportMesh(m_unifiedMesh.ptr(), "debug_split.obj");

    // Closing the holes is not always the best solution and does not fix all the problems.
    // We need to do some analysis of the holes and the genus to:
    // - Find cuts that reduce genus.
    // - Find cuts to connect holes.
    // - Use minimal spanning trees or seamster.
    if (!closeHoles()) {
        /*static int pieceCount = 0;
        StringBuilder fileName;
        fileName.format("debug_hole_%d.obj", pieceCount++);
        exportMesh(m_unifiedMesh.ptr(), fileName.str());*/
    }

    m_unifiedMesh = triangulate(m_unifiedMesh.ptr());
    
    //exportMesh(m_unifiedMesh.ptr(), "debug_triangulated.obj");


    // Analyze chart topology.
    MeshTopology topology(m_unifiedMesh.ptr());
    m_isDisk = topology.isDisk();

    // This is sometimes failing, when triangulate fails to add a triangle, it generates a hole in the mesh.
    //nvDebugCheck(m_isDisk);

    /*if (!m_isDisk) {
        static int pieceCount = 0;
        StringBuilder fileName;
        fileName.format("debug_hole_%d.obj", pieceCount++);
        exportMesh(m_unifiedMesh.ptr(), fileName.str());
    }*/


#if 0
    if (!m_isDisk) {
        nvDebugBreak();

        static int pieceCount = 0;
        
        StringBuilder fileName;
        fileName.format("debug_nodisk_%d.obj", pieceCount++);
        exportMesh(m_chartMesh.ptr(), fileName.str()); 
    }
#endif

}


void Chart::buildVertexMap(const HalfEdge::Mesh * originalMesh, const Array<uint> & unchartedMaterialArray)
{
    nvCheck(m_chartMesh == NULL && m_unifiedMesh == NULL);

    m_isVertexMapped = true;

    // Build face indices.
    m_faceArray.clear();

    const uint meshFaceCount = originalMesh->faceCount();
    for (uint f = 0; f < meshFaceCount; f++) {
        const HalfEdge::Face * face = originalMesh->faceAt(f);

        if (unchartedMaterialArray.contains(face->material)) {
            m_faceArray.append(f);
        }
    }

    const uint faceCount = m_faceArray.count();

    if (faceCount == 0) {
        return;
    }


    // @@ The chartMesh construction is basically the same as with regular charts, don't duplicate!

    const uint meshVertexCount = originalMesh->vertexCount();

    m_chartMesh = new HalfEdge::Mesh();

    Array<uint> chartMeshIndices;
    chartMeshIndices.resize(meshVertexCount, ~0);

    // Vertex map mesh only has disconnected vertices.
    for (uint f = 0; f < faceCount; f++)
    {
        const HalfEdge::Face * face = originalMesh->faceAt(m_faceArray[f]);
        nvDebugCheck(face != NULL);

        for (HalfEdge::Face::ConstEdgeIterator it(face->edges()); !it.isDone(); it.advance())
        {
            const HalfEdge::Vertex * vertex = it.current()->vertex;

            if (chartMeshIndices[vertex->id] == ~0)
            {
                chartMeshIndices[vertex->id] = m_chartMesh->vertexCount();
                m_chartToOriginalMap.append(vertex->id);

                HalfEdge::Vertex * v = m_chartMesh->addVertex(vertex->pos);
                v->nor = vertex->nor;
                v->tex = vertex->tex; // @@ Not necessary.
            }
        }
    }

    // @@ Link colocals using the original mesh canonical map? Build canonical map on the fly? Do we need to link colocals at all for this?
    //m_chartMesh->linkColocals();

    Array<uint> faceIndices(7);

    // Add faces.
    for (uint f = 0; f < faceCount; f++)
    {
        const HalfEdge::Face * face = originalMesh->faceAt(m_faceArray[f]);
        nvDebugCheck(face != NULL);

        faceIndices.clear();

        for(HalfEdge::Face::ConstEdgeIterator it(face->edges()); !it.isDone(); it.advance())
        {
            const HalfEdge::Vertex * vertex = it.current()->vertex;
            nvDebugCheck(vertex != NULL);
            nvDebugCheck(chartMeshIndices[vertex->id] != ~0);

            faceIndices.append(chartMeshIndices[vertex->id]);
        }

        HalfEdge::Face * new_face = m_chartMesh->addFace(faceIndices);
        nvDebugCheck(new_face != NULL);
    }

    m_chartMesh->linkBoundary();


    const uint chartVertexCount = m_chartMesh->vertexCount();

    Box bounds;
    bounds.clearBounds();

    for (uint i = 0; i < chartVertexCount; i++) {
        HalfEdge::Vertex * vertex = m_chartMesh->vertexAt(i);
        bounds.addPointToBounds(vertex->pos);
    }

    ProximityGrid grid;
    grid.init(bounds, chartVertexCount);

    for (uint i = 0; i < chartVertexCount; i++) {
        HalfEdge::Vertex * vertex = m_chartMesh->vertexAt(i);
        grid.add(vertex->pos, i);
    }


#if 0
    // Arrange vertices in a rectangle.
    vertexMapWidth = ftoi_ceil(sqrtf(float(chartVertexCount)));
    vertexMapHeight = (chartVertexCount + vertexMapWidth - 1) / vertexMapWidth;
    nvDebugCheck(vertexMapWidth >= vertexMapHeight);

    int x = 0, y = 0;
    for (uint i = 0; i < chartVertexCount; i++) {
        HalfEdge::Vertex * vertex = m_chartMesh->vertexAt(i);

        vertex->tex.x = float(x);
        vertex->tex.y = float(y);

        x++;
        if (x == vertexMapWidth) {
            x = 0;
            y++;
            nvCheck(y < vertexMapHeight);
        }
    }

#elif 0
    // Arrange vertices in a rectangle, traversing grid in 3D morton order and laying them down in 2D morton order.
    vertexMapWidth = ftoi_ceil(sqrtf(float(chartVertexCount)));
    vertexMapHeight = (chartVertexCount + vertexMapWidth - 1) / vertexMapWidth;
    nvDebugCheck(vertexMapWidth >= vertexMapHeight);

    int n = 0;
    uint32 texelCode = 0;

    uint cellsVisited = 0;

    const uint32 cellCodeCount = grid.mortonCount();
    for (uint32 cellCode = 0; cellCode < cellCodeCount; cellCode++) {
        int cell = grid.mortonIndex(cellCode);
        if (cell < 0) continue;

        cellsVisited++;

        const Array<uint> & indexArray = grid.cellArray[cell].indexArray;

        foreach(i, indexArray) {
            uint idx = indexArray[i];
            HalfEdge::Vertex * vertex = m_chartMesh->vertexAt(idx);

            //vertex->tex.x = float(n % rectangleWidth) + 0.5f;
            //vertex->tex.y = float(n / rectangleWidth) + 0.5f;

            // Lay down the points in z order too.
            uint x, y;
            do {
                x = decodeMorton2X(texelCode);
                y = decodeMorton2Y(texelCode);
                texelCode++;
            } while (x >= U32(vertexMapWidth) || y >= U32(vertexMapHeight));
            
            vertex->tex.x = float(x);
            vertex->tex.y = float(y);

            n++;
        }
    }

    nvDebugCheck(cellsVisited == grid.cellArray.count());
    nvDebugCheck(n == chartVertexCount);

#else

    uint texelCount = 0;

    const float positionThreshold = 0.01f;
    const float normalThreshold = 0.01f;

    uint verticesVisited = 0;
    uint cellsVisited = 0;

    Array<int> vertexIndexArray;
    vertexIndexArray.resize(chartVertexCount, -1); // Init all indices to -1.

    // Traverse vertices in morton order. @@ It may be more interesting to sort them based on orientation.
    const uint cellCodeCount = grid.mortonCount();
    for (uint cellCode = 0; cellCode < cellCodeCount; cellCode++) {
        int cell = grid.mortonIndex(cellCode);
        if (cell < 0) continue;

        cellsVisited++;

        const Array<uint> & indexArray = grid.cellArray[cell].indexArray;

        foreach(i, indexArray) {
            uint idx = indexArray[i];
            HalfEdge::Vertex * vertex = m_chartMesh->vertexAt(idx);

            nvDebugCheck(vertexIndexArray[idx] == -1);

            Array<uint> neighbors;
            grid.gather(vertex->pos, positionThreshold, /*ref*/neighbors);

            // Compare against all nearby vertices, cluster greedily.
            foreach(j, neighbors) {
                uint otherIdx = neighbors[j];

                if (vertexIndexArray[otherIdx] != -1) {
                    HalfEdge::Vertex * otherVertex = m_chartMesh->vertexAt(otherIdx);

                    if (distance(vertex->pos, otherVertex->pos) < positionThreshold &&
                        distance(vertex->nor, otherVertex->nor) < normalThreshold) 
                    {
                        vertexIndexArray[idx] = vertexIndexArray[otherIdx];
                        break;
                    }
                }
            }

            // If index not assigned, assign new one.
            if (vertexIndexArray[idx] == -1) {
                vertexIndexArray[idx] = texelCount++;
            }

            verticesVisited++;
        }
    }

    nvDebugCheck(cellsVisited == grid.cellArray.count());
    nvDebugCheck(verticesVisited == chartVertexCount);

    vertexMapWidth = ftoi_ceil(sqrtf(float(texelCount)));
    vertexMapWidth = (vertexMapWidth + 3) & ~3;                             // Width aligned to 4.
    vertexMapHeight = vertexMapWidth == 0 ? 0 : (texelCount + vertexMapWidth - 1) / vertexMapWidth;
    //vertexMapHeight = (vertexMapHeight + 3) & ~3;                           // Height aligned to 4.
    nvDebugCheck(vertexMapWidth >= vertexMapHeight);

    nvDebug("Reduced vertex count from %d to %d.\n", chartVertexCount, texelCount);

#if 0
    // This lays down the clustered vertices linearly.
    for (uint i = 0; i < chartVertexCount; i++) {
        HalfEdge::Vertex * vertex = m_chartMesh->vertexAt(i);

        int idx = vertexIndexArray[i];

        vertex->tex.x = float(idx % vertexMapWidth);
        vertex->tex.y = float(idx / vertexMapWidth);
    }
#else
    // Lay down the clustered vertices in morton order.

    Array<uint> texelCodes;
    texelCodes.resize(texelCount);

    // For each texel, assign one morton code.
    uint texelCode = 0;
    for (uint i = 0; i < texelCount; i++) {
        uint x, y;
        do {
            x = decodeMorton2X(texelCode);
            y = decodeMorton2Y(texelCode);
            texelCode++;
        } while (x >= U32(vertexMapWidth) || y >= U32(vertexMapHeight));

        texelCodes[i] = texelCode - 1;
    }

    for (uint i = 0; i < chartVertexCount; i++) {
        HalfEdge::Vertex * vertex = m_chartMesh->vertexAt(i);

        int idx = vertexIndexArray[i];
        if (idx != -1) {
            uint texelCode = texelCodes[idx];
            uint x = decodeMorton2X(texelCode);
            uint y = decodeMorton2Y(texelCode);

            vertex->tex.x = float(x);
            vertex->tex.y = float(y);
        }
    }

#endif
   
#endif

}



static void getBoundaryEdges(HalfEdge::Mesh * mesh, Array<HalfEdge::Edge *> & boundaryEdges)
{
    nvDebugCheck(mesh != NULL);

    const uint edgeCount = mesh->edgeCount();

    BitArray bitFlags(edgeCount);
    bitFlags.clearAll();

    boundaryEdges.clear();

    // Search for boundary edges. Mark all the edges that belong to the same boundary.
    for (uint e = 0; e < edgeCount; e++)
    {
        HalfEdge::Edge * startEdge = mesh->edgeAt(e);

        if (startEdge != NULL && startEdge->isBoundary() && bitFlags.bitAt(e) == false)
        {
            nvDebugCheck(startEdge->face != NULL);
            nvDebugCheck(startEdge->pair->face == NULL);

            startEdge = startEdge->pair;

            const HalfEdge::Edge * edge = startEdge;
            do {
                nvDebugCheck(edge->face == NULL);
                nvDebugCheck(bitFlags.bitAt(edge->id/2) == false);

                bitFlags.setBitAt(edge->id / 2);
                edge = edge->next;
            } while(startEdge != edge);

            boundaryEdges.append(startEdge);
        }
    }
}


bool Chart::closeLoop(uint start, const Array<HalfEdge::Edge *> & loop)
{
    const uint vertexCount = loop.count() - start;

    nvDebugCheck(vertexCount >= 3);
    if (vertexCount < 3) return false;

    nvDebugCheck(loop[start]->vertex->isColocal(loop[start+vertexCount-1]->to()));

    // If the hole is planar, then we add a single face that will be properly triangulated later.
    // If the hole is not planar, we add a triangle fan with a vertex at the hole centroid.
    // This is still a bit of a hack. There surely are better hole filling algorithms out there.

    Array<Vector3> points;
    points.resize(vertexCount);
    for (uint i = 0; i < vertexCount; i++) {
        points[i] = loop[start+i]->vertex->pos;
    }

    bool isPlanar = Fit::isPlanar(vertexCount, points.buffer());

    if (isPlanar) {
        // Add face and connect edges.
        HalfEdge::Face * face = m_unifiedMesh->addFace();
        for (uint i = 0; i < vertexCount; i++) {
            HalfEdge::Edge * edge = loop[start + i];
            
            edge->face = face;
            edge->setNext(loop[start + (i + 1) % vertexCount]);
        }
        face->edge = loop[start];

        nvDebugCheck(face->isValid());
    }
    else {
        // If the polygon is not planar, we just cross our fingers, and hope this will work:

        // Compute boundary centroid:
        Vector3 centroidPos(0);

        for (uint i = 0; i < vertexCount; i++) {
            centroidPos += points[i];
        }

        centroidPos *= (1.0f / vertexCount);

        HalfEdge::Vertex * centroid = m_unifiedMesh->addVertex(centroidPos);

        // Add one pair of edges for each boundary vertex.
        for (uint j = vertexCount-1, i = 0; i < vertexCount; j = i++) {
            HalfEdge::Face * face = m_unifiedMesh->addFace(centroid->id, loop[start+j]->vertex->id, loop[start+i]->vertex->id);
            nvDebugCheck(face != NULL);
        }
    }

    return true;
}


bool Chart::closeHoles()
{
    nvDebugCheck(!m_isVertexMapped);

    Array<HalfEdge::Edge *> boundaryEdges;
    getBoundaryEdges(m_unifiedMesh.ptr(), boundaryEdges);

    uint boundaryCount = boundaryEdges.count();
    if (boundaryCount <= 1)
    {
        // Nothing to close.
        return true;
    }

    // Compute lengths and areas.
    Array<float> boundaryLengths;
    //Array<Vector3> boundaryCentroids;

    for (uint i = 0; i < boundaryCount; i++)
    {
        const HalfEdge::Edge * startEdge = boundaryEdges[i];
        nvCheck(startEdge->face == NULL);

        //float boundaryEdgeCount = 0;
        float boundaryLength = 0.0f;
        //Vector3 boundaryCentroid(zero);

        const HalfEdge::Edge * edge = startEdge;
        do {
            Vector3 t0 = edge->from()->pos;
            Vector3 t1 = edge->to()->pos;

            //boundaryEdgeCount++;
            boundaryLength += length(t1 - t0);
            //boundaryCentroid += edge->vertex()->pos;

            edge = edge->next;
        } while(edge != startEdge);

        boundaryLengths.append(boundaryLength);
        //boundaryCentroids.append(boundaryCentroid / boundaryEdgeCount);
    }


    // Find disk boundary.
    uint diskBoundary = 0;
    float maxLength = boundaryLengths[0];

    for (uint i = 1; i < boundaryCount; i++)
    {
        if (boundaryLengths[i] > maxLength)
        {
            maxLength = boundaryLengths[i];
            diskBoundary = i;
        }
    }


    // Sew holes.
    /*for (uint i = 0; i < boundaryCount; i++)
    {
        if (diskBoundary == i)
        {
            // Skip disk boundary.
            continue;
        }

        HalfEdge::Edge * startEdge = boundaryEdges[i];
        nvCheck(startEdge->face() == NULL);

        boundaryEdges[i] = m_unifiedMesh->sewBoundary(startEdge);
    }

    exportMesh(m_unifiedMesh.ptr(), "debug_sewn.obj");*/

    //bool hasNewHoles = false;

    // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
    // @@ Close loop is wrong, after closing a loop, we do not only have to add the face, but make sure that every edge in he loop is pointing to the right place.

    // Close holes.
    for (uint i = 0; i < boundaryCount; i++)
    {
        if (diskBoundary == i)
        {
            // Skip disk boundary.
            continue;
        }

        HalfEdge::Edge * startEdge = boundaryEdges[i];
        nvDebugCheck(startEdge != NULL);
        nvDebugCheck(startEdge->face == NULL);

#if 1
        Array<HalfEdge::Vertex *> vertexLoop;
        Array<HalfEdge::Edge *> edgeLoop;

        HalfEdge::Edge * edge = startEdge;
        do {
            HalfEdge::Vertex * vertex = edge->next->vertex; // edge->to()

            uint i;
            for (i = 0; i < vertexLoop.count(); i++) {
                if (vertex->isColocal(vertexLoop[i])) {
                    break;
                }
            }
            
            bool isCrossing = (i != vertexLoop.count());

            if (isCrossing) {

                HalfEdge::Edge * prev = edgeLoop[i];    // Previous edge before the loop.
                HalfEdge::Edge * next = edge->next;   // Next edge after the loop.

                nvDebugCheck(prev->to()->isColocal(next->from()));

                // Close loop.
                edgeLoop.append(edge);
                closeLoop(i+1, edgeLoop);

                // Link boundary loop.
                prev->setNext(next);
                vertex->setEdge(next);

                // Start over again.
                vertexLoop.clear();
                edgeLoop.clear();
                
                edge = startEdge;
                vertex = edge->to();
            }

            vertexLoop.append(vertex);
            edgeLoop.append(edge);

            edge = edge->next;
        } while(edge != startEdge);

        closeLoop(0, edgeLoop);
#endif

        /*

        // Add face and connect boundary edges.
        HalfEdge::Face * face = m_unifiedMesh->addFace();
        face->setEdge(startEdge);

        HalfEdge::Edge * edge = startEdge;
        do {
            edge->setFace(face);

            edge = edge->next();
        } while(edge != startEdge);

        */


        /*
        uint edgeCount = 0;
        HalfEdge::Edge * edge = startEdge;
        do {
            edgeCount++;
            edge = edge->next();
        } while(edge != startEdge);



        // Count edges in this boundary.
        uint edgeCount = 0;
        HalfEdge::Edge * edge = startEdge;
        do {
            edgeCount++;
            edge = edge->next();
        } while(edge != startEdge);

        // Trivial hole, fill with one triangle. This actually works for all convex boundaries with non colinear vertices.
        if (edgeCount == 3) {
            // Add face and connect boundary edges.
            HalfEdge::Face * face = m_unifiedMesh->addFace();
            face->setEdge(startEdge);

            edge = startEdge;
            do {
                edge->setFace(face);

                edge = edge->next();
            } while(edge != startEdge);

            // @@ Implement the above using addFace, it should now work with existing edges, as long as their face pointers is zero.

        }
        else {
            // Ideally we should:
            // - compute best fit plane of boundary vertices.
            // - project boundary polygon onto plane.
            // - triangulate boundary polygon.
            // - add faces of the resulting triangulation.

            // I don't have a good triangulator available. A more simple solution that works in more (but not all) cases:
            // - compute boundary centroid.
            // - add vertex centroid.
            // - connect centroid vertex with boundary vertices.
            // - connect radial edges with boundary edges.

            // This should work for non-convex boundaries with colinear vertices as long as the kernel of the polygon is not empty.

            // Compute boundary centroid:
            Vector3 centroid_pos(0);
            Vector2 centroid_tex(0);

            HalfEdge::Edge * edge = startEdge;
            do {
                centroid_pos += edge->vertex()->pos;
                centroid_tex += edge->vertex()->tex;
                edge = edge->next();
            } while(edge != startEdge);

            centroid_pos *= (1.0f / edgeCount);
            centroid_tex *= (1.0f / edgeCount);

            HalfEdge::Vertex * centroid = m_unifiedMesh->addVertex(centroid_pos);
            centroid->tex = centroid_tex;

            // Add one pair of edges for each boundary vertex.
            edge = startEdge;
            do {
                HalfEdge::Edge * next = edge->next();

                nvCheck(edge->face() == NULL);
                HalfEdge::Face * face = m_unifiedMesh->addFace(centroid->id(), edge->from()->id(), edge->to()->id());
                
                if (face != NULL) {
                    nvCheck(edge->face() == face);
                }
                else {
                    hasNewHoles = true;
                }

                edge = next;
            } while(edge != startEdge);
        }
        */
    }

    /*nvDebugCheck(!hasNewHoles);

    if (hasNewHoles) {
        // Link boundary again, in case closeHoles created new holes!
        m_unifiedMesh->linkBoundary();
    }*/

    // Because some algorithms do not expect sparse edge buffers.
    //m_unifiedMesh->compactEdges();

    // In case we messed up:
    //m_unifiedMesh->linkBoundary();

    getBoundaryEdges(m_unifiedMesh.ptr(), boundaryEdges);

    boundaryCount = boundaryEdges.count();
    nvDebugCheck(boundaryCount == 1);

    //exportMesh(m_unifiedMesh.ptr(), "debug_hole_filled.obj");

    return boundaryCount == 1;
}


// Transfer parameterization from unified mesh to chart mesh.
void Chart::transferParameterization() {
    nvDebugCheck(!m_isVertexMapped);

    uint vertexCount = m_chartMesh->vertexCount();
    for (uint v = 0; v < vertexCount; v++) {
        HalfEdge::Vertex * vertex = m_chartMesh->vertexAt(v);
        HalfEdge::Vertex * unifiedVertex = m_unifiedMesh->vertexAt(mapChartVertexToUnifiedVertex(v));
        vertex->tex = unifiedVertex->tex;
    }
}

float Chart::computeSurfaceArea() const {
    return nv::computeSurfaceArea(m_chartMesh.ptr()) * scale;
}

float Chart::computeParametricArea() const {
    // This only makes sense in parameterized meshes.
    nvDebugCheck(m_isDisk);            
    nvDebugCheck(!m_isVertexMapped);

    return nv::computeParametricArea(m_chartMesh.ptr());
}

Vector2 Chart::computeParametricBounds() const {
    // This only makes sense in parameterized meshes.
    nvDebugCheck(m_isDisk);
    nvDebugCheck(!m_isVertexMapped);

    Box bounds;
    bounds.clearBounds();

    uint vertexCount = m_chartMesh->vertexCount();
    for (uint v = 0; v < vertexCount; v++) {
        HalfEdge::Vertex * vertex = m_chartMesh->vertexAt(v);
        bounds.addPointToBounds(Vector3(vertex->tex, 0));
    }

    return bounds.extents().xy();
}