mirror of
https://github.com/Relintai/mesh_utils.git
synced 2024-11-14 10:27:45 +01:00
Removed PlanarCharts and ClusteredCharts from xatlas.
This commit is contained in:
parent
a3c9009996
commit
62f5f8f269
@ -4642,931 +4642,6 @@ static uint32_t s_planarRegionsCurrentRegion;
|
|||||||
static uint32_t s_planarRegionsCurrentVertex;
|
static uint32_t s_planarRegionsCurrentVertex;
|
||||||
#endif
|
#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<uint32_t> chartFaces(uint32_t chartIndex) const {
|
|
||||||
const Chart &chart = m_charts[chartIndex];
|
|
||||||
return ConstArrayView<uint32_t>(&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<uint32_t> 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<std::mutex> 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<uint32_t> m_regionFirstFace;
|
|
||||||
Array<uint32_t> m_nextRegionFace;
|
|
||||||
Array<uint32_t> m_faceToRegionId;
|
|
||||||
Array<float> m_regionAreas;
|
|
||||||
Array<Chart> m_charts;
|
|
||||||
Array<uint32_t> m_chartFaces;
|
|
||||||
Array<Basis> 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<uint32_t> 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<uint32_t> faces;
|
|
||||||
Array<uint32_t> 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<Vector2> m_texcoords;
|
|
||||||
uint32_t m_facesLeft;
|
|
||||||
Array<int> m_faceCharts;
|
|
||||||
Array<Chart *> m_charts;
|
|
||||||
CostQueue m_bestTriangles;
|
|
||||||
Array<Vector3> m_tempPoints;
|
|
||||||
UniformGrid2 m_boundaryGrid;
|
|
||||||
#if XA_MERGE_CHARTS
|
|
||||||
// mergeCharts
|
|
||||||
Array<float> m_sharedBoundaryLengths;
|
|
||||||
Array<float> m_sharedBoundaryLengthsNoSeams;
|
|
||||||
Array<uint32_t> m_sharedBoundaryEdgeCountNoSeams;
|
|
||||||
#endif
|
|
||||||
bool m_placingSeeds;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct ChartGeneratorType {
|
struct ChartGeneratorType {
|
||||||
enum Enum {
|
enum Enum {
|
||||||
OriginalUv,
|
OriginalUv,
|
||||||
@ -5578,39 +4653,22 @@ struct ChartGeneratorType {
|
|||||||
|
|
||||||
struct Atlas {
|
struct Atlas {
|
||||||
Atlas() :
|
Atlas() :
|
||||||
m_originalUvCharts(m_data), m_planarCharts(m_data), m_clusteredCharts(m_data, m_planarCharts) {}
|
m_originalUvCharts(m_data) {}
|
||||||
|
|
||||||
uint32_t chartCount() const {
|
uint32_t chartCount() const {
|
||||||
return m_originalUvCharts.chartCount() + m_planarCharts.chartCount() + m_clusteredCharts.chartCount();
|
return m_originalUvCharts.chartCount();
|
||||||
}
|
}
|
||||||
|
|
||||||
ConstArrayView<uint32_t> chartFaces(uint32_t chartIndex) const {
|
ConstArrayView<uint32_t> chartFaces(uint32_t chartIndex) const {
|
||||||
if (chartIndex < m_originalUvCharts.chartCount())
|
return m_originalUvCharts.chartFaces(chartIndex);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const Basis &chartBasis(uint32_t chartIndex) const {
|
const Basis &chartBasis(uint32_t chartIndex) const {
|
||||||
if (chartIndex < m_originalUvCharts.chartCount())
|
return m_originalUvCharts.chartBasis(chartIndex);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ChartGeneratorType::Enum chartGeneratorType(uint32_t chartIndex) const {
|
ChartGeneratorType::Enum chartGeneratorType(uint32_t chartIndex) const {
|
||||||
if (chartIndex < m_originalUvCharts.chartCount())
|
|
||||||
return ChartGeneratorType::OriginalUv;
|
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) {
|
void reset(const Mesh *mesh, const ChartOptions &options) {
|
||||||
@ -5622,24 +4680,14 @@ struct Atlas {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void compute() {
|
void compute() {
|
||||||
if (m_data.options.useInputMeshUvs) {
|
|
||||||
XA_PROFILE_START(originalUvCharts)
|
XA_PROFILE_START(originalUvCharts)
|
||||||
m_originalUvCharts.compute();
|
m_originalUvCharts.compute();
|
||||||
XA_PROFILE_END(originalUvCharts)
|
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:
|
private:
|
||||||
AtlasData m_data;
|
AtlasData m_data;
|
||||||
OriginalUvCharts m_originalUvCharts;
|
OriginalUvCharts m_originalUvCharts;
|
||||||
PlanarCharts m_planarCharts;
|
|
||||||
ClusteredCharts m_clusteredCharts;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
struct ComputeUvMeshChartsTaskArgs {
|
struct ComputeUvMeshChartsTaskArgs {
|
||||||
|
Loading…
Reference in New Issue
Block a user