|
|
|
@ -676,17 +676,27 @@ public: |
|
|
|
bool CheckUVContinuity(const std::vector<FIndex>& faceList); |
|
|
|
bool CheckUVContinuity(const std::vector<FIndex>& faceList); |
|
|
|
|
|
|
|
|
|
|
|
struct TexelViewInfo { |
|
|
|
struct TexelViewInfo { |
|
|
|
IIndex best_view_id; // 最佳视图ID
|
|
|
|
float best_weight; |
|
|
|
float best_weight; // 最佳权重
|
|
|
|
IIndex best_view_id; |
|
|
|
Point2f best_proj; // 最佳投影坐标
|
|
|
|
Point2f best_proj; |
|
|
|
std::vector<IIndex> candidate_views; // 候选视图列表
|
|
|
|
|
|
|
|
}; |
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
bool SelectBestViewForTexel(const Point3f& worldPos, |
|
|
|
bool SelectBestViewForTexel(const Point3f& worldPos, |
|
|
|
const Normal& normal, |
|
|
|
const Normal& /*normal*/, |
|
|
|
const std::vector<IIndex>& candidateViews, |
|
|
|
const std::vector<IIndex>& candidateViews, |
|
|
|
const std::vector<float>& viewWeights, |
|
|
|
const std::vector<float>& viewWeights, |
|
|
|
TexelViewInfo& result); |
|
|
|
TexelViewInfo& result, |
|
|
|
|
|
|
|
std::vector<std::pair<IIndex, Point2f>>& topViews); |
|
|
|
|
|
|
|
bool SelectBestSingleView( |
|
|
|
|
|
|
|
const Point3f& worldPos, |
|
|
|
|
|
|
|
const Normal& /*normal*/, |
|
|
|
|
|
|
|
const std::vector<IIndex>& candidateViews, |
|
|
|
|
|
|
|
const std::vector<float>& viewWeights, |
|
|
|
|
|
|
|
TexelViewInfo& result); |
|
|
|
|
|
|
|
Pixel8U BlendTopTwoViews( |
|
|
|
|
|
|
|
const std::vector<std::pair<IIndex, Point2f>>& topViews, |
|
|
|
|
|
|
|
const std::vector<IIndex>& candidateViews, |
|
|
|
|
|
|
|
const std::vector<float>& viewWeights); |
|
|
|
|
|
|
|
|
|
|
|
float CalculateViewScale(const Camera& cam, const Point3d& pos); |
|
|
|
float CalculateViewScale(const Camera& cam, const Point3d& pos); |
|
|
|
|
|
|
|
|
|
|
|
@ -15241,40 +15251,125 @@ float MeshTexture::CalculateViewScale(const Camera& cam, const Point3d& pos) { |
|
|
|
bool MeshTexture::SelectBestViewForTexel(const Point3f& worldPos, |
|
|
|
bool MeshTexture::SelectBestViewForTexel(const Point3f& worldPos, |
|
|
|
const Normal& /*normal*/, |
|
|
|
const Normal& /*normal*/, |
|
|
|
const std::vector<IIndex>& candidateViews, |
|
|
|
const std::vector<IIndex>& candidateViews, |
|
|
|
const std::vector<float>& /*viewWeights*/, |
|
|
|
const std::vector<float>& viewWeights, |
|
|
|
TexelViewInfo& result) |
|
|
|
TexelViewInfo& result, |
|
|
|
|
|
|
|
std::vector<std::pair<IIndex, Point2f>>& topViews) |
|
|
|
{ |
|
|
|
{ |
|
|
|
|
|
|
|
topViews.clear(); |
|
|
|
result.best_weight = -1.0f; |
|
|
|
result.best_weight = -1.0f; |
|
|
|
|
|
|
|
result.best_view_id = static_cast<IIndex>(-1); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
struct CandidateView { |
|
|
|
|
|
|
|
IIndex viewId; |
|
|
|
|
|
|
|
Point2f proj; |
|
|
|
|
|
|
|
float weight; |
|
|
|
|
|
|
|
}; |
|
|
|
|
|
|
|
std::vector<CandidateView> candidates; |
|
|
|
|
|
|
|
candidates.reserve(candidateViews.size()); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 收集所有有效候选视图
|
|
|
|
for (size_t i = 0; i < candidateViews.size(); ++i) { |
|
|
|
for (size_t i = 0; i < candidateViews.size(); ++i) { |
|
|
|
const IIndex viewId = candidateViews[i]; |
|
|
|
const IIndex viewId = candidateViews[i]; |
|
|
|
|
|
|
|
if (viewId >= images.size()) |
|
|
|
|
|
|
|
continue; |
|
|
|
|
|
|
|
|
|
|
|
const Image& img = images[viewId]; |
|
|
|
const Image& img = images[viewId]; |
|
|
|
|
|
|
|
|
|
|
|
// ✅ 和 V1 完全一致
|
|
|
|
|
|
|
|
Point2f proj = ProjectPointWithAutoCorrection( |
|
|
|
Point2f proj = ProjectPointWithAutoCorrection( |
|
|
|
img.camera, |
|
|
|
img.camera, |
|
|
|
Vertex(worldPos.x, worldPos.y, worldPos.z), |
|
|
|
Vertex(worldPos.x, worldPos.y, worldPos.z), |
|
|
|
img |
|
|
|
img |
|
|
|
); |
|
|
|
); |
|
|
|
|
|
|
|
|
|
|
|
// ✅ 只用最基本、最安全的检查
|
|
|
|
// 严格的投影验证
|
|
|
|
if (!img.camera.IsInFront(Vertex(worldPos.x, worldPos.y, worldPos.z))) |
|
|
|
if (!img.image.isInside(proj)) |
|
|
|
|
|
|
|
continue; |
|
|
|
|
|
|
|
if (!img.camera.IsInFront(worldPos)) |
|
|
|
|
|
|
|
continue; |
|
|
|
|
|
|
|
if (!ValidateProjection(worldPos, img, proj)) |
|
|
|
continue; |
|
|
|
continue; |
|
|
|
|
|
|
|
|
|
|
|
if (!img.image.isInside(proj)) |
|
|
|
const float w = viewWeights[i]; |
|
|
|
|
|
|
|
if (w <= 0.1f) |
|
|
|
continue; |
|
|
|
continue; |
|
|
|
|
|
|
|
|
|
|
|
// ✅ 先不选“最佳”,先选“第一个能用的”
|
|
|
|
candidates.push_back({viewId, proj, w}); |
|
|
|
result.best_weight = 1.0f; |
|
|
|
|
|
|
|
result.best_view_id = viewId; |
|
|
|
|
|
|
|
result.best_proj = proj; |
|
|
|
|
|
|
|
return true; |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
return false; |
|
|
|
if (candidates.empty()) |
|
|
|
|
|
|
|
return false; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 按权重降序排序
|
|
|
|
|
|
|
|
std::sort(candidates.begin(), candidates.end(), |
|
|
|
|
|
|
|
[](const CandidateView& a, const CandidateView& b) { |
|
|
|
|
|
|
|
return a.weight > b.weight; |
|
|
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 取前两个(如果有两个的话)
|
|
|
|
|
|
|
|
const int numViews = std::min(2, (int)candidates.size()); |
|
|
|
|
|
|
|
for (int i = 0; i < numViews; ++i) { |
|
|
|
|
|
|
|
topViews.emplace_back(candidates[i].viewId, candidates[i].proj); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 设置最佳视图信息
|
|
|
|
|
|
|
|
result.best_view_id = candidates[0].viewId; |
|
|
|
|
|
|
|
result.best_proj = candidates[0].proj; |
|
|
|
|
|
|
|
result.best_weight = candidates[0].weight; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return true; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
Pixel8U MeshTexture::BlendTopTwoViews( |
|
|
|
|
|
|
|
const std::vector<std::pair<IIndex, Point2f>>& topViews, |
|
|
|
|
|
|
|
const std::vector<IIndex>& candidateViews, |
|
|
|
|
|
|
|
const std::vector<float>& viewWeights) |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
if (topViews.empty()) |
|
|
|
|
|
|
|
return Pixel8U(0, 0, 0); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 单视图情况
|
|
|
|
|
|
|
|
if (topViews.size() == 1) { |
|
|
|
|
|
|
|
const Image& img = images[topViews[0].first]; |
|
|
|
|
|
|
|
return SampleImageBicubic(img.image, topViews[0].second); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 双视图融合
|
|
|
|
|
|
|
|
double sumB = 0, sumG = 0, sumR = 0; |
|
|
|
|
|
|
|
float totalWeight = 0.0f; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for (const auto& view : topViews) { |
|
|
|
|
|
|
|
const Image& img = images[view.first]; |
|
|
|
|
|
|
|
Pixel8U color = SampleImageBicubic(img.image, view.second); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 查找对应权重
|
|
|
|
|
|
|
|
float w = 0.0f; |
|
|
|
|
|
|
|
for (size_t i = 0; i < candidateViews.size(); ++i) { |
|
|
|
|
|
|
|
if (candidateViews[i] == view.first) { |
|
|
|
|
|
|
|
w = viewWeights[i]; |
|
|
|
|
|
|
|
break; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (w <= 0.0f) |
|
|
|
|
|
|
|
continue; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
sumB += color[0] * w; |
|
|
|
|
|
|
|
sumG += color[1] * w; |
|
|
|
|
|
|
|
sumR += color[2] * w; |
|
|
|
|
|
|
|
totalWeight += w; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (totalWeight < 1e-6f) |
|
|
|
|
|
|
|
return Pixel8U(0, 0, 0); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Pixel8U result; |
|
|
|
|
|
|
|
result[0] = cv::saturate_cast<uchar>(sumB / totalWeight); |
|
|
|
|
|
|
|
result[1] = cv::saturate_cast<uchar>(sumG / totalWeight); |
|
|
|
|
|
|
|
result[2] = cv::saturate_cast<uchar>(sumR / totalWeight); |
|
|
|
|
|
|
|
return result; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//*
|
|
|
|
Mesh::Image8U3Arr MeshTexture::GenerateMultiViewTextureAtlasWithVirtualFaces( |
|
|
|
Mesh::Image8U3Arr MeshTexture::GenerateMultiViewTextureAtlasWithVirtualFaces( |
|
|
|
const VirtualFaceMap& virtualFaceMap, |
|
|
|
const VirtualFaceMap& virtualFaceMap, |
|
|
|
const VirtualFaceDataArr& virtualFaceDatas, // 这个参数现在不被使用,但保留以保持接口兼容
|
|
|
|
const VirtualFaceDataArr& virtualFaceDatas, // 这个参数现在不被使用,但保留以保持接口兼容
|
|
|
|
@ -15516,16 +15611,16 @@ Mesh::Image8U3Arr MeshTexture::GenerateMultiViewTextureAtlasWithVirtualFaces( |
|
|
|
DEBUG_EXTRA("Multi-view texture atlas generation with virtual faces complete"); |
|
|
|
DEBUG_EXTRA("Multi-view texture atlas generation with virtual faces complete"); |
|
|
|
return textures; |
|
|
|
return textures; |
|
|
|
} |
|
|
|
} |
|
|
|
*/ |
|
|
|
//*/
|
|
|
|
//*
|
|
|
|
/*
|
|
|
|
Mesh::Image8U3Arr MeshTexture::GenerateMultiViewTextureAtlasWithVirtualFaces( |
|
|
|
Mesh::Image8U3Arr MeshTexture::GenerateMultiViewTextureAtlasWithVirtualFaces( |
|
|
|
const VirtualFaceMap& virtualFaceMap, |
|
|
|
const VirtualFaceMap& virtualFaceMap, |
|
|
|
const VirtualFaceDataArr& virtualFaceDatas, |
|
|
|
const VirtualFaceDataArr&, |
|
|
|
const std::vector<std::vector<IIndex>>& faceViews, |
|
|
|
const std::vector<std::vector<IIndex>>& faceViews, |
|
|
|
const std::vector<std::vector<float>>& faceViewWeights, |
|
|
|
const std::vector<std::vector<float>>& faceViewWeights, |
|
|
|
unsigned nTextureSizeMultiple, |
|
|
|
unsigned nTextureSizeMultiple, |
|
|
|
Pixel8U colEmpty, |
|
|
|
Pixel8U colEmpty, |
|
|
|
float fSharpnessWeight) |
|
|
|
float) |
|
|
|
{ |
|
|
|
{ |
|
|
|
DEBUG_EXTRA("Generating multi-view texture atlas with virtual faces"); |
|
|
|
DEBUG_EXTRA("Generating multi-view texture atlas with virtual faces"); |
|
|
|
|
|
|
|
|
|
|
|
@ -15542,34 +15637,62 @@ Mesh::Image8U3Arr MeshTexture::GenerateMultiViewTextureAtlasWithVirtualFaces( |
|
|
|
|
|
|
|
|
|
|
|
const int textureSize = ComputeOptimalTextureSize(uvWidth, uvHeight, nTextureSizeMultiple); |
|
|
|
const int textureSize = ComputeOptimalTextureSize(uvWidth, uvHeight, nTextureSizeMultiple); |
|
|
|
|
|
|
|
|
|
|
|
// 3. 创建纹理图集(只做这一件事)
|
|
|
|
// 3. 创建纹理图集
|
|
|
|
Mesh::Image8U3Arr textures; |
|
|
|
Mesh::Image8U3Arr textures; |
|
|
|
Image8U3& textureAtlas = textures.emplace_back(textureSize, textureSize); |
|
|
|
Image8U3& textureAtlas = textures.emplace_back(textureSize, textureSize); |
|
|
|
textureAtlas.setTo(cv::Scalar(colEmpty.b, colEmpty.g, colEmpty.r)); |
|
|
|
textureAtlas.setTo(cv::Scalar(colEmpty.b, colEmpty.g, colEmpty.r)); |
|
|
|
|
|
|
|
|
|
|
|
// 缓存列数和数据指针(性能关键)
|
|
|
|
// 缓存列数和数据指针
|
|
|
|
const int cols = textureAtlas.cols; |
|
|
|
const int cols = textureAtlas.cols; |
|
|
|
Pixel8U* data = reinterpret_cast<Pixel8U*>(textureAtlas.data); |
|
|
|
Pixel8U* data = reinterpret_cast<Pixel8U*>(textureAtlas.data); |
|
|
|
|
|
|
|
|
|
|
|
DEBUG_EXTRA("Texture atlas size: %dx%d, UV bounds: [%.3f,%.3f]-[%.3f,%.3f]", |
|
|
|
DEBUG_EXTRA("Texture atlas size: %dx%d, UV bounds: [%.3f,%.3f]-[%.3f,%.3f]", |
|
|
|
textureSize, textureSize, |
|
|
|
textureSize, textureSize, |
|
|
|
uvBounds.ptMin.x(), uvBounds.ptMin.y(), |
|
|
|
uvBounds.ptMin.x(), uvBounds.ptMin.y(), |
|
|
|
uvBounds.ptMax.x(), uvBounds.ptMax.y()); |
|
|
|
uvBounds.ptMax.x(), uvBounds.ptMax.y()); |
|
|
|
|
|
|
|
|
|
|
|
// 4. 遍历虚拟面(OpenMP安全,因为只写不读)
|
|
|
|
// 4. 遍历虚拟面(OpenMP并行)
|
|
|
|
#ifdef _USE_OPENMP |
|
|
|
#ifdef _USE_OPENMP |
|
|
|
#pragma omp parallel for schedule(dynamic) |
|
|
|
#pragma omp parallel for schedule(dynamic) |
|
|
|
#endif |
|
|
|
#endif |
|
|
|
for (int_t idxVF = 0; idxVF < (int_t)virtualFaceMap.size(); ++idxVF) { |
|
|
|
for (int_t idxVF = 0; idxVF < (int_t)virtualFaceMap.size(); ++idxVF) { |
|
|
|
|
|
|
|
// 边界检查(防御性编程)
|
|
|
|
|
|
|
|
if (idxVF >= (int_t)faceViews.size() || idxVF >= (int_t)faceViewWeights.size()) |
|
|
|
|
|
|
|
continue; |
|
|
|
|
|
|
|
|
|
|
|
const VirtualFace& vf = virtualFaceMap[idxVF]; |
|
|
|
const VirtualFace& vf = virtualFaceMap[idxVF]; |
|
|
|
if (faceViews[idxVF].empty()) continue; |
|
|
|
if (faceViews[idxVF].empty()) |
|
|
|
|
|
|
|
continue; |
|
|
|
|
|
|
|
|
|
|
|
for (FIndex faceID : vf.faces) { |
|
|
|
for (FIndex faceID : vf.faces) { |
|
|
|
|
|
|
|
// 防御性检查faceID
|
|
|
|
|
|
|
|
if (faceID >= scene.mesh.faces.size()) |
|
|
|
|
|
|
|
continue; |
|
|
|
|
|
|
|
|
|
|
|
const Face& face = scene.mesh.faces[faceID]; |
|
|
|
const Face& face = scene.mesh.faces[faceID]; |
|
|
|
const TexCoord* uv = &scene.mesh.faceTexcoords[faceID * 3]; |
|
|
|
const TexCoord* uv = &scene.mesh.faceTexcoords[faceID * 3]; |
|
|
|
const Vertex* vtx = &scene.mesh.vertices[face[0]]; |
|
|
|
const Vertex* vtx = &scene.mesh.vertices[face[0]]; |
|
|
|
const Normal& faceNormal = scene.mesh.faceNormals[faceID]; |
|
|
|
const Normal& faceNormal = scene.mesh.faceNormals[faceID]; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// ✅ 关键:用面片中心选择最佳视图(面片级选视图)
|
|
|
|
|
|
|
|
Point3d faceCenter( |
|
|
|
|
|
|
|
(vtx[0].x + vtx[1].x + vtx[2].x) / 3.0, |
|
|
|
|
|
|
|
(vtx[0].y + vtx[1].y + vtx[2].y) / 3.0, |
|
|
|
|
|
|
|
(vtx[0].z + vtx[1].z + vtx[2].z) / 3.0 |
|
|
|
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
TexelViewInfo viewInfo; |
|
|
|
|
|
|
|
if (!SelectBestSingleView( |
|
|
|
|
|
|
|
Point3f(faceCenter.x, faceCenter.y, faceCenter.z), |
|
|
|
|
|
|
|
faceNormal, |
|
|
|
|
|
|
|
faceViews[idxVF], |
|
|
|
|
|
|
|
faceViewWeights[idxVF], |
|
|
|
|
|
|
|
viewInfo)) { |
|
|
|
|
|
|
|
continue; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const Image& bestImg = images[viewInfo.best_view_id]; |
|
|
|
|
|
|
|
|
|
|
|
// UV边界框
|
|
|
|
// UV边界框
|
|
|
|
AABB2f uvBox(true); |
|
|
|
AABB2f uvBox(true); |
|
|
|
uvBox.InsertFull(uv[0]); |
|
|
|
uvBox.InsertFull(uv[0]); |
|
|
|
@ -15591,38 +15714,33 @@ Mesh::Image8U3Arr MeshTexture::GenerateMultiViewTextureAtlasWithVirtualFaces( |
|
|
|
if (!PointInTriangle(texCoord, uv[0], uv[1], uv[2], bary)) |
|
|
|
if (!PointInTriangle(texCoord, uv[0], uv[1], uv[2], bary)) |
|
|
|
continue; |
|
|
|
continue; |
|
|
|
|
|
|
|
|
|
|
|
// 计算世界点(直接插值,O(1))
|
|
|
|
// 计算世界点
|
|
|
|
Point3d worldPos( |
|
|
|
Point3d worldPos( |
|
|
|
vtx[0].x * bary.x + vtx[1].x * bary.y + vtx[2].x * bary.z, |
|
|
|
vtx[0].x * bary.x + vtx[1].x * bary.y + vtx[2].x * bary.z, |
|
|
|
vtx[0].y * bary.x + vtx[1].y * bary.y + vtx[2].y * bary.z, |
|
|
|
vtx[0].y * bary.x + vtx[1].y * bary.y + vtx[2].y * bary.z, |
|
|
|
vtx[0].z * bary.x + vtx[1].z * bary.y + vtx[2].z * bary.z |
|
|
|
vtx[0].z * bary.x + vtx[1].z * bary.y + vtx[2].z * bary.z |
|
|
|
); |
|
|
|
); |
|
|
|
|
|
|
|
|
|
|
|
// 逐像素选图
|
|
|
|
// ✅ 只投影,不再选视图(使用面片选定的视图)
|
|
|
|
TexelViewInfo viewInfo; |
|
|
|
Point2f proj = ProjectPointWithAutoCorrection( |
|
|
|
if (!SelectBestViewForTexel( |
|
|
|
bestImg.camera, |
|
|
|
Point3f(worldPos.x, worldPos.y, worldPos.z), |
|
|
|
Vertex(worldPos.x, worldPos.y, worldPos.z), |
|
|
|
faceNormal, |
|
|
|
bestImg |
|
|
|
faceViews[idxVF], |
|
|
|
); |
|
|
|
faceViewWeights[idxVF], |
|
|
|
|
|
|
|
viewInfo)) |
|
|
|
if (!bestImg.image.isInside(proj)) |
|
|
|
|
|
|
|
continue; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Pixel8U color = SampleImageBicubic(bestImg.image, proj); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// ✅ 检查是否为空色(避免写入无效像素)
|
|
|
|
|
|
|
|
if (color[0] == colEmpty.r && |
|
|
|
|
|
|
|
color[1] == colEmpty.g && |
|
|
|
|
|
|
|
color[2] == colEmpty.b) |
|
|
|
continue; |
|
|
|
continue; |
|
|
|
|
|
|
|
|
|
|
|
// 采样并写入
|
|
|
|
// ✅ 写入纹理(无锁,后写覆盖先写)
|
|
|
|
const Image& bestImg = images[viewInfo.best_view_id]; |
|
|
|
data[y * cols + x] = color; |
|
|
|
// Pixel8U color = SampleImageBicubic(bestImg.image, viewInfo.best_proj);
|
|
|
|
|
|
|
|
Pixel8U color = SampleImageBilinear(bestImg.image, viewInfo.best_proj); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// // ✅ 正确、官方、颜色100%一致的写法
|
|
|
|
|
|
|
|
// Pixel8U color;
|
|
|
|
|
|
|
|
// bestImg.image.sample(
|
|
|
|
|
|
|
|
// viewInfo.best_proj.x,
|
|
|
|
|
|
|
|
// viewInfo.best_proj.y,
|
|
|
|
|
|
|
|
// color,
|
|
|
|
|
|
|
|
// SEACAVE::IMAGE_SAMPLE_BICUBIC
|
|
|
|
|
|
|
|
// );
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
data[y * cols + x] = color; |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
@ -15631,6 +15749,52 @@ Mesh::Image8U3Arr MeshTexture::GenerateMultiViewTextureAtlasWithVirtualFaces( |
|
|
|
DEBUG_EXTRA("Multi-view texture atlas generation with virtual faces complete"); |
|
|
|
DEBUG_EXTRA("Multi-view texture atlas generation with virtual faces complete"); |
|
|
|
return textures; |
|
|
|
return textures; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// ✅ 单视图选择函数(面片级)
|
|
|
|
|
|
|
|
bool MeshTexture::SelectBestSingleView( |
|
|
|
|
|
|
|
const Point3f& worldPos, |
|
|
|
|
|
|
|
const Normal&, |
|
|
|
|
|
|
|
const std::vector<IIndex>& candidateViews, |
|
|
|
|
|
|
|
const std::vector<float>& viewWeights, |
|
|
|
|
|
|
|
TexelViewInfo& result) |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
result.best_weight = -1.0f; |
|
|
|
|
|
|
|
result.best_view_id = static_cast<IIndex>(-1); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for (size_t i = 0; i < candidateViews.size(); ++i) { |
|
|
|
|
|
|
|
const IIndex viewId = candidateViews[i]; |
|
|
|
|
|
|
|
if (viewId >= images.size()) |
|
|
|
|
|
|
|
continue; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const Image& img = images[viewId]; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Point2f proj = ProjectPointWithAutoCorrection( |
|
|
|
|
|
|
|
img.camera, |
|
|
|
|
|
|
|
Vertex(worldPos.x, worldPos.y, worldPos.z), |
|
|
|
|
|
|
|
img |
|
|
|
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 严格的投影验证
|
|
|
|
|
|
|
|
if (!img.image.isInside(proj)) |
|
|
|
|
|
|
|
continue; |
|
|
|
|
|
|
|
if (!img.camera.IsInFront(worldPos)) |
|
|
|
|
|
|
|
continue; |
|
|
|
|
|
|
|
if (!ValidateProjection(worldPos, img, proj)) |
|
|
|
|
|
|
|
continue; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const float w = viewWeights[i]; |
|
|
|
|
|
|
|
if (w <= 0.1f) |
|
|
|
|
|
|
|
continue; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (w > result.best_weight) { |
|
|
|
|
|
|
|
result.best_weight = w; |
|
|
|
|
|
|
|
result.best_view_id = viewId; |
|
|
|
|
|
|
|
result.best_proj = proj; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return result.best_view_id != static_cast<IIndex>(-1); |
|
|
|
|
|
|
|
} |
|
|
|
//*/
|
|
|
|
//*/
|
|
|
|
|
|
|
|
|
|
|
|
bool MeshTexture::TextureWithExistingUV( |
|
|
|
bool MeshTexture::TextureWithExistingUV( |
|
|
|
|