找到闭合的不规则三角形3D表面与笛卡尔矩形3D网格的交点

时间:2015-09-29 21:13:43

标签: c matlab geometry intersection triangulation

我在网上寻找一种有效的方法,可以将笛卡尔矩形三维网格与一个三角形的紧密不规则三维表面相交。

此曲面表示一组顶点V和一组面F。笛卡尔矩形网格存储为:

x_0, x_1, ..., x_(ni-1)
y_0, y_1, ..., y_(nj-1)
z_0, z_1, ..., z_(nk-1)

在下图中,显示了笛卡尔网格的单个单元格。另外,示意性地示出了表面的两个三角形。该交叉点用虚线红线表示,实心红色圆圈表示与该特定单元格的交叉点。我的目标是找到表面与单元边缘的交点,这些点可以是非平面的。

我将在MATLAB,C或C ++中实现。

enter image description here

3 个答案:

答案 0 :(得分:2)

假设我们有一个规则的轴对齐矩形网格,每个网格单元匹配单位立方体(因此网格点( i j k) )位于( i j k ), i j k 整数),我建议尝试2D三角光栅化的3D变体。

基本思想是绘制三角形周长,然后绘制三角形与每个平面垂直于轴的每个交点,并将该轴与整数坐标相交。

您最终会在网格单元格面上显示线段,只要三角形通过网格单元格。每个网格单元的面上的线形成闭合的平面多边形。 (但是,您需要自己连接线段并定向多边形。)

为了仅找出三角形经过的网格单元,可以使用简化的方法和位图(每个网格单元一位)。这种情况基本上只是三角形光栅化的3D版本。

关键观察是,如果你有一条线( X 0 Y 0 ž的<子> 0 ) - ( X 的<子> 1 ý的<子> 1 Z 1 ),您可以沿着 x将它分割成整数坐标 x i 的段轴使用

  

t i =( x i - X 0 )/( X 1 - X 0

     

y i =(1 - t i Y 0 + t i Y 1

     

z i =(1 - t i Z 0 + t i Z 1

当然,与其他轴类似。

你需要做三次传球,每次传球一次。如果对顶点进行排序以使坐标沿该轴不减小,即 p 0 p 1 p 2 ,一个端点位于相交线 p 0 p <的整数坐标处sub> 2 ,另一个终点在小坐标处与行 p 0 p 1 相交,并在大坐标处划线 p 1 p 2

这些端点之间的交叉线垂直于一个轴,但仍需要将其拆分为不沿其他两个维度跨越整数坐标的段。幸运的是,这很简单;只需保持 t j t k 沿这两个维度,就像 t < sub> i ,然后步入下一个具有较小 t 值的整数坐标;从0开始,到1结束。

原始三角形的边缘也需要绘制,只需沿所有三个维度分割。同样,这很简单,通过保持每个轴的 t ,并沿着具有最小值的轴踩踏。我在C99中有示例代码,这是最复杂的情​​况,如下所示。

需要考虑很多实施细节。

因为每个单元格与另一个单元格共享每个面,并且每个边缘具有另外三个边缘,所以让我们为每个单元格定义以下属性( i j k ),其中 i j k 是识别单元格的整数:

  • X face x = i 的单元格面,垂直于 x
  • Y face y = j 的单元格面,垂直于 y
  • Z face z = k 的单元格面,垂直于 z
  • X edge :从( i j k )到( i)的边缘 1,Ĵķ
  • Y edge :从( i j k )到( i)的边缘Ĵ 1,ķ
  • Z edge :从( i j k )到( i)的边缘Ĵķ 1)

单元格的其他三个面( i j k

  • X face at( i +1, j k
  • Y face at( i j +1, k
  • Z face at( i j k +1)

类似地,每个边缘是其他三个细胞的边缘。单元格的 X edge i j k )也是网格单元格的边缘( j +1, k ),( i j k +1),和( i j +1, k +1)。单元格的 Y edge i j k )也是网格单元格的边缘( +1, j k ),( i j k +1),和( i +1, j k +1)。单元格的 Z edge i j k )也是网格单元格的边缘( +1, j k ),( i j +1,< em> k )和( i +1, j +1, k )。

这是一张可能有用的图片。 Illustration of a regular rectangular axis-aligned unit grid cell

(忽略它是左撇子的事实;我只是认为这样更容易标记。)

这意味着如果特定网格单元 face 上有线段,则在共享 face 的两个网格单元之间共享线段。同样,如果线段端点位于网格单元 edge 上,则有四个不同的网格单元,其他线段端点可以打开。

为了澄清这一点,下面的示例代码不仅打印了坐标,还打印了网格单元格以及线段端点所在的面/边/顶点。

#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>
#include <math.h>

typedef struct {
    double x;
    double y;
    double z;
} vector;

typedef struct {
    long   x;
    long   y;
    long   z;
} gridpos;

typedef enum {
    INSIDE = 0,    /* Point is inside the grid cell */
    X_FACE = 1,    /* Point is at integer X coordinate (on the YZ face) */
    Y_FACE = 2,    /* Point is at integer Y coordinate (on the XZ face) */
    Z_EDGE = 3,    /* Point is at integet X and Y coordinates (on the Z edge) */
    Z_FACE = 4,    /* Point is at integer Z coordinate (on the XY face) */
    Y_EDGE = 5,    /* Point is at integer X and Z coordinates (on the Y edge) */
    X_EDGE = 6,    /* Point is at integer Y and Z coordinates (on the X edge) */
    VERTEX = 7,    /* Point is at integer coordinates (at the grid point) */
} cellpos;

static inline cellpos cellpos_of(const vector v)
{
    return (v.x == floor(v.x))
         + (v.y == floor(v.y)) * 2
         + (v.z == floor(v.z)) * 4;
}

static const char *const face_name[8] = {
    "inside",
    "x-face",
    "y-face",
    "z-edge",
    "z-face",
    "y-edge",
    "x-edge",
    "vertex",
};

static int line_segments(const vector p0, const vector p1,
                         int (*segment)(void *custom,
                                        const gridpos src_cell, const cellpos src_face, const vector src_vec,
                                        const gridpos dst_cell, const cellpos dst_face, const vector dst_vec),
                         void *const custom)
{
    const vector  range = { p1.x - p0.x, p1.y - p0.y, p1.z - p0.z };
    const gridpos step = { (range.x < 0.0) ? -1L : (range.x > 0.0) ? +1L : 0L,
                           (range.y < 0.0) ? -1L : (range.y > 0.0) ? +1L : 0L,
                           (range.z < 0.0) ? -1L : (range.z > 0.0) ? +1L : 0L };
    const gridpos end = { floor(p1.x), floor(p1.y), floor(p1.z) };
    gridpos       prev_cell, curr_cell = { floor(p0.x), floor(p0.y), floor(p0.z) };
    vector        prev_vec,  curr_vec = p0;
    vector        curr_at = { 0.0, 0.0, 0.0 };
    vector        next_at = { (range.x != 0.0 && curr_cell.x != end.x) ? ((double)(curr_cell.x + step.x) - p0.x) / range.x : 1.0,
                              (range.y != 0.0 && curr_cell.y != end.y) ? ((double)(curr_cell.y + step.y) - p0.y) / range.y : 1.0,
                              (range.z != 0.0 && curr_cell.z != end.z) ? ((double)(curr_cell.z + step.z) - p0.z) / range.z : 1.0};
    cellpos       prev_face, curr_face;
    double        at;
    int           retval;

    curr_face = cellpos_of(p0);

    while (curr_at.x < 1.0 || curr_at.y < 1.0 || curr_at.z < 1.0) {
        prev_cell = curr_cell;
        prev_face = curr_face;
        prev_vec  = curr_vec;

        if (next_at.x < 1.0 && next_at.x <= next_at.y && next_at.x <= next_at.z) {
            /* YZ plane */
            at = next_at.x;
            curr_vec.x = round( (1.0 - at) * p0.x + at * p1.x );
            curr_vec.y =        (1.0 - at) * p0.y + at * p1.y;
            curr_vec.z =        (1.0 - at) * p0.z + at * p1.z;
        } else
        if (next_at.y < 1.0 && next_at.y < next_at.x && next_at.y <= next_at.z) {
            /* XZ plane */
            at = next_at.y;
            curr_vec.x =        (1.0 - at) * p0.x + at * p1.x;
            curr_vec.y = round( (1.0 - at) * p0.y + at * p1.y );
            curr_vec.z =        (1.0 - at) * p0.z + at * p1.z;
        } else
        if (next_at.z < 1.0 && next_at.z < next_at.x && next_at.z < next_at.y) {
            /* XY plane */
            at = next_at.z;
            curr_vec.x =        (1.0 - at) * p0.x + at * p1.x;
            curr_vec.y =        (1.0 - at) * p0.y + at * p1.y;
            curr_vec.z = round( (1.0 - at) * p0.z + at * p1.z );
        } else {
            at = 1.0;
            curr_vec = p1;
        }

        curr_face = cellpos_of(curr_vec);

        curr_cell.x = floor(curr_vec.x);
        curr_cell.y = floor(curr_vec.y);
        curr_cell.z = floor(curr_vec.z);

        retval = segment(custom,
                         prev_cell, prev_face, prev_vec,
                         curr_cell, curr_face, curr_vec);
        if (retval)
            return retval;

        if (at < 1.0) {
            curr_at = next_at;
            if (at >= next_at.x) {
                /* recalc next_at.x */
                if (curr_cell.x != end.x) {
                    next_at.x = ((double)(curr_cell.x + step.x) - p0.x) / range.x;
                    if (next_at.x > 1.0)
                        next_at.x = 1.0;
                } else
                    next_at.x = 1.0;
            }
            if (at >= next_at.y) {
                /* reclac next_at.y */
                if (curr_cell.y != end.y) {
                    next_at.y = ((double)(curr_cell.y + step.y) - p0.y) / range.y;
                    if (next_at.y > 1.0)
                        next_at.y = 1.0;
                } else
                    next_at.y = 1.0;
            }
            if (at >= next_at.z) {
                /* recalc next_at.z */
                if (curr_cell.z != end.z) {
                    next_at.z = ((double)(curr_cell.z + step.z) - p0.z) / range.z;
                    if (next_at.z > 1.0)
                        next_at.z = 1.0;
                } else
                    next_at.z = 1.0;
            }
        } else {
            curr_at.x = curr_at.y = curr_at.z = 1.0;
            next_at.x = next_at.y = next_at.z = 1.0;
        }
    }

    return 0;
}

int print_segment(void *outstream,
                  const gridpos src_cell, const cellpos src_face, const vector src_vec,
                  const gridpos dst_cell, const cellpos dst_face, const vector dst_vec)
{
    FILE *const out = outstream ? outstream : stdout;

    fprintf(out, "%.6f %.6f %.6f   %.6f %.6f %.6f   %s %ld %ld %ld   %s %ld %ld %ld\n",
                 src_vec.x, src_vec.y, src_vec.z,
                 dst_vec.x, dst_vec.y, dst_vec.z,
                 face_name[src_face], src_cell.x, src_cell.y, src_cell.z,
                 face_name[dst_face], dst_cell.x, dst_cell.y, dst_cell.z);

    fflush(out);
    return 0;
}

static int parse_vector(const char *s, vector *const v)
{
    double x, y, z;
    char   c;

    if (!s)
        return EINVAL;

    if (sscanf(s, " %lf %*[,/:;] %lf %*[,/:;] %lf %c", &x, &y, &z, &c) == 3) {
        if (v) {
            v->x = x;
            v->y = y;
            v->z = z;
        }
        return 0;
    }

    return ENOENT;
}


int main(int argc, char *argv[])
{
    vector start, end;

    if (argc != 3 || !strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")) {
        fprintf(stderr, "\n");
        fprintf(stderr, "Usage: %s [ -h | --help ]\n", argv[0]);
        fprintf(stderr, "       %s x0:y0:z0 x1:y1:z1\n", argv[0]);
        fprintf(stderr, "\n");
        return EXIT_FAILURE;
    }

    if (parse_vector(argv[1], &start)) {
        fprintf(stderr, "%s: Invalid start point.\n", argv[1]);
        return EXIT_FAILURE;
    }
    if (parse_vector(argv[2], &end)) {
        fprintf(stderr, "%s: Invalid end point.\n", argv[2]);
        return EXIT_FAILURE;
    }

    if (line_segments(start, end, print_segment, stdout))
        return EXIT_FAILURE;

    return EXIT_SUCCESS;
}

该程序采用两个命令行参数,即要分割的行的3D端点。如果你编译上面说example,然后运行

./example 0.5/0.25/3.50 3.5/4.0/0.50

输出

0.500000 0.250000 3.500000   1.000000 0.875000 3.000000   inside 0 0 3   x-face 1 0 3
1.000000 0.875000 3.000000   1.100000 1.000000 2.900000   x-face 1 0 3   y-face 1 1 2
1.100000 1.000000 2.900000   1.900000 2.000000 2.100000   y-face 1 1 2   y-face 1 2 2
1.900000 2.000000 2.100000   2.000000 2.125000 2.000000   y-face 1 2 2   y-edge 2 2 2
2.000000 2.125000 2.000000   2.700000 3.000000 1.300000   y-edge 2 2 2   y-face 2 3 1
2.700000 3.000000 1.300000   3.000000 3.375000 1.000000   y-face 2 3 1   y-edge 3 3 1
3.000000 3.375000 1.000000   3.500000 4.000000 0.500000   y-edge 3 3 1   y-face 3 4 0

表明线(0.5,0.25,3.50) - (3.5,4.0,0.50)被分成七段;这条特殊的线路正好穿过七个网格单元。

对于光栅化情况 - 当您只对表面三角形通过的网格单元感兴趣时,您不需要存储线段点,只计算它们全部。当点位于顶点或网格单元格内时,标记与该网格单元格对应的位。当一个点在一个面上时,设置共享该面的两个网格单元的位。当线段端点位于边缘时,设置共享该边缘的四个网格单元的位。

有问题吗?

答案 1 :(得分:1)

将问题分解为更小的步骤。

Finding the intersection of a line segment with a triangle is easy.

实现后,只需执行一个嵌套循环,检查网格中每个线条组合与曲面三角形之间的交集。

答案 2 :(得分:0)

在每个曲面三角形边上使用线平面交点,如here所述。您将使用六个平面,每个网格面一个。