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

时间:2018-05-30 02:26:51

标签: c++ vtk

我已经通过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的人给那些几乎在那里的人们提供了模糊的暗示,因此对我来说几乎没有帮助。

(附件11/9/2018) 为了澄清,我正在使用由单个3D扫描仪拍摄创建的非退化三角网格,因此我的代码将不会看到四边形和其他更高的多边形。一般的解决方案应该考虑到这些问题,但这可以通过良好的手法应用程序首先对网格进行三角测量来实现。

2 个答案:

答案 0 :(得分:1)

[重写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

答案 1 :(得分:1)

代码

注意,如果一个顶点属于多个多边形并且具有不同的纹理坐标,则VTK将创建该顶点的副本。 我不使用vtkCleanPolyData,因为据我所知,VTK将合并此类“重复项”,并且我们将丢失所需的信息。

我使用vtkCellLocator而不是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(在i中用于{x, y, z}的方程式具有解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 |   --       --
                 --       --

我从AB中减去C顶点的坐标 和A'B'中的纹理坐标C', 现在我们有了第一个顶点的三角形 在坐标系的开始以及相应的纹理坐标。 这意味着现在三角形不相对于另一个进行平移(移动) 而且我们不需要插值矩阵的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中没有用于计算伪逆矩阵的函数。 我们转到Wikipedia上的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'是三角形中第一个顶点的纹理坐标的2D向量, A是顶点坐标的3D向量, M是找到的插值矩阵, p是我们要获取纹理坐标的3D交点, t'是带有插值纹理坐标的2D向量

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