Browse Source

单像素清晰化保存

ManualUV
hesuicong 4 weeks ago
parent
commit
5229b4f284
  1. 518
      libs/MVS/SceneTexture.cpp

518
libs/MVS/SceneTexture.cpp

@ -692,6 +692,13 @@ public: @@ -692,6 +692,13 @@ public:
bool GetWorldPositionAndNormal(const Point2f& texCoord,
Point3f& worldPos, Normal& normal, FIndex& faceID);
float CalculatePixelDensity(const Camera& cam, const Point3d& pos);
float CalculateLocalViewDensity(const Point3f& pos,
const std::vector<IIndex>& visibleViews);
int GetAdaptiveTexelSize(const Point3f& pos,
const std::vector<IIndex>& visibleViews,
int baseTexelSize);
Pixel8U SampleImageBicubic(const Image8U3& img, const Point2f& pt);
Mesh::Image8U3Arr GenerateMultiViewTextureAtlasWithVirtualFaces(
const VirtualFaceMap& virtualFaceMap,
@ -15088,70 +15095,186 @@ bool MeshTexture::GetWorldPositionAndNormal( @@ -15088,70 +15095,186 @@ bool MeshTexture::GetWorldPositionAndNormal(
return false;
}
float MeshTexture::CalculatePixelDensity(const Camera& cam, const Point3d& pos) {
// 1. 点在相机坐标系下的坐标
Point3d posCam = cam.R * pos + cam.C; // 世界坐标转相机坐标
const double Z = posCam.z;
if (Z <= DBL_EPSILON) return 0.0f; // 点在相机后
// 2. 透视投影的雅可比矩阵(描述世界空间变化到图像空间变化的映射)
// 投影公式:u = fx*(X/Z) + cx, v = fy*(Y/Z) + cy
const double fx = cam.K(0,0);
const double fy = cam.K(1,1);
const double invZ = 1.0 / Z;
const double invZ2 = invZ * invZ;
// 雅可比矩阵 J 的元素:∂u/∂X, ∂u/∂Y, ∂v/∂X, ∂v/∂Y
const double dudx = fx * invZ;
const double dudy = 0.0;
const double dvdx = 0.0;
const double dvdy = fy * invZ;
// (注:这里简化了Z方向的影响,因为切平面内Z的变化很小,不影响密度计算)
// 3. 面积缩放比 = 雅可比行列式的绝对值
const double scale = std::abs(dudx * dvdy - dudy * dvdx);
return static_cast<float>(scale);
}
float MeshTexture::CalculateLocalViewDensity(const Point3f& pos,
const std::vector<IIndex>& visibleViews) {
float density = 0.0f;
for (IIndex viewId : visibleViews) {
const Camera& cam = images[viewId].camera;
// 计算该视图在pos处的像素密度
float pixelDensity = CalculatePixelDensity(cam, pos);
density += pixelDensity;
}
return density;
}
int MeshTexture::GetAdaptiveTexelSize(const Point3f& pos,
const std::vector<IIndex>& visibleViews,
int baseTexelSize) {
float density = CalculateLocalViewDensity(pos, visibleViews);
// 密度越高,纹素越小(分辨率越高)
if (density > 10.0f) return baseTexelSize / 2; // 高密度区:2倍分辨率
else if (density > 5.0f) return baseTexelSize * 2/3; // 中高密度
else if (density > 2.0f) return baseTexelSize; // 正常密度
else return baseTexelSize * 2; // 低密度区:降低分辨率
}
// 标准Keys立方滤波器(OpenCV/RC同款,a=-0.5)
static inline float CubicKernel(float x, float a = -0.5f) {
x = std::abs(x);
if (x <= 1.0f) {
return (a + 2.0f) * x*x*x - (a + 3.0f) * x*x + 1.0f;
} else if (x < 2.0f) {
return a * x*x*x - 5.0f*a * x*x + 8.0f*a * x - 4.0f*a;
}
return 0.0f;
}
Pixel8U MeshTexture::SampleImageBicubic(const Image8U3& image, const Point2f& point) {
const int w = image.cols;
const int h = image.rows;
const float x = CLAMP(point.x, 0.0f, (float)w - 1.001f);
const float y = CLAMP(point.y, 0.0f, (float)h - 1.001f);
const int ix = (int)std::floor(x);
const int iy = (int)std::floor(y);
const float dx = x - ix;
const float dy = y - iy;
// OpenCV Keys cubic (a=-0.75) - 和双线性同坐标系
auto cubic = [](float t) -> float {
t = std::abs(t);
if (t < 1.0f)
return 1.0f - 2.0f*t*t + t*t*t;
else if (t < 2.0f)
return 4.0f - 8.0f*t + 5.0f*t*t - t*t*t;
return 0.0f;
};
float wx[4], wy[4];
for (int i = 0; i < 4; ++i) {
wx[i] = cubic(dx - (i - 1));
wy[i] = cubic(dy - (i - 1));
}
double sum[3] = {0, 0, 0};
double weight_sum = 0.0;
for (int j = 0; j < 4; ++j) {
const int py = CLAMP(iy + j - 1, 0, h - 1);
for (int i = 0; i < 4; ++i) {
const int px = CLAMP(ix + i - 1, 0, w - 1);
// ✅ 关键:用 operator() 而不是 getPixel()
const Pixel8U& p = image(py, px);
const float w = wx[i] * wy[j];
// ✅ 用 operator[] 访问通道(BGR顺序)
sum[0] += w * p[0]; // B
sum[1] += w * p[1]; // G
sum[2] += w * p[2]; // R
weight_sum += w;
}
}
if (weight_sum > 1e-8) {
sum[0] /= weight_sum;
sum[1] /= weight_sum;
sum[2] /= weight_sum;
}
Pixel8U result;
result[0] = (uint8_t)CLAMP(sum[0], 0.0, 255.0);
result[1] = (uint8_t)CLAMP(sum[1], 0.0, 255.0);
result[2] = (uint8_t)CLAMP(sum[2], 0.0, 255.0);
return result;
}
// 计算视图缩放因子
float MeshTexture::CalculateViewScale(const Camera& cam, const Point3d& pos) {
// ✅ 全部使用 double
Point2d proj = cam.ProjectPointP(pos);
// Point2d proj_dx = cam.ProjectPointP(pos + Point3d(0.01, 0.0, 0.0));
// Point2d proj_dy = cam.ProjectPointP(pos + Point3d(0.0, 0.01, 0.0));
// const double pixelArea = std::abs(
// (proj_dx.x - proj.x) * (proj_dy.y - proj.y) -
// (proj_dx.y - proj.y) * (proj_dy.x - proj.x)
// );
// // ✅ 返回尺度权重
// return static_cast<float>(std::min(1.0, pixelArea / 4.0));
return 0.0f;
constexpr double delta = 0.01;
// ✅ 全部使用 SEACAVE::TPoint3<double>(即 Point3d)
const Point2d p0 = cam.ProjectPointP(pos);
// ✅ 使用 Point3d 构造偏移点(不要用 cv::Point3d)
const Point2d p1 = cam.ProjectPointP(
Point3d(pos.x + delta, pos.y, pos.z)
);
const Point2d p2 = cam.ProjectPointP(
Point3d(pos.x, pos.y + delta, pos.z)
);
// 计算投影后的微小平行四边形面积(像素²)
const double area = std::abs(
(p1.x - p0.x) * (p2.y - p0.y) -
(p1.y - p0.y) * (p2.x - p0.x)
);
return static_cast<float>(area);
}
bool MeshTexture::SelectBestViewForTexel(const Point3f& worldPos,
const Normal& normal,
const Normal& /*normal*/,
const std::vector<IIndex>& candidateViews,
const std::vector<float>& viewWeights,
TexelViewInfo& result) {
const std::vector<float>& /*viewWeights*/,
TexelViewInfo& result)
{
result.best_weight = -1.0f;
for (size_t i = 0; i < candidateViews.size(); ++i) {
const IIndex viewId = candidateViews[i];
const Image& img = images[viewId];
// 1. 投影验证
Point2f proj = ProjectPointWithAutoCorrection(img.camera, worldPos, img);
if (!ValidateProjection(worldPos, img, proj) ||
!img.image.isInside(proj) ||
!img.camera.IsInFront(worldPos)) {
continue;
}
// 2. 计算视角权重(关键:使用真实视角角度)
Point3f camCenter(
static_cast<float>(img.camera.C.x),
static_cast<float>(img.camera.C.y),
static_cast<float>(img.camera.C.z)
);
Point3f viewDir = camCenter - worldPos;
viewDir = NormalizePoint3(viewDir);
float cosAngle = normal.dot(viewDir);
// ✅ 和 V1 完全一致
Point2f proj = ProjectPointWithAutoCorrection(
img.camera,
Vertex(worldPos.x, worldPos.y, worldPos.z),
img
);
// 3. 计算尺度权重(避免远处视图的模糊)
float scale = CalculateViewScale(img.camera, worldPos);
// ✅ 只用最基本、最安全的检查
if (!img.camera.IsInFront(Vertex(worldPos.x, worldPos.y, worldPos.z)))
continue;
// 4. 综合评分
float score = viewWeights[i] * cosAngle * scale;
if (!img.image.isInside(proj))
continue;
if (score > result.best_weight) {
result.best_weight = score;
result.best_view_id = viewId;
result.best_proj = proj;
}
// ✅ 先不选“最佳”,先选“第一个能用的”
result.best_weight = 1.0f;
result.best_view_id = viewId;
result.best_proj = proj;
return true;
}
return result.best_weight > 0.1f;
return false;
}
/*
Mesh::Image8U3Arr MeshTexture::GenerateMultiViewTextureAtlasWithVirtualFaces(
const VirtualFaceMap& virtualFaceMap,
const VirtualFaceDataArr& virtualFaceDatas, // 这个参数现在不被使用,但保留以保持接口兼容
@ -15233,7 +15356,6 @@ Mesh::Image8U3Arr MeshTexture::GenerateMultiViewTextureAtlasWithVirtualFaces( @@ -15233,7 +15356,6 @@ Mesh::Image8U3Arr MeshTexture::GenerateMultiViewTextureAtlasWithVirtualFaces(
for (int x = startX; x <= endX; ++x) {
const Point2f texCoord((float)x / textureSize, (float)y / textureSize);
/*
// 计算重心坐标
Point3f barycentric;
if (PointInTriangle(texCoord, uvCoords[0], uvCoords[1], uvCoords[2], barycentric)) {
@ -15284,39 +15406,6 @@ Mesh::Image8U3Arr MeshTexture::GenerateMultiViewTextureAtlasWithVirtualFaces( @@ -15284,39 +15406,6 @@ Mesh::Image8U3Arr MeshTexture::GenerateMultiViewTextureAtlasWithVirtualFaces(
pointColors.push_back(accumColor);
}
}
//*/
//*
// 获取3D位置和法线
Point3f worldPos;
Normal normal;
FIndex faceID;
if (!GetWorldPositionAndNormal(texCoord, worldPos, normal, faceID)) continue;
// 查找虚拟面
if (idxVF >= faceViews.size()) continue;
// 1. 逐像素选图(关键!)
TexelViewInfo viewInfo;
if (!SelectBestViewForTexel(worldPos, normal,
faceViews[idxVF],
faceViewWeights[idxVF],
viewInfo)) {
continue;
}
// // 2. 自适应纹素密度
// int adaptiveSize = GetAdaptiveTexelSize(worldPos,
// faceViews[idxVF],
// textureSize);
// // 3. 从最佳视图采样(使用双三次插值保持清晰度)
// const Image& bestImg = images[viewInfo.best_view_id];
// Pixel8U color = SampleImageBicubic(bestImg.image, viewInfo.best_proj);
// // 存储结果
// textureAtlas(y, x) = color;
//*/
}
}
@ -15389,7 +15478,6 @@ Mesh::Image8U3Arr MeshTexture::GenerateMultiViewTextureAtlasWithVirtualFaces( @@ -15389,7 +15478,6 @@ Mesh::Image8U3Arr MeshTexture::GenerateMultiViewTextureAtlasWithVirtualFaces(
}
}
//*
// 6. 应用权重归一化
DEBUG_EXTRA("Applying weight normalization for virtual faces");
for (int y = 0; y < textureSize; ++y) {
@ -15412,7 +15500,6 @@ Mesh::Image8U3Arr MeshTexture::GenerateMultiViewTextureAtlasWithVirtualFaces( @@ -15412,7 +15500,6 @@ Mesh::Image8U3Arr MeshTexture::GenerateMultiViewTextureAtlasWithVirtualFaces(
}
}
}
//*/
// 7. 填充缝隙和未采样区域
DEBUG_EXTRA("Filling gaps in texture atlas for virtual faces");
@ -15429,6 +15516,122 @@ Mesh::Image8U3Arr MeshTexture::GenerateMultiViewTextureAtlasWithVirtualFaces( @@ -15429,6 +15516,122 @@ Mesh::Image8U3Arr MeshTexture::GenerateMultiViewTextureAtlasWithVirtualFaces(
DEBUG_EXTRA("Multi-view texture atlas generation with virtual faces complete");
return textures;
}
*/
//*
Mesh::Image8U3Arr MeshTexture::GenerateMultiViewTextureAtlasWithVirtualFaces(
const VirtualFaceMap& virtualFaceMap,
const VirtualFaceDataArr& virtualFaceDatas,
const std::vector<std::vector<IIndex>>& faceViews,
const std::vector<std::vector<float>>& faceViewWeights,
unsigned nTextureSizeMultiple,
Pixel8U colEmpty,
float fSharpnessWeight)
{
DEBUG_EXTRA("Generating multi-view texture atlas with virtual faces");
// 1. UV边界
AABB2f uvBounds(true);
FOREACH(i, scene.mesh.faceTexcoords)
uvBounds.InsertFull(scene.mesh.faceTexcoords[i]);
// 2. 纹理尺寸
float uvWidth = uvBounds.ptMax.x() - uvBounds.ptMin.x();
float uvHeight = uvBounds.ptMax.y() - uvBounds.ptMin.y();
if (uvWidth < 0.001f) uvWidth = 1.0f;
if (uvHeight < 0.001f) uvHeight = 1.0f;
const int textureSize = ComputeOptimalTextureSize(uvWidth, uvHeight, nTextureSizeMultiple);
// 3. 创建纹理图集(只做这一件事)
Mesh::Image8U3Arr textures;
Image8U3& textureAtlas = textures.emplace_back(textureSize, textureSize);
textureAtlas.setTo(cv::Scalar(colEmpty.b, colEmpty.g, colEmpty.r));
// 缓存列数和数据指针(性能关键)
const int cols = textureAtlas.cols;
Pixel8U* data = reinterpret_cast<Pixel8U*>(textureAtlas.data);
DEBUG_EXTRA("Texture atlas size: %dx%d, UV bounds: [%.3f,%.3f]-[%.3f,%.3f]",
textureSize, textureSize,
uvBounds.ptMin.x(), uvBounds.ptMin.y(),
uvBounds.ptMax.x(), uvBounds.ptMax.y());
// 4. 遍历虚拟面(OpenMP安全,因为只写不读)
#ifdef _USE_OPENMP
#pragma omp parallel for schedule(dynamic)
#endif
for (int_t idxVF = 0; idxVF < (int_t)virtualFaceMap.size(); ++idxVF) {
const VirtualFace& vf = virtualFaceMap[idxVF];
if (faceViews[idxVF].empty()) continue;
for (FIndex faceID : vf.faces) {
const Face& face = scene.mesh.faces[faceID];
const TexCoord* uv = &scene.mesh.faceTexcoords[faceID * 3];
const Vertex* vtx = &scene.mesh.vertices[face[0]];
const Normal& faceNormal = scene.mesh.faceNormals[faceID];
// UV边界框
AABB2f uvBox(true);
uvBox.InsertFull(uv[0]);
uvBox.InsertFull(uv[1]);
uvBox.InsertFull(uv[2]);
const int x0 = std::max(0, (int)(uvBox.ptMin.x() * textureSize));
const int y0 = std::max(0, (int)(uvBox.ptMin.y() * textureSize));
const int x1 = std::min(textureSize - 1, (int)(uvBox.ptMax.x() * textureSize));
const int y1 = std::min(textureSize - 1, (int)(uvBox.ptMax.y() * textureSize));
// 遍历纹素
for (int y = y0; y <= y1; ++y) {
for (int x = x0; x <= x1; ++x) {
const Point2f texCoord((float)x / textureSize, (float)y / textureSize);
// 重心坐标测试
Point3f bary;
if (!PointInTriangle(texCoord, uv[0], uv[1], uv[2], bary))
continue;
// 计算世界点(直接插值,O(1))
Point3d worldPos(
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].z * bary.x + vtx[1].z * bary.y + vtx[2].z * bary.z
);
// 逐像素选图
TexelViewInfo viewInfo;
if (!SelectBestViewForTexel(
Point3f(worldPos.x, worldPos.y, worldPos.z),
faceNormal,
faceViews[idxVF],
faceViewWeights[idxVF],
viewInfo))
continue;
// 采样并写入
const Image& bestImg = images[viewInfo.best_view_id];
// 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;
}
}
}
}
DEBUG_EXTRA("Multi-view texture atlas generation with virtual faces complete");
return textures;
}
//*/
bool MeshTexture::TextureWithExistingUV(
const IIndexArr& views,
@ -16722,26 +16925,30 @@ cv::Vec3b MeshTexture::ConvertBGRtoRGBIfNeeded(const cv::Vec3b& bgrColor) { @@ -16722,26 +16925,30 @@ cv::Vec3b MeshTexture::ConvertBGRtoRGBIfNeeded(const cv::Vec3b& bgrColor) {
}
}
Point2f MeshTexture::ProjectPointWithAutoCorrection(const Camera& camera, const Vertex& worldPoint, const Image& sourceImage) {
Point2f MeshTexture::ProjectPointWithAutoCorrection(const Camera& camera,
const Vertex& worldPoint,
const Image& sourceImage) {
// ✅ 只做一件事:严格投影
Point2f imgPoint = camera.ProjectPointP(worldPoint);
// 检查投影点是否在有效范围内
// ✅ 只验证,不修改
if (!sourceImage.image.isInside(imgPoint)) {
// 尝试不同的偏移量来找到最佳投影点
std::vector<Point2f> testOffsets = {
Point2f(0, sourceImage.image.rows * 0.015f), // 当前使用的偏移
Point2f(0, -sourceImage.image.rows * 0.015f), // 反向偏移
Point2f(sourceImage.image.cols * 0.015f, 0), // 水平偏移
Point2f(0, 0) // 无偏移
};
for (const auto& offset : testOffsets) {
Point2f testPoint = imgPoint + offset;
if (sourceImage.image.isInside(testPoint) && camera.IsInFront(worldPoint)) {
return testPoint;
}
}
}
// 记录调试信息,但不修改点
// DEBUG_EXTRA("投影点(%.3f, %.3f)超出图像范围(%d,%d)",
// imgPoint.x, imgPoint.y,
// sourceImage.image.width(),
// sourceImage.image.height());
}
// ✅ 可选:轻微的数值稳定性修正(不是偏移!)
// 例如:处理浮点精度导致的边界外一点的情况
const float epsilon = 0.001f;
if (imgPoint.x < 0 && imgPoint.x > -epsilon) imgPoint.x = 0;
if (imgPoint.y < 0 && imgPoint.y > -epsilon) imgPoint.y = 0;
if (imgPoint.x >= sourceImage.image.width() - epsilon)
imgPoint.x = static_cast<float>(sourceImage.image.width() - 1);
if (imgPoint.y >= sourceImage.image.height() - epsilon)
imgPoint.y = static_cast<float>(sourceImage.image.height() - 1);
return imgPoint;
}
@ -17322,26 +17529,28 @@ Mesh::Image8U3Arr MeshTexture::GenerateTextureAtlasFromUV( @@ -17322,26 +17529,28 @@ Mesh::Image8U3Arr MeshTexture::GenerateTextureAtlasFromUV(
}
bool MeshTexture::ValidateProjection(const Vertex& worldPoint,
const Image& sourceImage, Point2f imgPoint,
float maxReprojectionError) {
// 1. 前向投影:3D点 → 2D图像坐标
Point2f projectedPoint = sourceImage.camera.ProjectPointP(worldPoint);
// 2. 计算重投影误差
float reprojectionError = norm(projectedPoint - imgPoint);
// 3. 设置误差阈值
if (reprojectionError > maxReprojectionError) {
DEBUG_EXTRA("重投影误差过大: %.3f像素,跳过该采样点", reprojectionError);
const Image& sourceImage,
Point2f imgPoint,
float maxReprojectionError) {
// 1. 检查点是否在相机前方(最重要!)
if (!sourceImage.camera.IsInFront(worldPoint)) {
return false;
}
// 4. 视线方向一致性检查
if (!sourceImage.camera.IsInFront(worldPoint)) {
DEBUG_EXTRA("点位于相机后方,跳过");
// 2. 检查投影点是否在图像内(允许微小的数值误差)
const int width = sourceImage.image.width();
const int height = sourceImage.image.height();
const float epsilon = 0.001f;
if (imgPoint.x < -epsilon || imgPoint.x >= width + epsilon ||
imgPoint.y < -epsilon || imgPoint.y >= height + epsilon) {
return false;
}
// 3. ✅ 删除重投影误差检查!
// 原因:在纹理映射中,我们没有“观测点”
// 投影点本身就是理论值,不需要和自己比较
return true;
}
@ -17497,61 +17706,42 @@ void MeshTexture::ProjectFaceToTexture(FIndex faceID, IIndex viewID, @@ -17497,61 +17706,42 @@ void MeshTexture::ProjectFaceToTexture(FIndex faceID, IIndex viewID,
* @param bary (1-u-v, u, v)
* @return bool truefalse
*/
bool MeshTexture::PointInTriangle(const Point2f& p, const Point2f& a, const Point2f& b, const Point2f& c, Point3f& bary)
/// 判断2D点是否位于三角形内部,并计算对应的重心坐标
/// @param p 待检测的2D点(纹理坐标,范围[0,1],Point2f=TPoint2<float>,x/y为公有成员)
/// @param a/b/c 三角形的三个顶点(纹理坐标,TexCoord=TPoint2<float>,x/y为公有成员)
/// @param bary 输出的重心坐标(u,v,w分别对应a,b,c的权重,Point3f=TPoint3<float>,x/y/z为公有成员)
/// @return 点在三角形内(含边界)返回true,否则false
bool MeshTexture::PointInTriangle(const Point2f& p,
const TexCoord& a,
const TexCoord& b,
const TexCoord& c,
Point3f& bary)
{
// 添加调试输出
// DEBUG_EXTRA("PointInTriangle - Input: p(%.6f,%.6f), a(%.6f,%.6f), b(%.6f,%.6f), c(%.6f,%.6f)",
// p.x, p.y, a.x, a.y, b.x, b.y, c.x, c.y);
// 检查输入有效性
if (!std::isfinite(p.x) || !std::isfinite(p.y) ||
!std::isfinite(a.x) || !std::isfinite(a.y) ||
!std::isfinite(b.x) || !std::isfinite(b.y) ||
!std::isfinite(c.x) || !std::isfinite(c.y)) {
// DEBUG_EXTRA("PointInTriangle - Invalid input coordinates");
return false;
}
// 计算边向量
Point2f v0 = b - a;
Point2f v1 = c - a;
Point2f v2 = p - a;
// 计算必要的点积
float dot00 = v0.x * v0.x + v0.y * v0.y;
float dot01 = v0.x * v1.x + v0.y * v1.y;
float dot02 = v0.x * v2.x + v0.y * v2.y;
float dot11 = v1.x * v1.x + v1.y * v1.y;
float dot12 = v1.x * v2.x + v1.y * v2.y;
// 计算分母(三角形平行四边形面积的两倍)
float denom = dot00 * dot11 - dot01 * dot01;
// 处理退化三角形情况(面积接近0)
const float epsilon = 1e-10f;
if (std::abs(denom) < epsilon) {
// DEBUG_EXTRA("PointInTriangle - Degenerate triangle, denom=%.10f", denom);
// return false;
}
// 计算重心坐标参数
float invDenom = 1.0f / denom;
float u = (dot11 * dot02 - dot01 * dot12) * invDenom;
float v = (dot00 * dot12 - dot01 * dot02) * invDenom;
// DEBUG_EXTRA("PointInTriangle - u=%.6f, v=%.6f, u+v=%.6f", u, v, u+v);
// 检查点是否在三角形内(使用更宽松的容差)
if (u >= -epsilon && v >= -epsilon && (u + v) <= 1.0f + epsilon) {
// 点在三角形内,计算完整的重心坐标
bary.x = 1.0f - u - v;
bary.y = u;
bary.z = v;
// DEBUG_EXTRA("PointInTriangle - Point INSIDE triangle, bary(%.3f,%.3f,%.3f)", bary.x, bary.y, bary.z);
// 直接访问顶点坐标公有成员,无函数调用开销,高频调用性能最优
const float ax = a.x, ay = a.y;
const float bx = b.x, by = b.y;
const float cx = c.x, cy = c.y;
const float px = p.x, py = p.y;
// 计算重心坐标行列式(三角形有向面积的2倍,过滤退化三角形)
const float det = (by - cy) * (ax - cx) + (cx - bx) * (ay - cy);
// 阈值1e-10f适配UV坐标精度,避免浮点误差导致的误判
if (std::abs(det) < 1e-10f) return false;
const float invDet = 1.0f / det;
// 计算重心坐标u(a的权重)、v(b的权重)
const float u = ((by - cy) * (px - cx) + (cx - bx) * (py - cy)) * invDet;
const float v = ((cy - ay) * (px - cx) + (ax - cx) * (py - cy)) * invDet;
const float w = 1.0f - u - v;
// 宽松边界判断:允许微小数值误差,确保UV三角形边缘像素不被过滤(解决纹理空白核心逻辑)
constexpr float eps = 1e-6f;
if (u >= -eps && v >= -eps && w >= -eps) {
bary.x = u; // Point3f的x为公有成员,直接赋值
bary.y = v; // Point3f的y为公有成员,直接赋值
bary.z = w; // Point3f的z为公有成员,直接赋值
return true;
}
// DEBUG_EXTRA("PointInTriangle - Point OUTSIDE triangle");
return false;
}

Loading…
Cancel
Save