From e74bbbcd3bbd47a890f9d69228758e58c6a12d13 Mon Sep 17 00:00:00 2001 From: mifth Date: Sun, 27 Oct 2024 21:44:51 +0100 Subject: [PATCH 01/17] Fixes for Quads and NGons. --- source/xatlas/xatlas.cpp | 42 +++++++++++++++++++++++++++++----------- 1 file changed, 31 insertions(+), 11 deletions(-) diff --git a/source/xatlas/xatlas.cpp b/source/xatlas/xatlas.cpp index 5c5c57e..3804599 100644 --- a/source/xatlas/xatlas.cpp +++ b/source/xatlas/xatlas.cpp @@ -3459,16 +3459,19 @@ struct Triangulator { // This is doing a simple ear-clipping algorithm that skips invalid triangles. Ideally, we should // also sort the ears by angle, start with the ones that have the smallest angle and proceed in order. - void triangulatePolygon(ConstArrayView vertices, ConstArrayView inputIndices, Array &outputIndices) + void triangulatePolygon(ConstArrayView vertices, const Array& inputIndices, Array &outputIndices, Array &outindicesIDs) { m_polygonVertices.clear(); - m_polygonVertices.reserve(inputIndices.length); + m_polygonVertices.reserve(inputIndices.size()); outputIndices.clear(); - if (inputIndices.length == 3) { + if (inputIndices.size() == 3) { // Simple case for triangles. outputIndices.push_back(inputIndices[0]); outputIndices.push_back(inputIndices[1]); outputIndices.push_back(inputIndices[2]); + outindicesIDs.push_back(0); + outindicesIDs.push_back(1); + outindicesIDs.push_back(2); } else { // Build 2D polygon projecting vertices onto normal plane. @@ -3478,15 +3481,18 @@ struct Triangulator basis.normal = normalize(cross(vertices[inputIndices[1]] - vertices[inputIndices[0]], vertices[inputIndices[2]] - vertices[inputIndices[1]])); basis.tangent = basis.computeTangent(basis.normal); basis.bitangent = basis.computeBitangent(basis.normal, basis.tangent); - const uint32_t edgeCount = inputIndices.length; + const uint32_t edgeCount = inputIndices.size(); m_polygonPoints.clear(); m_polygonPoints.reserve(edgeCount); m_polygonAngles.clear(); m_polygonAngles.reserve(edgeCount); - for (uint32_t i = 0; i < inputIndices.length; i++) { + // A temporary array for indicesIDs + Array indicesIDsTmp; + for (uint32_t i = 0; i < inputIndices.size(); i++) { m_polygonVertices.push_back(inputIndices[i]); const Vector3 &pos = vertices[inputIndices[i]]; m_polygonPoints.push_back(Vector2(dot(basis.tangent, pos), dot(basis.bitangent, pos))); + indicesIDsTmp.push_back(i); } m_polygonAngles.resize(edgeCount); while (m_polygonVertices.size() > 2) { @@ -3534,7 +3540,11 @@ struct Triangulator outputIndices.push_back(m_polygonVertices[i0]); outputIndices.push_back(m_polygonVertices[i1]); outputIndices.push_back(m_polygonVertices[i2]); + outindicesIDs.push_back(indicesIDsTmp[i0]); // Add Index of Array of Vertex Indices + outindicesIDs.push_back(indicesIDsTmp[i1]); + outindicesIDs.push_back(indicesIDsTmp[i2]); m_polygonVertices.removeAt(i1); + indicesIDsTmp.removeAt(i1); m_polygonPoints.removeAt(i1); m_polygonAngles.removeAt(i1); } @@ -9086,14 +9096,18 @@ AddMeshError AddMesh(Atlas *atlas, const MeshDecl &meshDecl, uint32_t meshCountH const uint32_t kMaxWarnings = 50; uint32_t warningCount = 0; internal::Array triIndices; + internal::Array indicesIDs; internal::Triangulator triangulator; + uint32_t polygonStartID = 0; + internal::Array polygon; for (uint32_t face = 0; face < faceCount; face++) { // Decode face indices. const uint32_t faceVertexCount = meshDecl.faceVertexCount ? (uint32_t)meshDecl.faceVertexCount[face] : 3; - uint32_t polygon[UINT8_MAX]; + polygon.clear(); for (uint32_t i = 0; i < faceVertexCount; i++) { if (hasIndices) { - polygon[i] = DecodeIndex(meshDecl.indexFormat, meshDecl.indexData, meshDecl.indexOffset, face * faceVertexCount + i); + uint32_t vertID = DecodeIndex(meshDecl.indexFormat, meshDecl.indexData, meshDecl.indexOffset, polygonStartID + i); + polygon.push_back(vertID); // Check if any index is out of range. if (polygon[i] >= meshDecl.vertexCount) { mesh->~Mesh(); @@ -9101,14 +9115,15 @@ AddMeshError AddMesh(Atlas *atlas, const MeshDecl &meshDecl, uint32_t meshCountH return AddMeshError::IndexOutOfRange; } } else { - polygon[i] = face * faceVertexCount + i; + polygon[i] = polygonStartID + i; } } + // Ignore faces with degenerate or zero length edges. bool ignore = false; for (uint32_t i = 0; i < faceVertexCount; i++) { const uint32_t index1 = polygon[i]; - const uint32_t index2 = polygon[(i + 1) % 3]; + const uint32_t index2 = polygon[(i + 1) % faceVertexCount]; if (index1 == index2) { ignore = true; if (++warningCount <= kMaxWarnings) @@ -9156,12 +9171,16 @@ AddMeshError AddMesh(Atlas *atlas, const MeshDecl &meshDecl, uint32_t meshCountH } // Triangulate if necessary. triIndices.clear(); + indicesIDs.clear(); if (faceVertexCount == 3) { triIndices.push_back(polygon[0]); triIndices.push_back(polygon[1]); triIndices.push_back(polygon[2]); + indicesIDs.push_back(0); + indicesIDs.push_back(1); + indicesIDs.push_back(2); } else { - triangulator.triangulatePolygon(mesh->positions(), internal::ConstArrayView(polygon, faceVertexCount), triIndices); + triangulator.triangulatePolygon(mesh->positions(), polygon, triIndices, indicesIDs); } // Check for zero area faces. if (!ignore) { @@ -9193,8 +9212,9 @@ AddMeshError AddMesh(Atlas *atlas, const MeshDecl &meshDecl, uint32_t meshCountH } if (meshPolygonMapping) { for (uint32_t i = 0; i < triIndices.size(); i++) - meshPolygonMapping->triangleToPolygonIndicesMap.push_back(triIndices[i]); + meshPolygonMapping->triangleToPolygonIndicesMap.push_back(indicesIDs[i] + polygonStartID); } + polygonStartID += faceVertexCount; // Add an offset for the next polygon's start index } if (warningCount > kMaxWarnings) XA_PRINT(" %u additional warnings truncated\n", warningCount - kMaxWarnings); From 83c6c45d306a1fb57af4841fc829a0409e8225ce Mon Sep 17 00:00:00 2001 From: mifth Date: Sun, 27 Oct 2024 23:22:03 +0100 Subject: [PATCH 02/17] Removed an empty line --- source/xatlas/xatlas.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/source/xatlas/xatlas.cpp b/source/xatlas/xatlas.cpp index 3804599..44be98c 100644 --- a/source/xatlas/xatlas.cpp +++ b/source/xatlas/xatlas.cpp @@ -9118,7 +9118,6 @@ AddMeshError AddMesh(Atlas *atlas, const MeshDecl &meshDecl, uint32_t meshCountH polygon[i] = polygonStartID + i; } } - // Ignore faces with degenerate or zero length edges. bool ignore = false; for (uint32_t i = 0; i < faceVertexCount; i++) { From 69b139da1f220c5177811092ba57430a9a5a2ec7 Mon Sep 17 00:00:00 2001 From: mifth Date: Mon, 28 Oct 2024 12:28:31 +0100 Subject: [PATCH 03/17] Set faceVertexCount=uint32_t as potentially NGon can have 1000000 indices. --- source/xatlas/xatlas.cpp | 2 +- source/xatlas/xatlas.h | 2 +- source/xatlas/xatlas_c.h | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/source/xatlas/xatlas.cpp b/source/xatlas/xatlas.cpp index 44be98c..20a67a2 100644 --- a/source/xatlas/xatlas.cpp +++ b/source/xatlas/xatlas.cpp @@ -8880,7 +8880,7 @@ struct Atlas // Used to map triangulated polygons back to polygons. struct MeshPolygonMapping { - internal::Array faceVertexCount; // Copied from MeshDecl::faceVertexCount. + internal::Array faceVertexCount; // Copied from MeshDecl::faceVertexCount. internal::Array triangleToPolygonMap; // Triangle index (mesh face index) to polygon index. internal::Array triangleToPolygonIndicesMap; // Triangle indices to polygon indices. }; diff --git a/source/xatlas/xatlas.h b/source/xatlas/xatlas.h index d66a96d..18b794b 100644 --- a/source/xatlas/xatlas.h +++ b/source/xatlas/xatlas.h @@ -123,7 +123,7 @@ struct MeshDecl // Optional. Must be faceCount in length. // Polygon / n-gon support. Faces are assumed to be triangles if this is null. - const uint8_t *faceVertexCount = nullptr; + const uint32_t *faceVertexCount = nullptr; uint32_t vertexCount = 0; uint32_t vertexPositionStride = 0; diff --git a/source/xatlas/xatlas_c.h b/source/xatlas/xatlas_c.h index b87edd4..55390e2 100644 --- a/source/xatlas/xatlas_c.h +++ b/source/xatlas/xatlas_c.h @@ -139,7 +139,7 @@ typedef struct const void *indexData; const bool *faceIgnoreData; const uint32_t *faceMaterialData; - const uint8_t *faceVertexCount; + const uint32_t *faceVertexCount; uint32_t vertexCount; uint32_t vertexPositionStride; uint32_t vertexNormalStride; From 45d673cc898a9ab64dd292f546679fa7454db43e Mon Sep 17 00:00:00 2001 From: mifth Date: Tue, 29 Oct 2024 21:58:00 +0100 Subject: [PATCH 04/17] The indicesIDs array is moved to the Triangulated struct and has a reserve() line. --- source/xatlas/xatlas.cpp | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/source/xatlas/xatlas.cpp b/source/xatlas/xatlas.cpp index 20a67a2..da994f5 100644 --- a/source/xatlas/xatlas.cpp +++ b/source/xatlas/xatlas.cpp @@ -3486,13 +3486,13 @@ struct Triangulator m_polygonPoints.reserve(edgeCount); m_polygonAngles.clear(); m_polygonAngles.reserve(edgeCount); - // A temporary array for indicesIDs - Array indicesIDsTmp; + m_indicesIDs.clear(); + m_indicesIDs.reserve(edgeCount); for (uint32_t i = 0; i < inputIndices.size(); i++) { m_polygonVertices.push_back(inputIndices[i]); const Vector3 &pos = vertices[inputIndices[i]]; m_polygonPoints.push_back(Vector2(dot(basis.tangent, pos), dot(basis.bitangent, pos))); - indicesIDsTmp.push_back(i); + m_indicesIDs.push_back(i); } m_polygonAngles.resize(edgeCount); while (m_polygonVertices.size() > 2) { @@ -3540,11 +3540,11 @@ struct Triangulator outputIndices.push_back(m_polygonVertices[i0]); outputIndices.push_back(m_polygonVertices[i1]); outputIndices.push_back(m_polygonVertices[i2]); - outindicesIDs.push_back(indicesIDsTmp[i0]); // Add Index of Array of Vertex Indices - outindicesIDs.push_back(indicesIDsTmp[i1]); - outindicesIDs.push_back(indicesIDsTmp[i2]); + outindicesIDs.push_back(m_indicesIDs[i0]); // Add Index of Array of Vertex Indices + outindicesIDs.push_back(m_indicesIDs[i1]); + outindicesIDs.push_back(m_indicesIDs[i2]); m_polygonVertices.removeAt(i1); - indicesIDsTmp.removeAt(i1); + m_indicesIDs.removeAt(i1); m_polygonPoints.removeAt(i1); m_polygonAngles.removeAt(i1); } @@ -3560,6 +3560,7 @@ struct Triangulator Array m_polygonVertices; Array m_polygonAngles; Array m_polygonPoints; + Array m_indicesIDs; // indexes of polygon's Indices array }; class UniformGrid2 From 0a96bd145255d3dd47c00b581d3b9933602795d0 Mon Sep 17 00:00:00 2001 From: mifth Date: Tue, 29 Oct 2024 22:37:32 +0100 Subject: [PATCH 05/17] Fix for a zero area calculation for NGons. https://github.com/jpcy/xatlas/issues/135#issuecomment-2445328029 --- source/xatlas/xatlas.cpp | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/source/xatlas/xatlas.cpp b/source/xatlas/xatlas.cpp index da994f5..5153a24 100644 --- a/source/xatlas/xatlas.cpp +++ b/source/xatlas/xatlas.cpp @@ -9184,17 +9184,18 @@ AddMeshError AddMesh(Atlas *atlas, const MeshDecl &meshDecl, uint32_t meshCountH } // Check for zero area faces. if (!ignore) { + float area; // Area of all triangles of the polygon. for (uint32_t i = 0; i < triIndices.size(); i += 3) { const internal::Vector3 &a = mesh->position(triIndices[i + 0]); const internal::Vector3 &b = mesh->position(triIndices[i + 1]); const internal::Vector3 &c = mesh->position(triIndices[i + 2]); - const float area = internal::length(internal::cross(b - a, c - a)) * 0.5f; - if (area <= internal::kAreaEpsilon) { - ignore = true; - if (++warningCount <= kMaxWarnings) - XA_PRINT(" Zero area face: %d, area is %f\n", face, area); - break; - } + area += internal::length(internal::cross(b - a, c - a)) * 0.5f; + if (area > internal::kAreaEpsilon) break; // Optimisation for high polygonal NGons + } + if (area <= internal::kAreaEpsilon) { + ignore = true; + if (++warningCount <= kMaxWarnings) + XA_PRINT(" Zero area face: %d, area is %f\n", face, area); } } // User face ignore. From 710357a8ec7ab027051296877a0b0d7aca5a2326 Mon Sep 17 00:00:00 2001 From: mifth Date: Wed, 30 Oct 2024 00:46:11 +0100 Subject: [PATCH 06/17] fix: initialised "area" variable. --- source/xatlas/xatlas.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/xatlas/xatlas.cpp b/source/xatlas/xatlas.cpp index 5153a24..4302b62 100644 --- a/source/xatlas/xatlas.cpp +++ b/source/xatlas/xatlas.cpp @@ -9184,7 +9184,7 @@ AddMeshError AddMesh(Atlas *atlas, const MeshDecl &meshDecl, uint32_t meshCountH } // Check for zero area faces. if (!ignore) { - float area; // Area of all triangles of the polygon. + float area = 0.0f; // Area of all triangles of the polygon. for (uint32_t i = 0; i < triIndices.size(); i += 3) { const internal::Vector3 &a = mesh->position(triIndices[i + 0]); const internal::Vector3 &b = mesh->position(triIndices[i + 1]); From 22f4d644aeff61a99af10facd579d4736c5043d4 Mon Sep 17 00:00:00 2001 From: mifth Date: Mon, 4 Nov 2024 20:31:34 +0100 Subject: [PATCH 07/17] Final changes for Quads and NGons. computeBoundaryIntersection variable is added to ChartOptions. --- source/xatlas/xatlas.cpp | 142 ++++++++++++++++++++++++++++++++++----- source/xatlas/xatlas.h | 2 + source/xatlas/xatlas_c.h | 1 + 3 files changed, 128 insertions(+), 17 deletions(-) diff --git a/source/xatlas/xatlas.cpp b/source/xatlas/xatlas.cpp index 4302b62..33d5c7b 100644 --- a/source/xatlas/xatlas.cpp +++ b/source/xatlas/xatlas.cpp @@ -2798,6 +2798,9 @@ class Mesh HashMap m_edgeMap; public: + // Quads/NGons IDs. Triangle id is a number, Quad/NGon id is a value. It can be empty if there is no polygon mapping. + Array trianglesToPolygonIDs; // Triangle index (mesh face index) to Quad/NGon index. + class FaceEdgeIterator { public: @@ -3461,9 +3464,8 @@ struct Triangulator // also sort the ears by angle, start with the ones that have the smallest angle and proceed in order. void triangulatePolygon(ConstArrayView vertices, const Array& inputIndices, Array &outputIndices, Array &outindicesIDs) { - m_polygonVertices.clear(); - m_polygonVertices.reserve(inputIndices.size()); outputIndices.clear(); + outindicesIDs.clear(); if (inputIndices.size() == 3) { // Simple case for triangles. outputIndices.push_back(inputIndices[0]); @@ -3473,6 +3475,21 @@ struct Triangulator outindicesIDs.push_back(1); outindicesIDs.push_back(2); } + else if (inputIndices.size() == 3) { + // Simple case for quads. + outputIndices.push_back(inputIndices[0]); + outputIndices.push_back(inputIndices[1]); + outputIndices.push_back(inputIndices[2]); + outindicesIDs.push_back(0); + outindicesIDs.push_back(1); + outindicesIDs.push_back(2); + outputIndices.push_back(inputIndices[0]); + outputIndices.push_back(inputIndices[2]); + outputIndices.push_back(inputIndices[3]); + outindicesIDs.push_back(0); + outindicesIDs.push_back(2); + outindicesIDs.push_back(3); + } else { // Build 2D polygon projecting vertices onto normal plane. // Faces are not necesarily planar, this is for example the case, when the face comes from filling a hole. In such cases @@ -3488,6 +3505,8 @@ struct Triangulator m_polygonAngles.reserve(edgeCount); m_indicesIDs.clear(); m_indicesIDs.reserve(edgeCount); + m_polygonVertices.clear(); + m_polygonVertices.reserve(inputIndices.size()); for (uint32_t i = 0; i < inputIndices.size(); i++) { m_polygonVertices.push_back(inputIndices[i]); const Vector3 &pos = vertices[inputIndices[i]]; @@ -5186,14 +5205,22 @@ struct PlanarCharts m_regionFirstFace.clear(); m_nextRegionFace.resize(faceCount); m_faceToRegionId.resize(faceCount); + const bool hasQuadsOrNGons = m_data.mesh->trianglesToPolygonIDs.size() > 0 ? true : false; + Array parsedFaces; // For Quads/NGons + if (hasQuadsOrNGons) + parsedFaces.resize(faceCount); for (uint32_t f = 0; f < faceCount; f++) { m_nextRegionFace[f] = f; m_faceToRegionId[f] = UINT32_MAX; + if (hasQuadsOrNGons) + parsedFaces[f] = false; // Set default value (false) } Array faceStack; faceStack.reserve(min(faceCount, 16u)); uint32_t regionCount = 0; for (uint32_t f = 0; f < faceCount; f++) { + if (hasQuadsOrNGons) + if (parsedFaces[f]) continue; // No need to parse as we already parsed a triangle of a Quad/NGon. if (m_nextRegionFace[f] != f) continue; // Already assigned. if (m_data.isFaceInChart.get(f)) @@ -5214,6 +5241,19 @@ struct PlanarCharts continue; // Already assigned. if (m_data.isFaceInChart.get(oface)) continue; // Already in a chart. + // if Triangle is a part of a Quad/NGon. + if (hasQuadsOrNGons) { + if (m_data.mesh->trianglesToPolygonIDs[face] == m_data.mesh->trianglesToPolygonIDs[oface]) { + const uint32_t next = m_nextRegionFace[face]; + m_nextRegionFace[face] = oface; + m_nextRegionFace[oface] = next; + m_faceToRegionId[oface] = regionCount; + faceStack.push_back(oface); + parsedFaces[oface] = true; // set parsed + parsedFaces[face] = true; // set parsed + continue; + } + } if (!equal(dot(m_data.faceNormals[face], m_data.faceNormals[oface]), 1.0f, kEpsilon)) continue; // Not coplanar. const uint32_t next = m_nextRegionFace[face]; @@ -5271,6 +5311,10 @@ struct PlanarCharts const uint32_t oface = it.oppositeFace(); if (m_faceToRegionId[oface] == region) continue; // Ignore internal edges. + if (hasQuadsOrNGons) { // Accept an entire NGon. As potentially it should be flat. + if (m_data.mesh->trianglesToPolygonIDs[face] == m_data.mesh->trianglesToPolygonIDs[oface]) + continue; // if Triangle is a part of a Quad/NGon. + } const float angle = m_data.edgeDihedralAngles[it.edge()]; if (angle > 0.0f && angle < FLT_MAX) { // FLT_MAX on boundaries. createChart = false; @@ -5635,21 +5679,28 @@ struct ClusteredCharts 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++) { + const uint32_t faceCount = m_data.mesh->faceCount(); + bool isPickedFace = false; + uint32_t seed = 0; + for (uint32_t f = 0; f < 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; + seed = f; + isPickedFace = true; } } + if (!isPickedFace && m_data.isFaceInChart.get(seed)) + return; // all faces are already added to charts. + // Create a new Chart and do further changes. + Chart *chart = XA_NEW(MemTag::Default, Chart); + chart->id = (int)m_charts.size(); + m_charts.push_back(chart); + chart->seed = seed; addFaceToChart(chart, chart->seed); // Grow the chart as much as possible within the given threshold. for (;;) { @@ -5658,7 +5709,7 @@ struct ClusteredCharts const uint32_t f = chart->candidates.pop(); if (m_data.isFaceInChart.get(f)) continue; - if (!addFaceToChart(chart, f)) { + if (!addFaceToChart(chart, f)) { // If Failed to add a triangle chart->failedPlanarRegions.push_back(m_planarCharts.regionIdFromFace(f)); continue; } @@ -5747,6 +5798,8 @@ struct ClusteredCharts XA_DEBUG_ASSERT(!m_data.isFaceInChart.get(face)); const uint32_t oldFaceCount = chart->faces.size(); const bool firstFace = oldFaceCount == 0; + const bool hasQuadsOrNGons = m_data.mesh->trianglesToPolygonIDs.size() > 0 ? true : false; + const uint32_t meshFaceCount = m_data.mesh->faceCount(); // 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); @@ -5755,6 +5808,35 @@ struct ClusteredCharts chart->faces.push_back(coplanarFace); coplanarFace = m_planarCharts.nextRegionFace(coplanarFace); } + if (hasQuadsOrNGons) { + // Add missing triangles of Quad/NGon + const uint32_t faceCountTmp = chart->faces.size(); + for (uint32_t i = oldFaceCount; i < faceCountTmp; i++) { // Run Forward + const uint32_t i_face = chart->faces[i]; + const uint32_t faceQuadOrNGonID = m_data.mesh->trianglesToPolygonIDs[i_face]; + const uint32_t nextFace = i_face + 1; + for (uint32_t f = nextFace; f < meshFaceCount; f++) { + if (m_data.mesh->trianglesToPolygonIDs[f] == faceQuadOrNGonID) { + if (!chart->faces.contains(f)) { + chart->faces.push_back(f); + } + } + else {break;} + } + if (i_face > 0) { // Run Backward + int32_t f = static_cast(i_face) - 1; + while (f >= 0) { + if (m_data.mesh->trianglesToPolygonIDs[f] == faceQuadOrNGonID) { + if (!chart->faces.contains(f)) { + chart->faces.push_back(f); + } + } + else {break;} + --f; + } + } + } + } const uint32_t faceCount = chart->faces.size(); // Compute basis. Basis basis; @@ -7156,6 +7238,9 @@ class Chart m_originalIndices.resize(faces.length * 3); // Add geometry. const uint32_t faceCount = faces.length; + const bool hasQuadsOrNGons = sourceMesh->trianglesToPolygonIDs.size() > 0 ? true : false; + if (hasQuadsOrNGons) + m_unifiedMesh->trianglesToPolygonIDs.resize(faceCount); // Resize an Array Before copying some IDs for (uint32_t f = 0; f < faceCount; f++) { uint32_t unifiedIndices[3]; for (uint32_t i = 0; i < 3; i++) { @@ -7183,6 +7268,9 @@ class Chart XA_DEBUG_ASSERT(unifiedIndices[i] != UINT32_MAX); } m_unifiedMesh->addFace(unifiedIndices); + // Add TriangleIDs and PolygonIDs + if (hasQuadsOrNGons) + m_unifiedMesh->trianglesToPolygonIDs[f] = sourceMesh->trianglesToPolygonIDs[m_faceToSourceFaceMap[f]]; } m_unifiedMesh->createBoundaries(); if (m_generatorType == segment::ChartGeneratorType::Planar) { @@ -7205,6 +7293,7 @@ class Chart { const uint32_t faceCount = faces.length; m_faceToSourceFaceMap.resize(faceCount); + const bool hasQuadsOrNGons = sourceMesh->trianglesToPolygonIDs.size() > 0 ? true : false; for (uint32_t i = 0; i < faceCount; i++) m_faceToSourceFaceMap[i] = parent->m_faceToSourceFaceMap[faces[i]]; // Map faces to parent chart source mesh. // Copy face indices. @@ -7234,6 +7323,8 @@ class Chart } // Add faces. m_originalIndices.resize(faceCount * 3); + if (hasQuadsOrNGons) // NOTE FOR FUTURE! Maybe "parentMesh" should be used instead of "sourceMesh" + m_unifiedMesh->trianglesToPolygonIDs.resize(faceCount); // Resize an Array Before copying some IDs for (uint32_t f = 0; f < faceCount; f++) { uint32_t unifiedIndices[3]; for (uint32_t i = 0; i < 3; i++) { @@ -7243,6 +7334,9 @@ class Chart unifiedIndices[i] = sourceVertexToUnifiedVertexMap.get(unifiedVertex); } m_unifiedMesh->addFace(unifiedIndices); + // Add TriangleIDs and PolygonIDs + if (hasQuadsOrNGons) // NOTE FOR FUTURE! Maybe "parentMesh" should be used instead of "sourceMesh" + m_unifiedMesh->trianglesToPolygonIDs[f] = sourceMesh->trianglesToPolygonIDs[m_faceToSourceFaceMap[f]]; } m_unifiedMesh->createBoundaries(); // Need to store texcoords for backup/restore so packing can be run multiple times. @@ -7291,7 +7385,8 @@ class Chart // Computing charts checks for flipped triangles and boundary intersection. Don't need to do that again here if chart is planar. if (m_type != ChartType::Planar && m_generatorType != segment::ChartGeneratorType::OriginalUv) { XA_PROFILE_START(parameterizeChartsEvaluateQuality) - m_quality.computeBoundaryIntersection(m_unifiedMesh, boundaryGrid); + if (options.computeBoundaryIntersection) // intersect edges. It can create artifacts (little peeces). + m_quality.computeBoundaryIntersection(m_unifiedMesh, boundaryGrid); m_quality.computeFlippedFaces(m_unifiedMesh, nullptr); m_quality.computeMetrics(m_unifiedMesh); XA_PROFILE_END(parameterizeChartsEvaluateQuality) @@ -7308,7 +7403,8 @@ class Chart computeLeastSquaresConformalMap(m_unifiedMesh); XA_PROFILE_END(parameterizeChartsLSCM) XA_PROFILE_START(parameterizeChartsEvaluateQuality) - m_quality.computeBoundaryIntersection(m_unifiedMesh, boundaryGrid); + if (options.computeBoundaryIntersection) // intersect edges. It can create artifacts (little peeces). + m_quality.computeBoundaryIntersection(m_unifiedMesh, boundaryGrid); #if XA_DEBUG_EXPORT_OBJ_INVALID_PARAMETERIZATION m_quality.computeFlippedFaces(m_unifiedMesh, &m_paramFlippedFaces); #else @@ -7680,6 +7776,9 @@ class ChartGroup const uint32_t approxVertexCount = min(faceCount * 3, m_sourceMesh->vertexCount()); Mesh *mesh = XA_NEW_ARGS(MemTag::Mesh, Mesh, m_sourceMesh->epsilon(), approxVertexCount, faceCount, m_sourceMesh->flags() & MeshFlags::HasNormals); HashMap> sourceVertexToVertexMap(MemTag::Mesh, approxVertexCount); + const bool hasQuadsOrNGons = m_sourceMesh->trianglesToPolygonIDs.size() > 0 ? true : false; + if (hasQuadsOrNGons) + mesh->trianglesToPolygonIDs.resize(faceCount); // Resize an Array before copying some IDs for (uint32_t f = 0; f < faceCount; f++) { const uint32_t face = m_faceToSourceFaceMap[f]; for (uint32_t i = 0; i < 3; i++) { @@ -7692,6 +7791,9 @@ class ChartGroup mesh->addVertex(m_sourceMesh->position(vertex), normal, m_sourceMesh->texcoord(vertex)); } } + // Add TriangleIDs and PolygonIDs + if (hasQuadsOrNGons) + mesh->trianglesToPolygonIDs[f] = m_sourceMesh->trianglesToPolygonIDs[m_faceToSourceFaceMap[f]]; } // Add faces. for (uint32_t f = 0; f < faceCount; f++) { @@ -9099,24 +9201,27 @@ AddMeshError AddMesh(Atlas *atlas, const MeshDecl &meshDecl, uint32_t meshCountH internal::Array triIndices; internal::Array indicesIDs; internal::Triangulator triangulator; - uint32_t polygonStartID = 0; + uint32_t polygonVertexStartID = 0; internal::Array polygon; + bool hasQuadsOrNGons = false; for (uint32_t face = 0; face < faceCount; face++) { // Decode face indices. const uint32_t faceVertexCount = meshDecl.faceVertexCount ? (uint32_t)meshDecl.faceVertexCount[face] : 3; polygon.clear(); for (uint32_t i = 0; i < faceVertexCount; i++) { if (hasIndices) { - uint32_t vertID = DecodeIndex(meshDecl.indexFormat, meshDecl.indexData, meshDecl.indexOffset, polygonStartID + i); + uint32_t vertID = DecodeIndex(meshDecl.indexFormat, meshDecl.indexData, meshDecl.indexOffset, polygonVertexStartID + i); polygon.push_back(vertID); + if (faceVertexCount > 3) hasQuadsOrNGons = true; // Mesh has Quads or NGons. // Check if any index is out of range. - if (polygon[i] >= meshDecl.vertexCount) { + if (polygon[i] >= meshDecl.vertexCount + || faceVertexCount < 3) { mesh->~Mesh(); XA_FREE(mesh); return AddMeshError::IndexOutOfRange; } } else { - polygon[i] = polygonStartID + i; + polygon[i] = polygonVertexStartID + i; } } // Ignore faces with degenerate or zero length edges. @@ -9213,10 +9318,13 @@ AddMeshError AddMesh(Atlas *atlas, const MeshDecl &meshDecl, uint32_t meshCountH } if (meshPolygonMapping) { for (uint32_t i = 0; i < triIndices.size(); i++) - meshPolygonMapping->triangleToPolygonIndicesMap.push_back(indicesIDs[i] + polygonStartID); + meshPolygonMapping->triangleToPolygonIndicesMap.push_back(indicesIDs[i] + polygonVertexStartID); } - polygonStartID += faceVertexCount; // Add an offset for the next polygon's start index + polygonVertexStartID += faceVertexCount; // Add an offset for the next polygon's start index } + // Add TriangleIDs and PolygonIDs if we have Quads/NGons + if (meshPolygonMapping && hasQuadsOrNGons) + meshPolygonMapping->triangleToPolygonMap.copyTo(mesh->trianglesToPolygonIDs); if (warningCount > kMaxWarnings) XA_PRINT(" %u additional warnings truncated\n", warningCount - kMaxWarnings); XA_PROFILE_END(addMeshCopyData) diff --git a/source/xatlas/xatlas.h b/source/xatlas/xatlas.h index 18b794b..3e4ec4f 100644 --- a/source/xatlas/xatlas.h +++ b/source/xatlas/xatlas.h @@ -189,6 +189,8 @@ struct ChartOptions bool useInputMeshUvs = false; // Use MeshDecl::vertexUvData for charts. bool fixWinding = false; // Enforce consistent texture coordinate winding. + + bool computeBoundaryIntersection = false; // Used in parameterize() to intersect edges. It can create artifacts (little peeces). }; // Call after all AddMesh calls. Can be called multiple times to recompute charts with different options. diff --git a/source/xatlas/xatlas_c.h b/source/xatlas/xatlas_c.h index 55390e2..896123a 100644 --- a/source/xatlas/xatlas_c.h +++ b/source/xatlas/xatlas_c.h @@ -191,6 +191,7 @@ typedef struct uint32_t maxIterations; bool useInputMeshUvs; bool fixWinding; + bool computeBoundaryIntersection; } xatlasChartOptions; From c4703c552703b5bb0b773c0630f873b649c494e3 Mon Sep 17 00:00:00 2001 From: mifth Date: Tue, 5 Nov 2024 10:41:34 +0100 Subject: [PATCH 08/17] Fixed typo --- source/xatlas/xatlas.cpp | 2 +- source/xatlas/xatlas.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/source/xatlas/xatlas.cpp b/source/xatlas/xatlas.cpp index 33d5c7b..8739930 100644 --- a/source/xatlas/xatlas.cpp +++ b/source/xatlas/xatlas.cpp @@ -7385,7 +7385,7 @@ class Chart // Computing charts checks for flipped triangles and boundary intersection. Don't need to do that again here if chart is planar. if (m_type != ChartType::Planar && m_generatorType != segment::ChartGeneratorType::OriginalUv) { XA_PROFILE_START(parameterizeChartsEvaluateQuality) - if (options.computeBoundaryIntersection) // intersect edges. It can create artifacts (little peeces). + if (options.computeBoundaryIntersection) // intersect edges. It can create artifacts (little pieces). m_quality.computeBoundaryIntersection(m_unifiedMesh, boundaryGrid); m_quality.computeFlippedFaces(m_unifiedMesh, nullptr); m_quality.computeMetrics(m_unifiedMesh); diff --git a/source/xatlas/xatlas.h b/source/xatlas/xatlas.h index 3e4ec4f..79207b4 100644 --- a/source/xatlas/xatlas.h +++ b/source/xatlas/xatlas.h @@ -190,7 +190,7 @@ struct ChartOptions bool useInputMeshUvs = false; // Use MeshDecl::vertexUvData for charts. bool fixWinding = false; // Enforce consistent texture coordinate winding. - bool computeBoundaryIntersection = false; // Used in parameterize() to intersect edges. It can create artifacts (little peeces). + bool computeBoundaryIntersection = false; // Used in parameterize() to intersect edges. It can create artifacts (little pieces). }; // Call after all AddMesh calls. Can be called multiple times to recompute charts with different options. From f01768abf0358d47e116755b107a89ac888f8614 Mon Sep 17 00:00:00 2001 From: mifth Date: Wed, 6 Nov 2024 00:20:06 +0100 Subject: [PATCH 09/17] Removed computeBoundingIntersection variable. Fixed quads/ngons for patches. --- source/xatlas/xatlas.cpp | 47 +++++++++++++++++++++++++++++++++++----- source/xatlas/xatlas.h | 2 -- source/xatlas/xatlas_c.h | 1 - 3 files changed, 41 insertions(+), 9 deletions(-) diff --git a/source/xatlas/xatlas.cpp b/source/xatlas/xatlas.cpp index 8739930..87d89da 100644 --- a/source/xatlas/xatlas.cpp +++ b/source/xatlas/xatlas.cpp @@ -6686,6 +6686,7 @@ struct PiecewiseParam // Add the seed face (first unassigned face) to the patch. const uint32_t faceCount = m_mesh->faceCount(); uint32_t seed = UINT32_MAX; + const bool hasQuadsOrNGons = m_mesh->trianglesToPolygonIDs.size() > 0 ? true : false; for (uint32_t f = 0; f < faceCount; f++) { if (m_faceInAnyPatch.get(f)) continue; @@ -6800,6 +6801,40 @@ struct PiecewiseParam XA_PROFILE_END(parameterizeChartsPiecewiseBoundaryIntersection) } } + if (hasQuadsOrNGons) { + // Add other triangles of already added Quads/NGons + for (uint32_t f1 = 0; f1 < m_patch.size(); f1++) { + // uint32_t polygonID = m_mesh->trianglesToPolygonIDs[f]; // ID of a Quad/NGon + const uint32_t i_face = m_patch[f1]; + const uint32_t faceQuadOrNGonID = m_mesh->trianglesToPolygonIDs[i_face]; + const uint32_t nextFace = i_face + 1; + for (uint32_t f = nextFace; f < faceCount; f++) { + if (m_mesh->trianglesToPolygonIDs[f] == faceQuadOrNGonID) { + if (!m_faceInAnyPatch.get(f)) { + m_patch.push_back(f); + m_faceInPatch.set(f); + m_faceInAnyPatch.set(f); + } + } + else {break;} + } + if (i_face > 0) { // Run Backward + int32_t f = static_cast(i_face) - 1; + while (f >= 0) { + if (m_mesh->trianglesToPolygonIDs[f] == faceQuadOrNGonID) { + if (!m_faceInAnyPatch.get(f)) { + m_patch.push_back(f); + m_faceInPatch.set(f); + m_faceInAnyPatch.set(f); + } + } + else {break;} + --f; + } + } + } + + } return true; } @@ -6864,11 +6899,13 @@ struct PiecewiseParam } XA_DEBUG_ASSERT(freeVertex != UINT32_MAX); if (m_vertexInPatch.get(freeVertex)) { -#if 0 +// #if 0 // If the free vertex is already in the patch, the face is enclosed by the patch. Add the face to the patch - don't need to assign texcoords. + if (m_faceInAnyPatch.get(oface)) + continue; freeVertex = UINT32_MAX; addFaceToPatch(oface); -#endif +// #endif continue; } // Check this here rather than above so faces enclosed by the patch are always added. @@ -7385,8 +7422,7 @@ class Chart // Computing charts checks for flipped triangles and boundary intersection. Don't need to do that again here if chart is planar. if (m_type != ChartType::Planar && m_generatorType != segment::ChartGeneratorType::OriginalUv) { XA_PROFILE_START(parameterizeChartsEvaluateQuality) - if (options.computeBoundaryIntersection) // intersect edges. It can create artifacts (little pieces). - m_quality.computeBoundaryIntersection(m_unifiedMesh, boundaryGrid); + m_quality.computeBoundaryIntersection(m_unifiedMesh, boundaryGrid); m_quality.computeFlippedFaces(m_unifiedMesh, nullptr); m_quality.computeMetrics(m_unifiedMesh); XA_PROFILE_END(parameterizeChartsEvaluateQuality) @@ -7403,8 +7439,7 @@ class Chart computeLeastSquaresConformalMap(m_unifiedMesh); XA_PROFILE_END(parameterizeChartsLSCM) XA_PROFILE_START(parameterizeChartsEvaluateQuality) - if (options.computeBoundaryIntersection) // intersect edges. It can create artifacts (little peeces). - m_quality.computeBoundaryIntersection(m_unifiedMesh, boundaryGrid); + m_quality.computeBoundaryIntersection(m_unifiedMesh, boundaryGrid); #if XA_DEBUG_EXPORT_OBJ_INVALID_PARAMETERIZATION m_quality.computeFlippedFaces(m_unifiedMesh, &m_paramFlippedFaces); #else diff --git a/source/xatlas/xatlas.h b/source/xatlas/xatlas.h index 79207b4..18b794b 100644 --- a/source/xatlas/xatlas.h +++ b/source/xatlas/xatlas.h @@ -189,8 +189,6 @@ struct ChartOptions bool useInputMeshUvs = false; // Use MeshDecl::vertexUvData for charts. bool fixWinding = false; // Enforce consistent texture coordinate winding. - - bool computeBoundaryIntersection = false; // Used in parameterize() to intersect edges. It can create artifacts (little pieces). }; // Call after all AddMesh calls. Can be called multiple times to recompute charts with different options. diff --git a/source/xatlas/xatlas_c.h b/source/xatlas/xatlas_c.h index 896123a..55390e2 100644 --- a/source/xatlas/xatlas_c.h +++ b/source/xatlas/xatlas_c.h @@ -191,7 +191,6 @@ typedef struct uint32_t maxIterations; bool useInputMeshUvs; bool fixWinding; - bool computeBoundaryIntersection; } xatlasChartOptions; From 2d4471947ab95340627e053a402c1c41294458c9 Mon Sep 17 00:00:00 2001 From: mifth Date: Wed, 6 Nov 2024 12:34:48 +0100 Subject: [PATCH 10/17] Delete unneeded check. --- source/xatlas/xatlas.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/source/xatlas/xatlas.cpp b/source/xatlas/xatlas.cpp index 87d89da..59ece6f 100644 --- a/source/xatlas/xatlas.cpp +++ b/source/xatlas/xatlas.cpp @@ -6901,8 +6901,6 @@ struct PiecewiseParam if (m_vertexInPatch.get(freeVertex)) { // #if 0 // If the free vertex is already in the patch, the face is enclosed by the patch. Add the face to the patch - don't need to assign texcoords. - if (m_faceInAnyPatch.get(oface)) - continue; freeVertex = UINT32_MAX; addFaceToPatch(oface); // #endif From 5ac6a5d1cc0835db12e4ad983767b90e5707c5eb Mon Sep 17 00:00:00 2001 From: mifth Date: Thu, 7 Nov 2024 10:23:08 +0100 Subject: [PATCH 11/17] Reverted "#IF 0" as it will be as a different PR. Little tweaks. --- source/xatlas/xatlas.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/source/xatlas/xatlas.cpp b/source/xatlas/xatlas.cpp index 59ece6f..862c345 100644 --- a/source/xatlas/xatlas.cpp +++ b/source/xatlas/xatlas.cpp @@ -5243,14 +5243,14 @@ struct PlanarCharts continue; // Already in a chart. // if Triangle is a part of a Quad/NGon. if (hasQuadsOrNGons) { - if (m_data.mesh->trianglesToPolygonIDs[face] == m_data.mesh->trianglesToPolygonIDs[oface]) { + if (m_data.mesh->trianglesToPolygonIDs[face] == m_data.mesh->trianglesToPolygonIDs[oface] + && face != oface) { const uint32_t next = m_nextRegionFace[face]; m_nextRegionFace[face] = oface; m_nextRegionFace[oface] = next; m_faceToRegionId[oface] = regionCount; faceStack.push_back(oface); parsedFaces[oface] = true; // set parsed - parsedFaces[face] = true; // set parsed continue; } } @@ -6899,11 +6899,11 @@ struct PiecewiseParam } XA_DEBUG_ASSERT(freeVertex != UINT32_MAX); if (m_vertexInPatch.get(freeVertex)) { -// #if 0 +#if 0 // If the free vertex is already in the patch, the face is enclosed by the patch. Add the face to the patch - don't need to assign texcoords. freeVertex = UINT32_MAX; addFaceToPatch(oface); -// #endif +#endif continue; } // Check this here rather than above so faces enclosed by the patch are always added. From 49ce073b84e6d32ba4da8eaec063752a9610dfd1 Mon Sep 17 00:00:00 2001 From: mifth Date: Thu, 7 Nov 2024 10:43:21 +0100 Subject: [PATCH 12/17] Cleanup. --- source/xatlas/xatlas.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/source/xatlas/xatlas.cpp b/source/xatlas/xatlas.cpp index 862c345..0e61ddc 100644 --- a/source/xatlas/xatlas.cpp +++ b/source/xatlas/xatlas.cpp @@ -6804,7 +6804,6 @@ struct PiecewiseParam if (hasQuadsOrNGons) { // Add other triangles of already added Quads/NGons for (uint32_t f1 = 0; f1 < m_patch.size(); f1++) { - // uint32_t polygonID = m_mesh->trianglesToPolygonIDs[f]; // ID of a Quad/NGon const uint32_t i_face = m_patch[f1]; const uint32_t faceQuadOrNGonID = m_mesh->trianglesToPolygonIDs[i_face]; const uint32_t nextFace = i_face + 1; @@ -6833,7 +6832,6 @@ struct PiecewiseParam } } } - } return true; } From 98806d6d92a54db9d1f347a8d101fd1fd59c069d Mon Sep 17 00:00:00 2001 From: mifth Date: Thu, 7 Nov 2024 22:57:56 +0100 Subject: [PATCH 13/17] Wrong number for a quad. --- source/xatlas/xatlas.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/xatlas/xatlas.cpp b/source/xatlas/xatlas.cpp index 0e61ddc..7ce0f57 100644 --- a/source/xatlas/xatlas.cpp +++ b/source/xatlas/xatlas.cpp @@ -3475,7 +3475,7 @@ struct Triangulator outindicesIDs.push_back(1); outindicesIDs.push_back(2); } - else if (inputIndices.size() == 3) { + else if (inputIndices.size() == 4) { // Simple case for quads. outputIndices.push_back(inputIndices[0]); outputIndices.push_back(inputIndices[1]); From 15a9d204956b1e7ca5a88d6daebac9f8afc1b480 Mon Sep 17 00:00:00 2001 From: mifth Date: Fri, 8 Nov 2024 16:58:26 +0100 Subject: [PATCH 14/17] Commented a simple case for "quads to triangles". But I would like to leave it commented to make an option in future. --- source/xatlas/xatlas.cpp | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/source/xatlas/xatlas.cpp b/source/xatlas/xatlas.cpp index 7ce0f57..ec99767 100644 --- a/source/xatlas/xatlas.cpp +++ b/source/xatlas/xatlas.cpp @@ -3475,21 +3475,21 @@ struct Triangulator outindicesIDs.push_back(1); outindicesIDs.push_back(2); } - else if (inputIndices.size() == 4) { - // Simple case for quads. - outputIndices.push_back(inputIndices[0]); - outputIndices.push_back(inputIndices[1]); - outputIndices.push_back(inputIndices[2]); - outindicesIDs.push_back(0); - outindicesIDs.push_back(1); - outindicesIDs.push_back(2); - outputIndices.push_back(inputIndices[0]); - outputIndices.push_back(inputIndices[2]); - outputIndices.push_back(inputIndices[3]); - outindicesIDs.push_back(0); - outindicesIDs.push_back(2); - outindicesIDs.push_back(3); - } + // else if (inputIndices.size() == 4) { + // // Simple case for quads. + // outputIndices.push_back(inputIndices[0]); + // outputIndices.push_back(inputIndices[1]); + // outputIndices.push_back(inputIndices[2]); + // outindicesIDs.push_back(0); + // outindicesIDs.push_back(1); + // outindicesIDs.push_back(2); + // outputIndices.push_back(inputIndices[0]); + // outputIndices.push_back(inputIndices[2]); + // outputIndices.push_back(inputIndices[3]); + // outindicesIDs.push_back(0); + // outindicesIDs.push_back(2); + // outindicesIDs.push_back(3); + // } else { // Build 2D polygon projecting vertices onto normal plane. // Faces are not necesarily planar, this is for example the case, when the face comes from filling a hole. In such cases From 2f47d81623641cb9122e1ff503170472680f4cd0 Mon Sep 17 00:00:00 2001 From: mifth Date: Fri, 8 Nov 2024 17:35:53 +0100 Subject: [PATCH 15/17] PiecewiseParam::computeChart() function still does artifacts for Quads/NGons and I wasn't be able to fix it. So I commented my fixes in the PiecewiseParam::computeChart() function and set "m_isInvalid" to work only for Triangles. I hope someone will fix the PiecewiseParam::computeChart() function in future. --- source/xatlas/xatlas.cpp | 69 +++++++++++++++++++++------------------- 1 file changed, 36 insertions(+), 33 deletions(-) diff --git a/source/xatlas/xatlas.cpp b/source/xatlas/xatlas.cpp index ec99767..127c7d0 100644 --- a/source/xatlas/xatlas.cpp +++ b/source/xatlas/xatlas.cpp @@ -6801,38 +6801,39 @@ struct PiecewiseParam XA_PROFILE_END(parameterizeChartsPiecewiseBoundaryIntersection) } } - if (hasQuadsOrNGons) { - // Add other triangles of already added Quads/NGons - for (uint32_t f1 = 0; f1 < m_patch.size(); f1++) { - const uint32_t i_face = m_patch[f1]; - const uint32_t faceQuadOrNGonID = m_mesh->trianglesToPolygonIDs[i_face]; - const uint32_t nextFace = i_face + 1; - for (uint32_t f = nextFace; f < faceCount; f++) { - if (m_mesh->trianglesToPolygonIDs[f] == faceQuadOrNGonID) { - if (!m_faceInAnyPatch.get(f)) { - m_patch.push_back(f); - m_faceInPatch.set(f); - m_faceInAnyPatch.set(f); - } - } - else {break;} - } - if (i_face > 0) { // Run Backward - int32_t f = static_cast(i_face) - 1; - while (f >= 0) { - if (m_mesh->trianglesToPolygonIDs[f] == faceQuadOrNGonID) { - if (!m_faceInAnyPatch.get(f)) { - m_patch.push_back(f); - m_faceInPatch.set(f); - m_faceInAnyPatch.set(f); - } - } - else {break;} - --f; - } - } - } - } + // // Still it makes artifacts for Quads/NGons. We need a different approach. + // if (hasQuadsOrNGons) { + // // Add other triangles of already added Quads/NGons + // for (uint32_t f1 = 0; f1 < m_patch.size(); f1++) { + // const uint32_t i_face = m_patch[f1]; + // const uint32_t faceQuadOrNGonID = m_mesh->trianglesToPolygonIDs[i_face]; + // const uint32_t nextFace = i_face + 1; + // for (uint32_t f = nextFace; f < faceCount; f++) { + // if (m_mesh->trianglesToPolygonIDs[f] == faceQuadOrNGonID) { + // if (!m_faceInAnyPatch.get(f)) { + // m_patch.push_back(f); + // m_faceInPatch.set(f); + // m_faceInAnyPatch.set(f); + // } + // } + // else {break;} + // } + // if (i_face > 0) { // Run Backward + // int32_t f = static_cast(i_face) - 1; + // while (f >= 0) { + // if (m_mesh->trianglesToPolygonIDs[f] == faceQuadOrNGonID) { + // if (!m_faceInAnyPatch.get(f)) { + // m_patch.push_back(f); + // m_faceInPatch.set(f); + // m_faceInAnyPatch.set(f); + // } + // } + // else {break;} + // --f; + // } + // } + // } + // } return true; } @@ -7408,6 +7409,7 @@ class Chart void parameterize(const ChartOptions &options, UniformGrid2 &boundaryGrid) { const uint32_t unifiedVertexCount = m_unifiedMesh->vertexCount(); + const bool hasQuadsOrNGons = m_unifiedMesh->trianglesToPolygonIDs.size() > 0 ? true : false; if (m_generatorType == segment::ChartGeneratorType::OriginalUv) { } else { // Project vertices to plane. @@ -7443,7 +7445,8 @@ class Chart #endif // Don't need to call computeMetrics here, that's only used in evaluateOrthoQuality to determine if quality is acceptable enough to use ortho projection. if (m_quality.boundaryIntersection || m_quality.flippedTriangleCount > 0 || m_quality.zeroAreaTriangleCount > 0) - m_isInvalid = true; + if (!hasQuadsOrNGons) // PiecewiseParam::computeChart() doesn't support Quads/NGons + m_isInvalid = true; XA_PROFILE_END(parameterizeChartsEvaluateQuality) } } From a4bb139ad50537b4511b0cc65f4991f4d8649359 Mon Sep 17 00:00:00 2001 From: mifth Date: Fri, 8 Nov 2024 21:48:34 +0100 Subject: [PATCH 16/17] Uncommented code for the PiecewiseParam::computeChart() as it's needed for flipped faces and zero faces. --- source/xatlas/xatlas.cpp | 77 ++++++++++++++++++++++------------------ 1 file changed, 42 insertions(+), 35 deletions(-) diff --git a/source/xatlas/xatlas.cpp b/source/xatlas/xatlas.cpp index 127c7d0..b0d7376 100644 --- a/source/xatlas/xatlas.cpp +++ b/source/xatlas/xatlas.cpp @@ -6801,39 +6801,41 @@ struct PiecewiseParam XA_PROFILE_END(parameterizeChartsPiecewiseBoundaryIntersection) } } - // // Still it makes artifacts for Quads/NGons. We need a different approach. - // if (hasQuadsOrNGons) { - // // Add other triangles of already added Quads/NGons - // for (uint32_t f1 = 0; f1 < m_patch.size(); f1++) { - // const uint32_t i_face = m_patch[f1]; - // const uint32_t faceQuadOrNGonID = m_mesh->trianglesToPolygonIDs[i_face]; - // const uint32_t nextFace = i_face + 1; - // for (uint32_t f = nextFace; f < faceCount; f++) { - // if (m_mesh->trianglesToPolygonIDs[f] == faceQuadOrNGonID) { - // if (!m_faceInAnyPatch.get(f)) { - // m_patch.push_back(f); - // m_faceInPatch.set(f); - // m_faceInAnyPatch.set(f); - // } - // } - // else {break;} - // } - // if (i_face > 0) { // Run Backward - // int32_t f = static_cast(i_face) - 1; - // while (f >= 0) { - // if (m_mesh->trianglesToPolygonIDs[f] == faceQuadOrNGonID) { - // if (!m_faceInAnyPatch.get(f)) { - // m_patch.push_back(f); - // m_faceInPatch.set(f); - // m_faceInAnyPatch.set(f); - // } - // } - // else {break;} - // --f; - // } - // } - // } - // } + // Still it makes artifacts for Quads/NGons. We need a different approach. + if (hasQuadsOrNGons) { + // Add other triangles of already added Quads/NGons + for (uint32_t f1 = 0; f1 < m_patch.size(); f1++) { + const uint32_t i_face = m_patch[f1]; + const uint32_t faceQuadOrNGonID = m_mesh->trianglesToPolygonIDs[i_face]; + const uint32_t nextFace = i_face + 1; + for (uint32_t f = nextFace; f < faceCount; f++) { + if (m_mesh->trianglesToPolygonIDs[f] == faceQuadOrNGonID) { + if (!m_faceInAnyPatch.get(f)) { + m_patch.push_back(f); + m_faceInPatch.set(f); + m_faceInAnyPatch.set(f); + if(m_faceInvalid.get(f)) m_faceInvalid.unset(f); + } + } + else {break;} + } + if (i_face > 0) { // Run Backward + int32_t f = static_cast(i_face) - 1; + while (f >= 0) { + if (m_mesh->trianglesToPolygonIDs[f] == faceQuadOrNGonID) { + if (!m_faceInAnyPatch.get(f)) { + m_patch.push_back(f); + m_faceInPatch.set(f); + m_faceInAnyPatch.set(f); + if(m_faceInvalid.get(f)) m_faceInvalid.unset(f); + } + } + else {break;} + --f; + } + } + } + } return true; } @@ -7444,9 +7446,14 @@ class Chart m_quality.computeFlippedFaces(m_unifiedMesh, nullptr); #endif // Don't need to call computeMetrics here, that's only used in evaluateOrthoQuality to determine if quality is acceptable enough to use ortho projection. - if (m_quality.boundaryIntersection || m_quality.flippedTriangleCount > 0 || m_quality.zeroAreaTriangleCount > 0) - if (!hasQuadsOrNGons) // PiecewiseParam::computeChart() doesn't support Quads/NGons + if (m_quality.boundaryIntersection || m_quality.flippedTriangleCount > 0 || m_quality.zeroAreaTriangleCount > 0) { + if (hasQuadsOrNGons) { // PiecewiseParam::computeChart() doesn't support Quads/NGons well + if (!m_quality.boundaryIntersection && m_quality.zeroAreaTriangleCount == 0) // No support for these variants yet. + m_isInvalid = true; + } + else m_isInvalid = true; + } XA_PROFILE_END(parameterizeChartsEvaluateQuality) } } From d92f849395c91d85b78a35e53942e5ce3246cc71 Mon Sep 17 00:00:00 2001 From: mifth Date: Fri, 8 Nov 2024 22:31:01 +0100 Subject: [PATCH 17/17] Missed vertices are added. --- source/xatlas/xatlas.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/source/xatlas/xatlas.cpp b/source/xatlas/xatlas.cpp index b0d7376..f1dd0e6 100644 --- a/source/xatlas/xatlas.cpp +++ b/source/xatlas/xatlas.cpp @@ -6815,6 +6815,11 @@ struct PiecewiseParam m_faceInPatch.set(f); m_faceInAnyPatch.set(f); if(m_faceInvalid.get(f)) m_faceInvalid.unset(f); + // Add all 3 vertices. + for (uint32_t i = 0; i < 3; i++) { + const uint32_t vertex = m_mesh->vertexAt(f * 3 + i); + if(!m_vertexInPatch.get(f)) m_vertexInPatch.set(vertex); + } } } else {break;} @@ -6828,6 +6833,11 @@ struct PiecewiseParam m_faceInPatch.set(f); m_faceInAnyPatch.set(f); if(m_faceInvalid.get(f)) m_faceInvalid.unset(f); + // // Add all 3 vertices. + for (uint32_t i = 0; i < 3; i++) { + const uint32_t vertex = m_mesh->vertexAt(f * 3 + i); + if(!m_vertexInPatch.get(f)) m_vertexInPatch.set(vertex); + } } } else {break;}