如何从 VTK IntersectWithLine 获取纹理坐标?

How to get texture coordinates from VTK IntersectWithLine?

我已经通过 vtkOBJReader 加载了纹理映射的 OBJ 并将其加载到 vtkModifiedBSPTree:

auto readerOther(vtkSmartPointer<vtkOBJReader>::New());
auto rawOtherPath(modelPathOther.toLatin1());
readerOther->SetFileName(rawOtherPath.data());
readerOther->Update();

auto meshDataOther(readerOther->GetOutput());

auto bspTreeOther(vtkSmartPointer<vtkModifiedBSPTree>::New());
bspTreeOther->SetDataSet(meshDataOther);
bspTreeOther->BuildLocator();

然后我计算我的线段开始和结束并将其输入

if (bspTreeOther->IntersectWithLine(p1, p2, tolerance, distanceAlongLine, intersectionCoords, pcoords, subId, cellId, cell))

当然还有所有相关的预定义变量。

我需要的是纹理交点处的UV坐标。

我对 VTK 还很陌生,所以我还没有理解它是如何组合在一起的逻辑;当我挖掘源代码时,抽象层仍然让我迷失。

我在 SO 和 VTK 用户档案中寻找这个答案,发现那些对 VTK 有深刻理解的人给那些自己几乎在那里的人提供了模糊的提示,因此到目前为止对我帮助不大。

(2018 年 11 月 9 日追加) 澄清一下,我正在使用由单个 3D 扫描仪拍摄创建的非退化三角网格,因此我的代码永远不会看到四边形和其他更高的多边形。一个通用的解决方案应该考虑到这些事情,但这可以通过首先通过良好应用 handwavium 对网格进行三角测量来实现。

[重写 2019/5/7 以反映更新的理解。]

在发现参数坐标是一个函数的输入,在三角形单元的情况下,可以从该函数获得重心坐标,然后了解什么是重心坐标,我能够计算出以下内容。

    const auto readerOther(vtkSmartPointer<vtkOBJReader>::New());
    const auto rawOtherPath(modelPathOther.toLatin1());
    readerOther->SetFileName(rawOtherPath.data());
    readerOther->Update();

    const auto meshDataOther(readerOther->GetOutput());

    const auto bspTreeOther(vtkSmartPointer<vtkModifiedBSPTree>::New());
    bspTreeOther->SetDataSet(meshDataOther);
    bspTreeOther->BuildLocator();

    double point1[3]{0.0, 0.0, 0.0}; // start of line segment used to intersect the model.
    double point2[3]{0.0, 0.0, 10.0}; // end of line segment
    double distanceAlongLine;
    double intersectionCoords[3]; // The coordinate of the intersection.
    double parametricCoords[3]; // Parametric Coordinates of the intersection - see https://lorensen.github.io/VTKExamples/site/VTKBook/08Chapter8/#82-interpolation-functions
    int subId; // ?
    vtkIdType cellId;

    double intersectedTextureCoords[2];

    if (bspTreeOther->IntersectWithLine(point1, point2, TOLERANCE, distanceAlongLine, intersectionCoords, parametricCoords, subId, cellId))
    {
        const auto textureCoordsOther(meshDataOther->GetPointData()->GetTCoords());

        const auto pointIds{meshDataOther->GetCell(cellId)->GetPointIds()};

        const auto vertexIndex0{pointIds->GetId(0)};
        const auto vertexIndex1{pointIds->GetId(1)};
        const auto vertexIndex2{pointIds->GetId(2)};

        double texCoord0[2];
        double texCoord1[2];
        double texCoord2[2];
        textureCoordsOther->GetTuple(vertexIndex0, texCoord0);
        textureCoordsOther->GetTuple(vertexIndex1, texCoord1);
        textureCoordsOther->GetTuple(vertexIndex2, texCoord2);

        const auto parametricR{parametricCoords[0]};
        const auto parametricS{parametricCoords[1]};

        const auto barycentricW0{1 - parametricR - parametricS};
        const auto barycentricW1{parametricR};
        const auto barycentricW2{parametricS};

        intersectedTextureCoords[0] =
                barycentricW0 * texCoord0[0] +
                barycentricW1 * texCoord1[0] +
                barycentricW2 * texCoord2[0];
        intersectedTextureCoords[1] =
                barycentricW0 * texCoord0[1] +
                barycentricW1 * texCoord1[1] +
                barycentricW2 * texCoord2[1];
    }

请注意,此代码是对我正在使用的实际代码的解释;我正在使用 Qt 及其 QVector2D 和 QVector3D 类 以及一些解释器粘合函数来往返于双精度数组。

有关各种细胞类型的参数坐标系的详细信息,请参见https://lorensen.github.io/VTKExamples/site/VTKBook/08Chapter8

代码

注意如果一个顶点属于多个多边形并且具有不同的纹理坐标,VTK 将创建该顶点的副本。 我不使用 vtkCleanPolyData,因为 VTK 会合并这样的 "duplicates",据我所知,我们将丢失所需的信息。

我用vtkCellLocator instead of vtkModifiedBSPTree, 因为在我的情况下它更快。

主文件main.cpp。 您可以在 startend 数组中找到幻数 — 这些是您的 p1p2。 我设置这些值只是为了举例

#include <vtkSmartPointer.h>
#include <vtkPointData.h>
#include <vtkCellLocator.h>
#include <vtkGenericCell.h>
#include <vtkOBJReader.h>
#include <vtkTriangleFilter.h>
#include <vtkMath.h>

#include <iostream>

int main(int argc, char * argv[])
{
    if (argc < 2)
    {
        std::cerr << "Usage: " << argv[0] << " OBJ_file_name" << std::endl;
        return EXIT_FAILURE;
    }
    auto reader{vtkSmartPointer<vtkOBJReader>::New()};
    reader->SetFileName(argv[1]);
    reader->Update();
    // Triangulate the mesh if needed
    auto triangleFilter{vtkSmartPointer<vtkTriangleFilter>::New()};
    triangleFilter->SetInputConnection(reader->GetOutputPort());
    triangleFilter->Update();
    auto mesh{triangleFilter->GetOutput()};
    // Use `auto mesh(reader->GetOutput());` instead if no triangulation needed

    // Build a locator to find intersections
    auto locator{vtkSmartPointer<vtkCellLocator>::New()};
    locator->SetDataSet(mesh);
    locator->BuildLocator();

    // Initialize variables needed for intersection calculation
    double start[3]{-1, 0, 0.5};
    double end[3]{   1, 0, 0.5};
    double tolerance{1E-6};
    double relativeDistanceAlongLine;
    double intersectionCoordinates[3];
    double parametricCoordinates[3];
    int subId;
    vtkIdType cellId;
    auto cell{vtkSmartPointer<vtkGenericCell>::New()};
    // Find intersection
    int intersected = locator->IntersectWithLine(
        start,
        end,
        tolerance,
        relativeDistanceAlongLine,
        intersectionCoordinates,
        parametricCoordinates,
        subId,
        cellId,
        cell.Get()
    );

    // Get points of intersection cell
    auto pointsIds{vtkSmartPointer<vtkIdList>::New()};
    mesh->GetCellPoints(cellId, pointsIds);
    // Store coordinates and texture coordinates of vertices of the cell
    double meshTrianglePoints[3][3];
    double textureTrianglePoints[3][2];
    auto textureCoordinates{mesh->GetPointData()->GetTCoords()};
    for (unsigned pointNumber = 0; pointNumber < cell->GetNumberOfPoints(); ++pointNumber)
    {
        mesh->GetPoint(pointsIds->GetId(pointNumber), meshTrianglePoints[pointNumber]);
        textureCoordinates->GetTuple(pointsIds->GetId(pointNumber), textureTrianglePoints[pointNumber]);
    }

    // Normalize the coordinates
    double movedMeshTrianglePoints[3][3];
    for (unsigned i = 0; i < 3; ++i)
    {
        movedMeshTrianglePoints[0][i] = 0;
        movedMeshTrianglePoints[1][i] =
            meshTrianglePoints[1][i] -
            meshTrianglePoints[0][i];
        movedMeshTrianglePoints[2][i] =
            meshTrianglePoints[2][i] -
            meshTrianglePoints[0][i];
    }
    // Normalize the texture coordinates
    double movedTextureTrianglePoints[3][2];
    for (unsigned i = 0; i < 2; ++i)
    {
        movedTextureTrianglePoints[0][i] = 0;
        movedTextureTrianglePoints[1][i] =
            textureTrianglePoints[1][i] -
            textureTrianglePoints[0][i];
        movedTextureTrianglePoints[2][i] =
            textureTrianglePoints[2][i] -
            textureTrianglePoints[0][i];
    }

    // Calculate SVD of a matrix consisting of normalized vertices
    double U[3][3];
    double w[3];
    double VT[3][3];
    vtkMath::SingularValueDecomposition3x3(movedMeshTrianglePoints, U, w, VT);
    // Calculate pseudo inverse of a matrix consisting of normalized vertices
    double pseudoInverse[3][3]{0};
    for (unsigned i = 0; i < 3; ++i)
    {
        for (unsigned j = 0; j < 3; ++j)
        {
            for (unsigned k = 0; k < 3; ++k)
            {
                if (w[k] != 0)
                {
                    pseudoInverse[i][j] += VT[k][i] * U[j][k] / w[k];
                }
            }
        }
    }

    // Calculate interpolation matrix
    double interpolationMatrix[3][2]{0};
    for (unsigned i = 0; i < 3; ++i)
    {
        for (unsigned j = 0; j < 2; ++j)
        {
            for (unsigned k = 0; k < 3; ++k)
            {
                interpolationMatrix[i][j] += pseudoInverse[i][k] * movedTextureTrianglePoints[k][j];
            }
        }
    }

    // Calculate interpolated texture coordinates of the intersection point
    double interpolatedTexturePoint[2]{textureTrianglePoints[0][0], textureTrianglePoints[0][1]};
    for (unsigned i = 0; i < 2; ++i)
    {
        for (unsigned j = 0; j < 3; ++j)
        {
            interpolatedTexturePoint[i] += (intersectionCoordinates[j] - meshTrianglePoints[0][j]) * interpolationMatrix[j][i];
        }
    }

    // Print the result
    std::cout << "Interpolated texture coordinates";
    for (unsigned i = 0; i < 2; ++i)
    {
        std::cout << " " << interpolatedTexturePoint[i];
    }
    std::cout << std::endl;

    return EXIT_SUCCESS;
}

CMake 项目文件CMakeLists.txt

cmake_minimum_required(VERSION 3.1)

PROJECT(IntersectInterpolate)

set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

find_package(VTK REQUIRED)
include(${VTK_USE_FILE})

add_executable(IntersectInterpolate MACOSX_BUNDLE main.cpp)

if(VTK_LIBRARIES)
    target_link_libraries(IntersectInterpolate ${VTK_LIBRARIES})
else()
    target_link_libraries(IntersectInterpolate vtkHybrid vtkWidgets)
endif()

数学

我们需要什么

假设您有一个由三角形组成的网格,并且您的顶点具有纹理坐标。

给定三角形的顶点ABC,对应的纹理坐标A'B'C',你想要找到从三角形的另一个内部点和边界点到纹理的映射(以插值)。 让我们做一些理性的假设:

  • ABC应对应其纹理坐标A'B'C'
  • 边界上的每个点X,比如说AB,应该对应于A'B'线上的点,如下所示:|AX| / |AB| = |A'X'| / |A'B'|——原来的一半三角形应该在纹理贴图的中间;
    • 三角形的质心 (A + B + C) / 3 应该对应于纹理三角形的质心 (A' + B' + C') / 3.

要求解的方程

看起来我们想要仿射映射:原始三角形的顶点坐标应该乘以一些系数并添加到一些常数。 让我们构建方程组

Ax * Mxx + Ay * Myx + Az * Mzx + M0x = A'x
Ax * Mxy + Ay * Myy + Az * Mzy + M0y = A'y
Ax * Mxz + Ay * Myz + Az * Mzz + M0z = 0

BC 也一样。 您可以看到我们有 9 个方程和 12 个未知数。 虽然,包含 Miz 的方程(对于 {x, y, z} 中的 i)有解 0 并且在进一步的计算中没有任何作用,所以我们可以将它们设置为等于0。 因此,我们的系统具有 6 个方程和 8 个未知数

Ax * Mxx + Ay * Myx + Az * Mzx + M0x = A'x
Ax * Mxy + Ay * Myy + Az * Mzy + M0y = A'y

让我们在矩阵视图中编写整个系统

--          --   --       --   --       --
| 1 Ax Ay Az |   | M0x M0y |   | A'x A'y |
| 1 Bx By Bz | x | Mxx Mxy | = | B'x B'y |
| 1 Cx Cy Cz |   | Myx Myy |   | C'x C'y |
--          --   | Mzx Mzy |   --       --
                 --       --

我从 BC 中减去 A 顶点的坐标 和来自 B'C' 的纹理坐标 A', 现在我们有了第一个顶点位于的三角形 在坐标系的开始以及相应的纹理坐标中。 这意味着现在三角形不会相对于另一个平移(移动) 我们不需要 M0 插值矩阵的一部分

--        --   --       --   --       --
| Bx By Bz |   | Mxx Mxy |   | B'x B'y |
| Cx Cy Cz | x | Myx Myy | = | C'x C'y |
--        --   | Mzx Mzy |   --       --
               --       --

解决方案

我们称第一个矩阵为P,第二个为M,最后一个为T

P M = T

矩阵P不是方阵。 如果我们向其中添加零行,矩阵将变为奇异矩阵。 因此,我们必须计算它的伪逆才能求解方程。 VTK中没有计算伪逆矩阵的函数。 我们去维基百科上的 Moore–Penrose inverse 文章,看到它可以使用 SVD 计算。 VTKMath::SingularValueDecomposition3x3 函数允许我们这样做。 该函数为我们提供了 USVT 矩阵。 我将矩阵 P 的伪逆写为 P", 将 U 换位为 UT,将 VT 换位为 V。 对角矩阵的伪逆 S 是具有 1 / Sii 个元素的矩阵 其中 Sii 不是零,0 表示零元素

P = U S VT
P" = V S" UT
M = P" T

用法

要应用插值矩阵, 我们需要不要忘记我们需要翻译输入和输出向量。 A' 是三角形中第一个顶点的纹理坐标的二维向量, A是顶点坐标的3D向量, M是找到的插值矩阵, p 是我们想要获取纹理坐标的 3D 交点, t' 是生成的带有内插纹理坐标的 2D 矢量

t' = A' + (p - A) M