diff options
Diffstat (limited to 'thirdparty/thekla_atlas/nvmesh/param/Atlas.cpp')
-rw-r--r-- | thirdparty/thekla_atlas/nvmesh/param/Atlas.cpp | 1519 |
1 files changed, 0 insertions, 1519 deletions
diff --git a/thirdparty/thekla_atlas/nvmesh/param/Atlas.cpp b/thirdparty/thekla_atlas/nvmesh/param/Atlas.cpp deleted file mode 100644 index 98f92cef96..0000000000 --- a/thirdparty/thekla_atlas/nvmesh/param/Atlas.cpp +++ /dev/null @@ -1,1519 +0,0 @@ -// 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(); -} |