|
|
|
@ -18543,6 +18543,176 @@ bool MeshTexture::SelectBestViewsForVirtualFaces( |
|
|
|
DEBUG_EXTRA("Fallback by center face : %zu", fallbackByCenterFace); |
|
|
|
DEBUG_EXTRA("Fallback by center face : %zu", fallbackByCenterFace); |
|
|
|
DEBUG_EXTRA("================================================="); |
|
|
|
DEBUG_EXTRA("================================================="); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// ================================================================
|
|
|
|
|
|
|
|
// ★★★ 连通域强制统一视角(消除色差碎片)★★★
|
|
|
|
|
|
|
|
// ================================================================
|
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
TD_TIMER_START(); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const FIndex NF = (FIndex)faceToView.size(); |
|
|
|
|
|
|
|
if (NF == 0) return true; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 1. 构建面片邻接关系(只考虑有效面片)
|
|
|
|
|
|
|
|
std::vector<std::vector<FIndex>> adjList(NF); |
|
|
|
|
|
|
|
for (FIndex fid = 0; fid < NF; ++fid) { |
|
|
|
|
|
|
|
if (faceToView[fid] == NO_ID) continue; |
|
|
|
|
|
|
|
if (fid >= (FIndex)scene.mesh.faceFaces.size()) continue; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const Mesh::FaceFaces& neighbors = scene.mesh.faceFaces[fid]; |
|
|
|
|
|
|
|
for (int k = 0; k < 3; ++k) { |
|
|
|
|
|
|
|
FIndex nb = neighbors[k]; |
|
|
|
|
|
|
|
if (nb == NO_ID || nb >= NF) continue; |
|
|
|
|
|
|
|
if (faceToView[nb] == NO_ID) continue; |
|
|
|
|
|
|
|
adjList[fid].push_back(nb); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 2. 连通域标记(BFS)
|
|
|
|
|
|
|
|
std::vector<int> component(NF, -1); |
|
|
|
|
|
|
|
int numComponents = 0; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for (FIndex fid = 0; fid < NF; ++fid) { |
|
|
|
|
|
|
|
if (faceToView[fid] == NO_ID) continue; |
|
|
|
|
|
|
|
if (component[fid] >= 0) continue; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
std::queue<FIndex> q; |
|
|
|
|
|
|
|
q.push(fid); |
|
|
|
|
|
|
|
component[fid] = numComponents; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
while (!q.empty()) { |
|
|
|
|
|
|
|
FIndex cur = q.front(); q.pop(); |
|
|
|
|
|
|
|
for (FIndex nb : adjList[cur]) { |
|
|
|
|
|
|
|
if (component[nb] < 0) { |
|
|
|
|
|
|
|
component[nb] = numComponents; |
|
|
|
|
|
|
|
q.push(nb); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
numComponents++; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
DEBUG_EXTRA("Connected components: %d components from %d faces", numComponents, NF); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 3. 统计每个连通域的视角分布
|
|
|
|
|
|
|
|
struct CompInfo { |
|
|
|
|
|
|
|
std::vector<FIndex> faces; |
|
|
|
|
|
|
|
std::map<IIndex, int> viewVotes; |
|
|
|
|
|
|
|
std::map<IIndex, float> viewScoreSum; |
|
|
|
|
|
|
|
std::map<IIndex, float> viewAvgScore; |
|
|
|
|
|
|
|
}; |
|
|
|
|
|
|
|
std::vector<CompInfo> comps(numComponents); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for (FIndex fid = 0; fid < NF; ++fid) { |
|
|
|
|
|
|
|
if (faceToView[fid] == NO_ID) continue; |
|
|
|
|
|
|
|
int c = component[fid]; |
|
|
|
|
|
|
|
if (c < 0) continue; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
IIndex vid = faceToView[fid]; |
|
|
|
|
|
|
|
float score = faceScores[fid]; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
comps[c].faces.push_back(fid); |
|
|
|
|
|
|
|
comps[c].viewVotes[vid]++; |
|
|
|
|
|
|
|
comps[c].viewScoreSum[vid] += score; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for (int c = 0; c < numComponents; ++c) { |
|
|
|
|
|
|
|
for (auto& vs : comps[c].viewScoreSum) { |
|
|
|
|
|
|
|
IIndex vid = vs.first; |
|
|
|
|
|
|
|
int votes = comps[c].viewVotes[vid]; |
|
|
|
|
|
|
|
comps[c].viewAvgScore[vid] = vs.second / votes; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 4. 每个连通域选最佳视角
|
|
|
|
|
|
|
|
int changedFaces = 0; |
|
|
|
|
|
|
|
int smallComponents = 0; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for (int c = 0; c < numComponents; ++c) { |
|
|
|
|
|
|
|
auto& comp = comps[c]; |
|
|
|
|
|
|
|
if (comp.faces.empty()) continue; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 小连通域合并
|
|
|
|
|
|
|
|
if ((int)comp.faces.size() < 5) { |
|
|
|
|
|
|
|
smallComponents++; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
std::map<int, int> neighborCompVotes; |
|
|
|
|
|
|
|
for (FIndex fid : comp.faces) { |
|
|
|
|
|
|
|
for (FIndex nb : adjList[fid]) { |
|
|
|
|
|
|
|
int nc = component[nb]; |
|
|
|
|
|
|
|
if (nc >= 0 && nc != c) { |
|
|
|
|
|
|
|
neighborCompVotes[nc]++; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (!neighborCompVotes.empty()) { |
|
|
|
|
|
|
|
auto bestNb = std::max_element(neighborCompVotes.begin(), neighborCompVotes.end(), |
|
|
|
|
|
|
|
[](const auto& a, const auto& b) { return a.second < b.second; }); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
int targetComp = bestNb->first; |
|
|
|
|
|
|
|
IIndex targetView = NO_ID; |
|
|
|
|
|
|
|
int maxVotes = 0; |
|
|
|
|
|
|
|
for (const auto& vv : comps[targetComp].viewVotes) { |
|
|
|
|
|
|
|
if (vv.second > maxVotes) { |
|
|
|
|
|
|
|
maxVotes = vv.second; |
|
|
|
|
|
|
|
targetView = vv.first; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (targetView != NO_ID) { |
|
|
|
|
|
|
|
for (FIndex fid : comp.faces) { |
|
|
|
|
|
|
|
if (faceToView[fid] != targetView) { |
|
|
|
|
|
|
|
faceToView[fid] = targetView; |
|
|
|
|
|
|
|
faceScores[fid] = comps[targetComp].viewAvgScore[targetView]; |
|
|
|
|
|
|
|
changedFaces++; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
continue; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 正常连通域选视角
|
|
|
|
|
|
|
|
IIndex bestView = NO_ID; |
|
|
|
|
|
|
|
float bestWeightedScore = -FLT_MAX; |
|
|
|
|
|
|
|
int totalFaces = (int)comp.faces.size(); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for (const auto& vv : comp.viewVotes) { |
|
|
|
|
|
|
|
IIndex vid = vv.first; |
|
|
|
|
|
|
|
int votes = vv.second; |
|
|
|
|
|
|
|
float avgScore = comp.viewAvgScore[vid]; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
float voteRatio = (float)votes / totalFaces; |
|
|
|
|
|
|
|
float normalizedScore = std::max(0.0f, std::min(1.0f, avgScore)); |
|
|
|
|
|
|
|
float weightedScore = 0.6f * voteRatio + 0.4f * normalizedScore; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (weightedScore > bestWeightedScore) { |
|
|
|
|
|
|
|
bestWeightedScore = weightedScore; |
|
|
|
|
|
|
|
bestView = vid; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (bestView == NO_ID) continue; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for (FIndex fid : comp.faces) { |
|
|
|
|
|
|
|
if (faceToView[fid] != bestView) { |
|
|
|
|
|
|
|
faceToView[fid] = bestView; |
|
|
|
|
|
|
|
faceScores[fid] = comp.viewAvgScore[bestView]; |
|
|
|
|
|
|
|
changedFaces++; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
DEBUG_EXTRA("Component unification: %d faces changed, %d small components merged (%s)", |
|
|
|
|
|
|
|
changedFaces, smallComponents, TD_TIMER_GET_FMT().c_str()); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// ★★★ 注意:不要在这里更新 faceViews/faceViewWeights!
|
|
|
|
|
|
|
|
// faceViews 会在视图选择阶段的末尾,由 SelectBestViewsForVirtualFaces 根据 faceToView 重建
|
|
|
|
|
|
|
|
// 如果在这里强行同步,会破坏虚拟面片与视图的映射关系,导致采样坐标错误
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
DEBUG_EXTRA("Unification complete: %d components processed.", numComponents); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
return true; |
|
|
|
return true; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|