From 62f5f8f269d03cdb4447f9a756e5438edc27f8b0 Mon Sep 17 00:00:00 2001 From: Relintai Date: Tue, 28 Sep 2021 16:35:49 +0200 Subject: [PATCH] Removed PlanarCharts and ClusteredCharts from xatlas. --- xatlas/xatlas.cpp | 960 +--------------------------------------------- 1 file changed, 4 insertions(+), 956 deletions(-) diff --git a/xatlas/xatlas.cpp b/xatlas/xatlas.cpp index cfce4f5..18935d5 100644 --- a/xatlas/xatlas.cpp +++ b/xatlas/xatlas.cpp @@ -4642,931 +4642,6 @@ static uint32_t s_planarRegionsCurrentRegion; static uint32_t s_planarRegionsCurrentVertex; #endif -struct PlanarCharts { - PlanarCharts(AtlasData &data) : - m_data(data), m_nextRegionFace(MemTag::SegmentAtlasPlanarRegions), m_faceToRegionId(MemTag::SegmentAtlasPlanarRegions) {} - const Basis &chartBasis(uint32_t chartIndex) const { return m_chartBasis[chartIndex]; } - uint32_t chartCount() const { return m_charts.size(); } - - ConstArrayView chartFaces(uint32_t chartIndex) const { - const Chart &chart = m_charts[chartIndex]; - return ConstArrayView(&m_chartFaces[chart.firstFace], chart.faceCount); - } - - uint32_t regionIdFromFace(uint32_t face) const { return m_faceToRegionId[face]; } - uint32_t nextRegionFace(uint32_t face) const { return m_nextRegionFace[face]; } - float regionArea(uint32_t region) const { return m_regionAreas[region]; } - - void compute() { - const uint32_t faceCount = m_data.mesh->faceCount(); - // Precompute regions of coplanar incident faces. - m_regionFirstFace.clear(); - m_nextRegionFace.resize(faceCount); - m_faceToRegionId.resize(faceCount); - for (uint32_t f = 0; f < faceCount; f++) { - m_nextRegionFace[f] = f; - m_faceToRegionId[f] = UINT32_MAX; - } - Array faceStack; - faceStack.reserve(min(faceCount, 16u)); - uint32_t regionCount = 0; - for (uint32_t f = 0; f < faceCount; f++) { - if (m_nextRegionFace[f] != f) - continue; // Already assigned. - if (m_data.isFaceInChart.get(f)) - continue; // Already in a chart. - faceStack.clear(); - faceStack.push_back(f); - for (;;) { - if (faceStack.isEmpty()) - break; - const uint32_t face = faceStack.back(); - m_faceToRegionId[face] = regionCount; - faceStack.pop_back(); - for (Mesh::FaceEdgeIterator it(m_data.mesh, face); !it.isDone(); it.advance()) { - const uint32_t oface = it.oppositeFace(); - if (it.isBoundary()) - continue; - if (m_nextRegionFace[oface] != oface) - continue; // Already assigned. - if (m_data.isFaceInChart.get(oface)) - continue; // Already in a chart. - if (!equal(dot(m_data.faceNormals[face], m_data.faceNormals[oface]), 1.0f, kEpsilon)) - continue; // Not coplanar. - const uint32_t next = m_nextRegionFace[face]; - m_nextRegionFace[face] = oface; - m_nextRegionFace[oface] = next; - m_faceToRegionId[oface] = regionCount; - faceStack.push_back(oface); - } - } - m_regionFirstFace.push_back(f); - regionCount++; - } -#if XA_DEBUG_EXPORT_OBJ_PLANAR_REGIONS - static std::mutex s_mutex; - { - std::lock_guard lock(s_mutex); - FILE *file; - XA_FOPEN(file, "debug_mesh_planar_regions.obj", s_planarRegionsCurrentRegion == 0 ? "w" : "a"); - if (file) { - m_data.mesh->writeObjVertices(file); - fprintf(file, "s off\n"); - for (uint32_t i = 0; i < regionCount; i++) { - fprintf(file, "o region%u\n", s_planarRegionsCurrentRegion); - for (uint32_t j = 0; j < faceCount; j++) { - if (m_faceToRegionId[j] == i) - m_data.mesh->writeObjFace(file, j, s_planarRegionsCurrentVertex); - } - s_planarRegionsCurrentRegion++; - } - s_planarRegionsCurrentVertex += m_data.mesh->vertexCount(); - fclose(file); - } - } -#endif - // Precompute planar region areas. - m_regionAreas.resize(regionCount); - m_regionAreas.zeroOutMemory(); - for (uint32_t f = 0; f < faceCount; f++) { - if (m_faceToRegionId[f] == UINT32_MAX) - continue; - m_regionAreas[m_faceToRegionId[f]] += m_data.faceAreas[f]; - } - // Create charts from suitable planar regions. - // The dihedral angle of all boundary edges must be >= 90 degrees. - m_charts.clear(); - m_chartFaces.clear(); - for (uint32_t region = 0; region < regionCount; region++) { - const uint32_t firstRegionFace = m_regionFirstFace[region]; - uint32_t face = firstRegionFace; - bool createChart = true; - do { - for (Mesh::FaceEdgeIterator it(m_data.mesh, face); !it.isDone(); it.advance()) { - if (it.isBoundary()) - continue; // Ignore mesh boundary edges. - const uint32_t oface = it.oppositeFace(); - if (m_faceToRegionId[oface] == region) - continue; // Ignore internal edges. - const float angle = m_data.edgeDihedralAngles[it.edge()]; - if (angle > 0.0f && angle < FLT_MAX) { // FLT_MAX on boundaries. - createChart = false; - break; - } - } - if (!createChart) - break; - face = m_nextRegionFace[face]; - } while (face != firstRegionFace); - // Create a chart. - if (createChart) { - Chart chart; - chart.firstFace = m_chartFaces.size(); - chart.faceCount = 0; - face = firstRegionFace; - do { - m_data.isFaceInChart.set(face); - m_chartFaces.push_back(face); - chart.faceCount++; - face = m_nextRegionFace[face]; - } while (face != firstRegionFace); - m_charts.push_back(chart); - } - } - // Compute basis for each chart using the first face normal (all faces have the same normal). - m_chartBasis.resize(m_charts.size()); - for (uint32_t c = 0; c < m_charts.size(); c++) { - const uint32_t face = m_chartFaces[m_charts[c].firstFace]; - Basis &basis = m_chartBasis[c]; - basis.normal = m_data.faceNormals[face]; - basis.tangent = Basis::computeTangent(basis.normal); - basis.bitangent = Basis::computeBitangent(basis.normal, basis.tangent); - } - } - -private: - struct Chart { - uint32_t firstFace, faceCount; - }; - - AtlasData &m_data; - Array m_regionFirstFace; - Array m_nextRegionFace; - Array m_faceToRegionId; - Array m_regionAreas; - Array m_charts; - Array m_chartFaces; - Array m_chartBasis; -}; - -struct ClusteredCharts { - ClusteredCharts(AtlasData &data, const PlanarCharts &planarCharts) : - m_data(data), m_planarCharts(planarCharts), m_texcoords(MemTag::SegmentAtlasMeshData), m_bestTriangles(10), m_placingSeeds(false) {} - - ~ClusteredCharts() { - const uint32_t chartCount = m_charts.size(); - for (uint32_t i = 0; i < chartCount; i++) { - m_charts[i]->~Chart(); - XA_FREE(m_charts[i]); - } - } - - uint32_t chartCount() const { return m_charts.size(); } - ConstArrayView chartFaces(uint32_t chartIndex) const { return m_charts[chartIndex]->faces; } - const Basis &chartBasis(uint32_t chartIndex) const { return m_charts[chartIndex]->basis; } - - void compute() { - const uint32_t faceCount = m_data.mesh->faceCount(); - m_facesLeft = 0; - for (uint32_t i = 0; i < faceCount; i++) { - if (!m_data.isFaceInChart.get(i)) - m_facesLeft++; - } - const uint32_t chartCount = m_charts.size(); - for (uint32_t i = 0; i < chartCount; i++) { - m_charts[i]->~Chart(); - XA_FREE(m_charts[i]); - } - m_charts.clear(); - m_faceCharts.resize(faceCount); - m_faceCharts.fill(-1); - m_texcoords.resize(faceCount * 3); - if (m_facesLeft == 0) - return; - // Create initial charts greedely. - placeSeeds(m_data.options.maxCost * 0.5f); - if (m_data.options.maxIterations == 0) { - XA_DEBUG_ASSERT(m_facesLeft == 0); - return; - } - relocateSeeds(); - resetCharts(); - // Restart process growing charts in parallel. - uint32_t iteration = 0; - for (;;) { - growCharts(m_data.options.maxCost); - // When charts cannot grow more: fill holes, merge charts, relocate seeds and start new iteration. - fillHoles(m_data.options.maxCost * 0.5f); -#if XA_MERGE_CHARTS - mergeCharts(); -#endif - if (++iteration == m_data.options.maxIterations) - break; - if (!relocateSeeds()) - break; - resetCharts(); - } - // Make sure no holes are left! - XA_DEBUG_ASSERT(m_facesLeft == 0); - } - -private: - struct Chart { - Chart() : - faces(MemTag::SegmentAtlasChartFaces) {} - - int id = -1; - Basis basis; // Best fit normal. - float area = 0.0f; - float boundaryLength = 0.0f; - Vector3 centroidSum = Vector3(0.0f); // Sum of chart face centroids. - Vector3 centroid = Vector3(0.0f); // Average centroid of chart faces. - Array faces; - Array failedPlanarRegions; - CostQueue candidates; - uint32_t seed; - }; - - void placeSeeds(float threshold) { - XA_PROFILE_START(clusteredChartsPlaceSeeds) - m_placingSeeds = true; - // Instead of using a predefiened number of seeds: - // - Add seeds one by one, growing chart until a certain treshold. - // - Undo charts and restart growing process. - // @@ How can we give preference to faces far from sharp features as in the LSCM paper? - // - those points can be found using a simple flood filling algorithm. - // - how do we weight the probabilities? - while (m_facesLeft > 0) - createChart(threshold); - m_placingSeeds = false; - XA_PROFILE_END(clusteredChartsPlaceSeeds) - } - - // Returns true if any of the charts can grow more. - void growCharts(float threshold) { - XA_PROFILE_START(clusteredChartsGrow) - for (;;) { - if (m_facesLeft == 0) - break; - // Get the single best candidate out of the chart best candidates. - uint32_t bestFace = UINT32_MAX, bestChart = UINT32_MAX; - float lowestCost = FLT_MAX; - for (uint32_t i = 0; i < m_charts.size(); i++) { - Chart *chart = m_charts[i]; - // Get the best candidate from the chart. - // Cleanup any best candidates that have been claimed by another chart. - uint32_t face = UINT32_MAX; - float cost = FLT_MAX; - for (;;) { - if (chart->candidates.count() == 0) - break; - cost = chart->candidates.peekCost(); - face = chart->candidates.peekFace(); - if (!m_data.isFaceInChart.get(face)) - break; - else { - // Face belongs to another chart. Pop from queue so the next best candidate can be retrieved. - chart->candidates.pop(); - face = UINT32_MAX; - } - } - if (face == UINT32_MAX) - continue; // No candidates for this chart. - // See if best candidate overall. - if (cost < lowestCost) { - lowestCost = cost; - bestFace = face; - bestChart = i; - } - } - if (bestFace == UINT32_MAX || lowestCost > threshold) - break; - Chart *chart = m_charts[bestChart]; - chart->candidates.pop(); // Pop the selected candidate from the queue. - if (!addFaceToChart(chart, bestFace)) - chart->failedPlanarRegions.push_back(m_planarCharts.regionIdFromFace(bestFace)); - } - XA_PROFILE_END(clusteredChartsGrow) - } - - void resetCharts() { - XA_PROFILE_START(clusteredChartsReset) - const uint32_t faceCount = m_data.mesh->faceCount(); - for (uint32_t i = 0; i < faceCount; i++) { - if (m_faceCharts[i] != -1) - m_data.isFaceInChart.unset(i); - m_faceCharts[i] = -1; - } - m_facesLeft = 0; - for (uint32_t i = 0; i < faceCount; i++) { - if (!m_data.isFaceInChart.get(i)) - m_facesLeft++; - } - const uint32_t chartCount = m_charts.size(); - for (uint32_t i = 0; i < chartCount; i++) { - Chart *chart = m_charts[i]; - chart->area = 0.0f; - chart->boundaryLength = 0.0f; - chart->basis.normal = Vector3(0.0f); - chart->basis.tangent = Vector3(0.0f); - chart->basis.bitangent = Vector3(0.0f); - chart->centroidSum = Vector3(0.0f); - chart->centroid = Vector3(0.0f); - chart->faces.clear(); - chart->candidates.clear(); - chart->failedPlanarRegions.clear(); - addFaceToChart(chart, chart->seed); - } - XA_PROFILE_END(clusteredChartsReset) - } - - bool relocateSeeds() { - XA_PROFILE_START(clusteredChartsRelocateSeeds) - bool anySeedChanged = false; - const uint32_t chartCount = m_charts.size(); - for (uint32_t i = 0; i < chartCount; i++) { - if (relocateSeed(m_charts[i])) { - anySeedChanged = true; - } - } - XA_PROFILE_END(clusteredChartsRelocateSeeds) - return anySeedChanged; - } - - void fillHoles(float threshold) { - XA_PROFILE_START(clusteredChartsFillHoles) - while (m_facesLeft > 0) - createChart(threshold); - XA_PROFILE_END(clusteredChartsFillHoles) - } - -#if XA_MERGE_CHARTS - void mergeCharts() { - XA_PROFILE_START(clusteredChartsMerge) - const uint32_t chartCount = m_charts.size(); - // Merge charts progressively until there's none left to merge. - for (;;) { - bool merged = false; - for (int c = chartCount - 1; c >= 0; c--) { - Chart *chart = m_charts[c]; - if (chart == nullptr) - continue; - float externalBoundaryLength = 0.0f; - m_sharedBoundaryLengths.resize(chartCount); - m_sharedBoundaryLengths.zeroOutMemory(); - m_sharedBoundaryLengthsNoSeams.resize(chartCount); - m_sharedBoundaryLengthsNoSeams.zeroOutMemory(); - m_sharedBoundaryEdgeCountNoSeams.resize(chartCount); - m_sharedBoundaryEdgeCountNoSeams.zeroOutMemory(); - const uint32_t faceCount = chart->faces.size(); - for (uint32_t i = 0; i < faceCount; i++) { - const uint32_t f = chart->faces[i]; - for (Mesh::FaceEdgeIterator it(m_data.mesh, f); !it.isDone(); it.advance()) { - const float l = m_data.edgeLengths[it.edge()]; - if (it.isBoundary()) { - externalBoundaryLength += l; - } else { - const int neighborChart = m_faceCharts[it.oppositeFace()]; - if (neighborChart == -1) - externalBoundaryLength += l; - else if (m_charts[neighborChart] != chart) { - if ((it.isSeam() && (isNormalSeam(it.edge()) || it.isTextureSeam()))) { - externalBoundaryLength += l; - } else { - m_sharedBoundaryLengths[neighborChart] += l; - } - m_sharedBoundaryLengthsNoSeams[neighborChart] += l; - m_sharedBoundaryEdgeCountNoSeams[neighborChart]++; - } - } - } - } - for (int cc = chartCount - 1; cc >= 0; cc--) { - if (cc == c) - continue; - Chart *chart2 = m_charts[cc]; - if (chart2 == nullptr) - continue; - // Must share a boundary. - if (m_sharedBoundaryLengths[cc] <= 0.0f) - continue; - // Compare proxies. - if (dot(chart2->basis.normal, chart->basis.normal) < XA_MERGE_CHARTS_MIN_NORMAL_DEVIATION) - continue; - // Obey max chart area and boundary length. - if (m_data.options.maxChartArea > 0.0f && chart->area + chart2->area > m_data.options.maxChartArea) - continue; - if (m_data.options.maxBoundaryLength > 0.0f && chart->boundaryLength + chart2->boundaryLength - m_sharedBoundaryLengthsNoSeams[cc] > m_data.options.maxBoundaryLength) - continue; - // Merge if chart2 has a single face. - // chart1 must have more than 1 face. - // chart2 area must be <= 10% of chart1 area. - if (m_sharedBoundaryLengthsNoSeams[cc] > 0.0f && chart->faces.size() > 1 && chart2->faces.size() == 1 && chart2->area <= chart->area * 0.1f) - goto merge; - // Merge if chart2 has two faces (probably a quad), and chart1 bounds at least 2 of its edges. - if (chart2->faces.size() == 2 && m_sharedBoundaryEdgeCountNoSeams[cc] >= 2) - goto merge; - // Merge if chart2 is wholely inside chart1, ignoring seams. - if (m_sharedBoundaryLengthsNoSeams[cc] > 0.0f && equal(m_sharedBoundaryLengthsNoSeams[cc], chart2->boundaryLength, kEpsilon)) - goto merge; - if (m_sharedBoundaryLengths[cc] > 0.2f * max(0.0f, chart->boundaryLength - externalBoundaryLength) || - m_sharedBoundaryLengths[cc] > 0.75f * chart2->boundaryLength) - goto merge; - continue; - merge: - if (!mergeChart(chart, chart2, m_sharedBoundaryLengthsNoSeams[cc])) - continue; - merged = true; - break; - } - if (merged) - break; - } - if (!merged) - break; - } - // Remove deleted charts. - for (int c = 0; c < int32_t(m_charts.size()); /*do not increment if removed*/) { - if (m_charts[c] == nullptr) { - m_charts.removeAt(c); - // Update m_faceCharts. - const uint32_t faceCount = m_faceCharts.size(); - for (uint32_t i = 0; i < faceCount; i++) { - XA_DEBUG_ASSERT(m_faceCharts[i] != c); - XA_DEBUG_ASSERT(m_faceCharts[i] <= int32_t(m_charts.size())); - if (m_faceCharts[i] > c) { - m_faceCharts[i]--; - } - } - } else { - m_charts[c]->id = c; - c++; - } - } - XA_PROFILE_END(clusteredChartsMerge) - } -#endif - -private: - void createChart(float threshold) { - Chart *chart = XA_NEW(MemTag::Default, Chart); - chart->id = (int)m_charts.size(); - m_charts.push_back(chart); - // Pick a face not used by any chart yet, belonging to the largest planar region. - chart->seed = 0; - float largestArea = 0.0f; - for (uint32_t f = 0; f < m_data.mesh->faceCount(); f++) { - if (m_data.isFaceInChart.get(f)) - continue; - const float area = m_planarCharts.regionArea(m_planarCharts.regionIdFromFace(f)); - if (area > largestArea) { - largestArea = area; - chart->seed = f; - } - } - addFaceToChart(chart, chart->seed); - // Grow the chart as much as possible within the given threshold. - for (;;) { - if (chart->candidates.count() == 0 || chart->candidates.peekCost() > threshold) - break; - const uint32_t f = chart->candidates.pop(); - if (m_data.isFaceInChart.get(f)) - continue; - if (!addFaceToChart(chart, f)) { - chart->failedPlanarRegions.push_back(m_planarCharts.regionIdFromFace(f)); - continue; - } - } - } - - bool isChartBoundaryEdge(const Chart *chart, uint32_t edge) const { - const uint32_t oppositeEdge = m_data.mesh->oppositeEdge(edge); - const uint32_t oppositeFace = meshEdgeFace(oppositeEdge); - return oppositeEdge == UINT32_MAX || m_faceCharts[oppositeFace] != chart->id; - } - - bool computeChartBasis(Chart *chart, Basis *basis) { - const uint32_t faceCount = chart->faces.size(); - m_tempPoints.resize(chart->faces.size() * 3); - for (uint32_t i = 0; i < faceCount; i++) { - const uint32_t f = chart->faces[i]; - for (uint32_t j = 0; j < 3; j++) - m_tempPoints[i * 3 + j] = m_data.mesh->position(m_data.mesh->vertexAt(f * 3 + j)); - } - return Fit::computeBasis(m_tempPoints, basis); - } - - bool isFaceFlipped(uint32_t face) const { - const Vector2 &v1 = m_texcoords[face * 3 + 0]; - const Vector2 &v2 = m_texcoords[face * 3 + 1]; - const Vector2 &v3 = m_texcoords[face * 3 + 2]; - const float parametricArea = ((v2.x - v1.x) * (v3.y - v1.y) - (v3.x - v1.x) * (v2.y - v1.y)) * 0.5f; - return parametricArea < 0.0f; - } - - void parameterizeChart(const Chart *chart) { - const uint32_t faceCount = chart->faces.size(); - for (uint32_t i = 0; i < faceCount; i++) { - const uint32_t face = chart->faces[i]; - for (uint32_t j = 0; j < 3; j++) { - const uint32_t offset = face * 3 + j; - const Vector3 &pos = m_data.mesh->position(m_data.mesh->vertexAt(offset)); - m_texcoords[offset] = Vector2(dot(chart->basis.tangent, pos), dot(chart->basis.bitangent, pos)); - } - } - } - - // m_faceCharts for the chart faces must be set to the chart ID. Needed to compute boundary edges. - bool isChartParameterizationValid(const Chart *chart) { - const uint32_t faceCount = chart->faces.size(); - // Check for flipped faces in the parameterization. OK if all are flipped. - uint32_t flippedFaceCount = 0; - for (uint32_t i = 0; i < faceCount; i++) { - if (isFaceFlipped(chart->faces[i])) - flippedFaceCount++; - } - if (flippedFaceCount != 0 && flippedFaceCount != faceCount) - return false; - // Check for boundary intersection in the parameterization. - XA_PROFILE_START(clusteredChartsPlaceSeedsBoundaryIntersection) - XA_PROFILE_START(clusteredChartsGrowBoundaryIntersection) - m_boundaryGrid.reset(m_texcoords); - for (uint32_t i = 0; i < faceCount; i++) { - const uint32_t f = chart->faces[i]; - for (uint32_t j = 0; j < 3; j++) { - const uint32_t edge = f * 3 + j; - if (isChartBoundaryEdge(chart, edge)) - m_boundaryGrid.append(edge); - } - } - const bool intersection = m_boundaryGrid.intersect(m_data.mesh->epsilon()); -#if XA_PROFILE - if (m_placingSeeds) - XA_PROFILE_END(clusteredChartsPlaceSeedsBoundaryIntersection) - else - XA_PROFILE_END(clusteredChartsGrowBoundaryIntersection) -#endif - if (intersection) - return false; - return true; - } - - bool addFaceToChart(Chart *chart, uint32_t face) { - XA_DEBUG_ASSERT(!m_data.isFaceInChart.get(face)); - const uint32_t oldFaceCount = chart->faces.size(); - const bool firstFace = oldFaceCount == 0; - // Append the face and any coplanar connected faces to the chart faces array. - chart->faces.push_back(face); - uint32_t coplanarFace = m_planarCharts.nextRegionFace(face); - while (coplanarFace != face) { - XA_DEBUG_ASSERT(!m_data.isFaceInChart.get(coplanarFace)); - chart->faces.push_back(coplanarFace); - coplanarFace = m_planarCharts.nextRegionFace(coplanarFace); - } - const uint32_t faceCount = chart->faces.size(); - // Compute basis. - Basis basis; - if (firstFace) { - // Use the first face normal. - // Use any edge as the tangent vector. - basis.normal = m_data.faceNormals[face]; - basis.tangent = normalize(m_data.mesh->position(m_data.mesh->vertexAt(face * 3 + 0)) - m_data.mesh->position(m_data.mesh->vertexAt(face * 3 + 1))); - basis.bitangent = cross(basis.normal, basis.tangent); - } else { - // Use best fit normal. - if (!computeChartBasis(chart, &basis)) { - chart->faces.resize(oldFaceCount); - return false; - } - if (dot(basis.normal, m_data.faceNormals[face]) < 0.0f) // Flip normal if oriented in the wrong direction. - basis.normal = -basis.normal; - } - if (!firstFace) { - // Compute orthogonal parameterization and check that it is valid. - parameterizeChart(chart); - for (uint32_t i = oldFaceCount; i < faceCount; i++) - m_faceCharts[chart->faces[i]] = chart->id; - if (!isChartParameterizationValid(chart)) { - for (uint32_t i = oldFaceCount; i < faceCount; i++) - m_faceCharts[chart->faces[i]] = -1; - chart->faces.resize(oldFaceCount); - return false; - } - } - // Add face(s) to chart. - chart->basis = basis; - chart->area = computeArea(chart, face); - chart->boundaryLength = computeBoundaryLength(chart, face); - for (uint32_t i = oldFaceCount; i < faceCount; i++) { - const uint32_t f = chart->faces[i]; - m_faceCharts[f] = chart->id; - m_facesLeft--; - m_data.isFaceInChart.set(f); - chart->centroidSum += m_data.mesh->computeFaceCenter(f); - } - chart->centroid = chart->centroidSum / float(chart->faces.size()); - // Refresh candidates. - chart->candidates.clear(); - for (uint32_t i = 0; i < faceCount; i++) { - // Traverse neighboring faces, add the ones that do not belong to any chart yet. - const uint32_t f = chart->faces[i]; - for (uint32_t j = 0; j < 3; j++) { - const uint32_t edge = f * 3 + j; - const uint32_t oedge = m_data.mesh->oppositeEdge(edge); - if (oedge == UINT32_MAX) - continue; // Boundary edge. - const uint32_t oface = meshEdgeFace(oedge); - if (m_data.isFaceInChart.get(oface)) - continue; // Face belongs to another chart. - if (chart->failedPlanarRegions.contains(m_planarCharts.regionIdFromFace(oface))) - continue; // Failed to add this faces planar region to the chart before. - const float cost = computeCost(chart, oface); - if (cost < FLT_MAX) - chart->candidates.push(cost, oface); - } - } - return true; - } - - // Returns true if the seed has changed. - bool relocateSeed(Chart *chart) { - // Find the first N triangles that fit the proxy best. - const uint32_t faceCount = chart->faces.size(); - m_bestTriangles.clear(); - for (uint32_t i = 0; i < faceCount; i++) { - const float cost = computeNormalDeviationMetric(chart, chart->faces[i]); - m_bestTriangles.push(cost, chart->faces[i]); - } - // Of those, choose the most central triangle. - uint32_t mostCentral = 0; - float minDistance = FLT_MAX; - for (;;) { - if (m_bestTriangles.count() == 0) - break; - const uint32_t face = m_bestTriangles.pop(); - Vector3 faceCentroid = m_data.mesh->computeFaceCenter(face); - const float distance = length(chart->centroid - faceCentroid); - if (distance < minDistance) { - minDistance = distance; - mostCentral = face; - } - } - XA_DEBUG_ASSERT(minDistance < FLT_MAX); - if (mostCentral == chart->seed) - return false; - chart->seed = mostCentral; - return true; - } - - // Cost is combined metrics * weights. - float computeCost(Chart *chart, uint32_t face) const { - // Estimate boundary length and area: - const float newChartArea = computeArea(chart, face); - const float newBoundaryLength = computeBoundaryLength(chart, face); - // Enforce limits strictly: - if (m_data.options.maxChartArea > 0.0f && newChartArea > m_data.options.maxChartArea) - return FLT_MAX; - if (m_data.options.maxBoundaryLength > 0.0f && newBoundaryLength > m_data.options.maxBoundaryLength) - return FLT_MAX; - // Compute metrics. - float cost = 0.0f; - const float normalDeviation = computeNormalDeviationMetric(chart, face); - if (normalDeviation >= 0.707f) // ~75 degrees - return FLT_MAX; - cost += m_data.options.normalDeviationWeight * normalDeviation; - // Penalize faces that cross seams, reward faces that close seams or reach boundaries. - // Make sure normal seams are fully respected: - const float normalSeam = computeNormalSeamMetric(chart, face); - if (m_data.options.normalSeamWeight >= 1000.0f && normalSeam > 0.0f) - return FLT_MAX; - cost += m_data.options.normalSeamWeight * normalSeam; - cost += m_data.options.roundnessWeight * computeRoundnessMetric(chart, newBoundaryLength, newChartArea); - cost += m_data.options.straightnessWeight * computeStraightnessMetric(chart, face); - cost += m_data.options.textureSeamWeight * computeTextureSeamMetric(chart, face); - //float R = evaluateCompletenessMetric(chart, face); - //float D = evaluateDihedralAngleMetric(chart, face); - // @@ Add a metric based on local dihedral angle. - // @@ Tweaking the normal and texture seam metrics. - // - Cause more impedance. Never cross 90 degree edges. - XA_DEBUG_ASSERT(isFinite(cost)); - return cost; - } - - // Returns a value in [0-1]. - // 0 if face normal is coplanar to the chart's best fit normal. - // 1 if face normal is perpendicular. - float computeNormalDeviationMetric(Chart *chart, uint32_t face) const { - // All faces in coplanar regions have the same normal, can use any face. - const Vector3 faceNormal = m_data.faceNormals[face]; - // Use plane fitting metric for now: - return min(1.0f - dot(faceNormal, chart->basis.normal), 1.0f); // @@ normal deviations should be weighted by face area - } - - float computeRoundnessMetric(Chart *chart, float newBoundaryLength, float newChartArea) const { - const float oldRoundness = square(chart->boundaryLength) / chart->area; - const float newRoundness = square(newBoundaryLength) / newChartArea; - return 1.0f - oldRoundness / newRoundness; - } - - float computeStraightnessMetric(Chart *chart, uint32_t firstFace) const { - float l_out = 0.0f; // Length of firstFace planar region boundary that doesn't border the chart. - float l_in = 0.0f; // Length that does border the chart. - const uint32_t planarRegionId = m_planarCharts.regionIdFromFace(firstFace); - uint32_t face = firstFace; - for (;;) { - for (Mesh::FaceEdgeIterator it(m_data.mesh, face); !it.isDone(); it.advance()) { - const float l = m_data.edgeLengths[it.edge()]; - if (it.isBoundary()) { - l_out += l; - } else if (m_planarCharts.regionIdFromFace(it.oppositeFace()) != planarRegionId) { - if (m_faceCharts[it.oppositeFace()] != chart->id) - l_out += l; - else - l_in += l; - } - } - face = m_planarCharts.nextRegionFace(face); - if (face == firstFace) - break; - } -#if 1 - float ratio = (l_out - l_in) / (l_out + l_in); - return min(ratio, 0.0f); // Only use the straightness metric to close gaps. -#else - return 1.0f - l_in / l_out; -#endif - } - - bool isNormalSeam(uint32_t edge) const { - const uint32_t oppositeEdge = m_data.mesh->oppositeEdge(edge); - if (oppositeEdge == UINT32_MAX) - return false; // boundary edge - if (m_data.mesh->flags() & MeshFlags::HasNormals) { - const uint32_t v0 = m_data.mesh->vertexAt(meshEdgeIndex0(edge)); - const uint32_t v1 = m_data.mesh->vertexAt(meshEdgeIndex1(edge)); - const uint32_t ov0 = m_data.mesh->vertexAt(meshEdgeIndex0(oppositeEdge)); - const uint32_t ov1 = m_data.mesh->vertexAt(meshEdgeIndex1(oppositeEdge)); - if (v0 == ov1 && v1 == ov0) - return false; - return !equal(m_data.mesh->normal(v0), m_data.mesh->normal(ov1), kNormalEpsilon) || !equal(m_data.mesh->normal(v1), m_data.mesh->normal(ov0), kNormalEpsilon); - } - const uint32_t f0 = meshEdgeFace(edge); - const uint32_t f1 = meshEdgeFace(oppositeEdge); - if (m_planarCharts.regionIdFromFace(f0) == m_planarCharts.regionIdFromFace(f1)) - return false; - return !equal(m_data.faceNormals[f0], m_data.faceNormals[f1], kNormalEpsilon); - } - - float computeNormalSeamMetric(Chart *chart, uint32_t firstFace) const { - float seamFactor = 0.0f, totalLength = 0.0f; - uint32_t face = firstFace; - for (;;) { - for (Mesh::FaceEdgeIterator it(m_data.mesh, face); !it.isDone(); it.advance()) { - if (it.isBoundary()) - continue; - if (m_faceCharts[it.oppositeFace()] != chart->id) - continue; - float l = m_data.edgeLengths[it.edge()]; - totalLength += l; - if (!it.isSeam()) - continue; - // Make sure it's a normal seam. - if (isNormalSeam(it.edge())) { - float d; - if (m_data.mesh->flags() & MeshFlags::HasNormals) { - const Vector3 &n0 = m_data.mesh->normal(it.vertex0()); - const Vector3 &n1 = m_data.mesh->normal(it.vertex1()); - const Vector3 &on0 = m_data.mesh->normal(m_data.mesh->vertexAt(meshEdgeIndex0(it.oppositeEdge()))); - const Vector3 &on1 = m_data.mesh->normal(m_data.mesh->vertexAt(meshEdgeIndex1(it.oppositeEdge()))); - const float d0 = clamp(dot(n0, on1), 0.0f, 1.0f); - const float d1 = clamp(dot(n1, on0), 0.0f, 1.0f); - d = (d0 + d1) * 0.5f; - } else { - d = clamp(dot(m_data.faceNormals[face], m_data.faceNormals[meshEdgeFace(it.oppositeEdge())]), 0.0f, 1.0f); - } - l *= 1 - d; - seamFactor += l; - } - } - face = m_planarCharts.nextRegionFace(face); - if (face == firstFace) - break; - } - if (seamFactor <= 0.0f) - return 0.0f; - return seamFactor / totalLength; - } - - float computeTextureSeamMetric(Chart *chart, uint32_t firstFace) const { - float seamLength = 0.0f, totalLength = 0.0f; - uint32_t face = firstFace; - for (;;) { - for (Mesh::FaceEdgeIterator it(m_data.mesh, face); !it.isDone(); it.advance()) { - if (it.isBoundary()) - continue; - if (m_faceCharts[it.oppositeFace()] != chart->id) - continue; - float l = m_data.edgeLengths[it.edge()]; - totalLength += l; - if (!it.isSeam()) - continue; - // Make sure it's a texture seam. - if (it.isTextureSeam()) - seamLength += l; - } - face = m_planarCharts.nextRegionFace(face); - if (face == firstFace) - break; - } - if (seamLength <= 0.0f) - return 0.0f; // Avoid division by zero. - return seamLength / totalLength; - } - - float computeArea(Chart *chart, uint32_t firstFace) const { - float area = chart->area; - uint32_t face = firstFace; - for (;;) { - area += m_data.faceAreas[face]; - face = m_planarCharts.nextRegionFace(face); - if (face == firstFace) - break; - } - return area; - } - - float computeBoundaryLength(Chart *chart, uint32_t firstFace) const { - float boundaryLength = chart->boundaryLength; - // Add new edges, subtract edges shared with the chart. - const uint32_t planarRegionId = m_planarCharts.regionIdFromFace(firstFace); - uint32_t face = firstFace; - for (;;) { - for (Mesh::FaceEdgeIterator it(m_data.mesh, face); !it.isDone(); it.advance()) { - const float edgeLength = m_data.edgeLengths[it.edge()]; - if (it.isBoundary()) { - boundaryLength += edgeLength; - } else if (m_planarCharts.regionIdFromFace(it.oppositeFace()) != planarRegionId) { - if (m_faceCharts[it.oppositeFace()] != chart->id) - boundaryLength += edgeLength; - else - boundaryLength -= edgeLength; - } - } - face = m_planarCharts.nextRegionFace(face); - if (face == firstFace) - break; - } - return max(0.0f, boundaryLength); // @@ Hack! - } - - bool mergeChart(Chart *owner, Chart *chart, float sharedBoundaryLength) { - const uint32_t oldOwnerFaceCount = owner->faces.size(); - const uint32_t chartFaceCount = chart->faces.size(); - owner->faces.push_back(chart->faces); - for (uint32_t i = 0; i < chartFaceCount; i++) { - XA_DEBUG_ASSERT(m_faceCharts[chart->faces[i]] == chart->id); - m_faceCharts[chart->faces[i]] = owner->id; - } - // Compute basis using best fit normal. - Basis basis; - if (!computeChartBasis(owner, &basis)) { - owner->faces.resize(oldOwnerFaceCount); - for (uint32_t i = 0; i < chartFaceCount; i++) - m_faceCharts[chart->faces[i]] = chart->id; - return false; - } - if (dot(basis.normal, m_data.faceNormals[owner->faces[0]]) < 0.0f) // Flip normal if oriented in the wrong direction. - basis.normal = -basis.normal; - // Compute orthogonal parameterization and check that it is valid. - parameterizeChart(owner); - if (!isChartParameterizationValid(owner)) { - owner->faces.resize(oldOwnerFaceCount); - for (uint32_t i = 0; i < chartFaceCount; i++) - m_faceCharts[chart->faces[i]] = chart->id; - return false; - } - // Merge chart. - owner->basis = basis; - owner->failedPlanarRegions.push_back(chart->failedPlanarRegions); - // Update adjacencies? - owner->area += chart->area; - owner->boundaryLength += chart->boundaryLength - sharedBoundaryLength; - // Delete chart. - m_charts[chart->id] = nullptr; - chart->~Chart(); - XA_FREE(chart); - return true; - } - -private: - AtlasData &m_data; - const PlanarCharts &m_planarCharts; - Array m_texcoords; - uint32_t m_facesLeft; - Array m_faceCharts; - Array m_charts; - CostQueue m_bestTriangles; - Array m_tempPoints; - UniformGrid2 m_boundaryGrid; -#if XA_MERGE_CHARTS - // mergeCharts - Array m_sharedBoundaryLengths; - Array m_sharedBoundaryLengthsNoSeams; - Array m_sharedBoundaryEdgeCountNoSeams; -#endif - bool m_placingSeeds; -}; - struct ChartGeneratorType { enum Enum { OriginalUv, @@ -5578,39 +4653,22 @@ struct ChartGeneratorType { struct Atlas { Atlas() : - m_originalUvCharts(m_data), m_planarCharts(m_data), m_clusteredCharts(m_data, m_planarCharts) {} + m_originalUvCharts(m_data) {} uint32_t chartCount() const { - return m_originalUvCharts.chartCount() + m_planarCharts.chartCount() + m_clusteredCharts.chartCount(); + return m_originalUvCharts.chartCount(); } ConstArrayView chartFaces(uint32_t chartIndex) const { - if (chartIndex < m_originalUvCharts.chartCount()) - return m_originalUvCharts.chartFaces(chartIndex); - chartIndex -= m_originalUvCharts.chartCount(); - if (chartIndex < m_planarCharts.chartCount()) - return m_planarCharts.chartFaces(chartIndex); - chartIndex -= m_planarCharts.chartCount(); - return m_clusteredCharts.chartFaces(chartIndex); + return m_originalUvCharts.chartFaces(chartIndex); } const Basis &chartBasis(uint32_t chartIndex) const { - if (chartIndex < m_originalUvCharts.chartCount()) - return m_originalUvCharts.chartBasis(chartIndex); - chartIndex -= m_originalUvCharts.chartCount(); - if (chartIndex < m_planarCharts.chartCount()) - return m_planarCharts.chartBasis(chartIndex); - chartIndex -= m_planarCharts.chartCount(); - return m_clusteredCharts.chartBasis(chartIndex); + return m_originalUvCharts.chartBasis(chartIndex); } ChartGeneratorType::Enum chartGeneratorType(uint32_t chartIndex) const { - if (chartIndex < m_originalUvCharts.chartCount()) return ChartGeneratorType::OriginalUv; - chartIndex -= m_originalUvCharts.chartCount(); - if (chartIndex < m_planarCharts.chartCount()) - return ChartGeneratorType::Planar; - return ChartGeneratorType::Clustered; } void reset(const Mesh *mesh, const ChartOptions &options) { @@ -5622,24 +4680,14 @@ struct Atlas { } void compute() { - if (m_data.options.useInputMeshUvs) { XA_PROFILE_START(originalUvCharts) m_originalUvCharts.compute(); XA_PROFILE_END(originalUvCharts) - } - XA_PROFILE_START(planarCharts) - m_planarCharts.compute(); - XA_PROFILE_END(planarCharts) - XA_PROFILE_START(clusteredCharts) - m_clusteredCharts.compute(); - XA_PROFILE_END(clusteredCharts) } private: AtlasData m_data; OriginalUvCharts m_originalUvCharts; - PlanarCharts m_planarCharts; - ClusteredCharts m_clusteredCharts; }; struct ComputeUvMeshChartsTaskArgs {