From 5229b4f284fa44336ff1dd3037f300904ff0173a Mon Sep 17 00:00:00 2001 From: hesuicong Date: Tue, 14 Jul 2026 17:26:35 +0800 Subject: [PATCH] =?UTF-8?q?=E5=8D=95=E5=83=8F=E7=B4=A0=E6=B8=85=E6=99=B0?= =?UTF-8?q?=E5=8C=96=E4=BF=9D=E5=AD=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- libs/MVS/SceneTexture.cpp | 526 ++++++++++++++++++++++++++------------ 1 file changed, 358 insertions(+), 168 deletions(-) diff --git a/libs/MVS/SceneTexture.cpp b/libs/MVS/SceneTexture.cpp index 80ec372..9a96685 100644 --- a/libs/MVS/SceneTexture.cpp +++ b/libs/MVS/SceneTexture.cpp @@ -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& visibleViews); + int GetAdaptiveTexelSize(const Point3f& pos, + const std::vector& visibleViews, + int baseTexelSize); + Pixel8U SampleImageBicubic(const Image8U3& img, const Point2f& pt); Mesh::Image8U3Arr GenerateMultiViewTextureAtlasWithVirtualFaces( const VirtualFaceMap& virtualFaceMap, @@ -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(scale); +} + +float MeshTexture::CalculateLocalViewDensity(const Point3f& pos, + const std::vector& 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& 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(std::min(1.0, pixelArea / 4.0)); - return 0.0f; + constexpr double delta = 0.01; + + // ✅ 全部使用 SEACAVE::TPoint3(即 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(area); } -bool MeshTexture::SelectBestViewForTexel(const Point3f& worldPos, - const Normal& normal, +bool MeshTexture::SelectBestViewForTexel(const Point3f& worldPos, + const Normal& /*normal*/, const std::vector& candidateViews, - const std::vector& viewWeights, - TexelViewInfo& result) { + const std::vector& /*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)) { + + // ✅ 和 V1 完全一致 + Point2f proj = ProjectPointWithAutoCorrection( + img.camera, + Vertex(worldPos.x, worldPos.y, worldPos.z), + img + ); + + // ✅ 只用最基本、最安全的检查 + if (!img.camera.IsInFront(Vertex(worldPos.x, worldPos.y, worldPos.z))) continue; - } - - // 2. 计算视角权重(关键:使用真实视角角度) - Point3f camCenter( - static_cast(img.camera.C.x), - static_cast(img.camera.C.y), - static_cast(img.camera.C.z) - ); - Point3f viewDir = camCenter - worldPos; + if (!img.image.isInside(proj)) + continue; - viewDir = NormalizePoint3(viewDir); - float cosAngle = normal.dot(viewDir); - - // 3. 计算尺度权重(避免远处视图的模糊) - float scale = CalculateViewScale(img.camera, worldPos); - - // 4. 综合评分 - float score = viewWeights[i] * cosAngle * scale; - - 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( 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( 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( } } - //* // 6. 应用权重归一化 DEBUG_EXTRA("Applying weight normalization for virtual faces"); for (int y = 0; y < textureSize; ++y) { @@ -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( 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>& faceViews, + const std::vector>& 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(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) { } } -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 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(sourceImage.image.width() - 1); + if (imgPoint.y >= sourceImage.image.height() - epsilon) + imgPoint.y = static_cast(sourceImage.image.height() - 1); return imgPoint; } @@ -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, * @param bary 输出的重心坐标 (1-u-v, u, v) * @return bool 如果点在三角形内返回true,否则返回false */ -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,x/y为公有成员) +/// @param a/b/c 三角形的三个顶点(纹理坐标,TexCoord=TPoint2,x/y为公有成员) +/// @param bary 输出的重心坐标(u,v,w分别对应a,b,c的权重,Point3f=TPoint3,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; }