由体素制成的扁平3D三角形

时间:2012-06-08 18:00:39

标签: java algorithm geometry voxel

我有一个问题,我似乎无法获得一个有效的算法,我已经尝试了几天,但是到目前为止仍然如此接近。

我想画一个由3个点(p0,p1,p2)定义的三角形。该三角形可以是任何形状,大小和方向。三角形也必须填入内部。

以下是我尝试过的一些事情以及他们失败的原因:

1

  • 沿着三角形从一侧到另一侧绘制线条
    • 失败,因为三角形会有孔而且由于在不同位置改变位置的绘制线条的笨拙而不会是平坦的

2

  • 迭代一个区域并测试该点是否落在平行于三角形的平面上,另外3个平面投影到覆盖三角形区域的XY,ZY和XZ平面上
    • 失败,因为对于某些三角形(具有非常接近的边),会出现不可预测的结果,例如:浮动的体素没有连接任何东西

3

  • 迭代三角形边的区域(线算法)并测试一个点是否经过一个平行平面
    • 失败,因为从p0到p1的一条线与从p1到p0的一条线不同,任何重新排列的尝试都没有帮助,或者导致更多问题。不对称是这个问题。

这都是为了制作多边形和平面。 3给了我最大的成功并且制作了精确的三角形,但是当我尝试将这些连接在一起时,一切都崩溃了,我遇到的问题是没有连接,不对称等等。我相信3会有一些调整,但我只是穿着试图让这项工作长期存在并需要帮助。

我的算法中有很多细节并不是真的相关,所以我把它们排除在外。对于3号,它可能是我的实现的问题,而不是算法本身。如果你想要代码我会尝试清理它以使其易于理解,但我需要几分钟时间。但我正在寻找已知可行的算法。我似乎无法在任何地方找到任何体素形状制作算法,我从头开始做各种事情。

编辑:

这是第三次尝试。这是一团糟,但我试图清理它。

// Point3i is a class I made, however the Vector3fs you'll see are from lwjgl

public void drawTriangle (Point3i r0, Point3i r1, Point3i r2)
{
    // Util is a class I made with some useful stuff inside

    // Starting values for iteration

    int sx = (int) Util.min(r0.x, r1.x, r2.x);
    int sy = (int) Util.min(r0.y, r1.y, r2.y);
    int sz = (int) Util.min(r0.z, r1.z, r2.z);

    // Ending values for iteration

    int ex = (int) Util.max(r0.x, r1.x, r2.x);
    int ey = (int) Util.max(r0.y, r1.y, r2.y);
    int ez = (int) Util.max(r0.z, r1.z, r2.z);

    // Side lengths

    float l0 = Util.distance(r0.x, r1.x, r0.y, r1.y, r0.z, r1.z);
    float l1 = Util.distance(r2.x, r1.x, r2.y, r1.y, r2.z, r1.z);
    float l2 = Util.distance(r0.x, r2.x, r0.y, r2.y, r0.z, r2.z);

    // Calculate the normal vector

    Vector3f nn = new Vector3f(r1.x - r0.x, r1.y - r0.y, r1.z - r0.z);
    Vector3f n   = new Vector3f(r2.x - r0.x, r2.y - r0.y, r2.z - r0.z);
    Vector3f.cross(nn, n, n);

    // Determines which direction we increment for

    int iz = n.z >= 0 ? 1 : -1;
    int iy = n.y >= 0 ? 1 : -1;
    int ix = n.x >= 0 ? 1 : -1;

    // Reorganize for the direction of iteration

    if (iz < 0) {
        int tmp = sz;
        sz = ez;
        ez = tmp;
    }
    if (iy < 0) {
        int tmp = sy;
        sy = ey;
        ey = tmp;
    }
    if (ix < 0) {
        int tmp = sx;
        sx = ex;
        ex = tmp;
    }

    // We're we want to iterate over the end vars so we change the value
    // by their incrementors/decrementors

    ex += ix;
    ey += iy;
    ez += iz;

    // Maximum length

    float lmax = Util.max(l0, l1, l2);

    // This is a class I made which manually iterates over a line, I already
    // know that this class is working

    GeneratorLine3d g0, g1, g2;

    // This is a vector for the longest side

    Vector3f v = new Vector3f();

    // make the generators

    if (lmax == l0) {
        v.x = r1.x - r0.x;
        v.y = r1.y - r0.y;
        v.z = r1.z - r0.z;

        g0 = new GeneratorLine3d(r0, r1);
        g1 = new GeneratorLine3d(r0, r2);
        g2 = new GeneratorLine3d(r2, r1);
    }
    else if (lmax == l1) {
        v.x = r1.x - r2.x;
        v.y = r1.y - r2.y;
        v.z = r1.z - r2.z;

        g0 = new GeneratorLine3d(r2, r1);
        g1 = new GeneratorLine3d(r2, r0);
        g2 = new GeneratorLine3d(r0, r1);
    }
    else {
        v.x = r2.x - r0.x;
        v.y = r2.y - r0.y;
        v.z = r2.z - r0.z;

        g0 = new GeneratorLine3d(r0, r2);
        g1 = new GeneratorLine3d(r0, r1);
        g2 = new GeneratorLine3d(r1, r2);
    }

    // Absolute values for the normal

    float anx = Math.abs(n.x);
    float any = Math.abs(n.y);
    float anz = Math.abs(n.z);

    int i, o;
    int si, so;
    int ii, io;
    int ei, eo;

    boolean maxx, maxy, maxz,
    midy, midz, midx,
    minx, miny, minz;

    maxx = maxy = maxz =
    midy = midz = midx =
    minx = miny = minz = false;

    // Absolute values for the longest side vector      

    float rnx = Math.abs(v.x);
    float rny = Math.abs(v.y);
    float rnz = Math.abs(v.z);

    int rmid = Util.max(rnx, rny, rnz);

    if (rmid == rnz) midz = true;
    else if (rmid == rny) midy = true;

    midx = !midz && !midy;

    // Determine the inner and outer loop directions

    if (midz) {
        if (any > anx) 
        {
            maxy = true;
            si = sy;
            ii = iy;
            ei = ey;
        }
        else {
            maxx = true;
            si = sx;
            ii = ix;
            ei = ex;
        }
    }
    else {
        if (anz > anx) {
            maxz = true;
            si = sz;
            ii = iz;
            ei = ez;
        }
        else {
            maxx = true;
            si = sx;
            ii = ix;
            ei = ex;
        }
    }

    if (!midz && !maxz) {
        minz = true;
        so = sz;
        eo = ez;
    }
    else if (!midy && !maxy) {
        miny = true;
        so = sy;
        eo = ey;
    }
    else {
        minx = true;
        so = sx;
        eo = ex;
    }

    // GeneratorLine3d is iterable
    Point3i p1;
    for (Point3i p0 : g0) {
        // Make sure the two 'mid' coordinate correspond for the area inside the triangle

        if (midz)
            do p1 = g1.hasNext() ? g1.next() : g2.next();
            while (p1.z != p0.z);
        else if (midy)
            do p1 = g1.hasNext() ? g1.next() : g2.next();
            while (p1.y != p0.y);
        else
            do p1 = g1.hasNext() ? g1.next() : g2.next();
            while (p1.x != p0.x);

        eo = (minx ? p0.x : miny ? p0.y : p0.z);
        so = (minx ? p1.x : miny ? p1.y : p1.z);
        io = eo - so >= 0 ? 1 : -1;

        for (o = so; o != eo; o += io) {
            for (i = si; i != ei; i += ii) {
                int x = maxx ? i : midx ? p0.x : o;
                int y = maxy ? i : midy ? p0.y : o;
                int z = maxz ? i : midz ? p0.z : o;

                // isPassing tests to see if a point goes past a plane
                // I know it's working, so no code

                // voxels is a member that is an arraylist of Point3i

                if (isPassing(x, y, z, r0, n.x, n.y, n.z)) {
                    voxels.add(new Point3i(x, y, z));
                    break;
                }
            }
        }
    }
}

2 个答案:

答案 0 :(得分:1)

您可以使用Besenham's line algorithm之类的内容,但可以扩展为三维。我们想要从中得出的两个主要想法是:

  • 旋转初始线,使其坡度不会太陡。
  • 对于任何给定的x值,找到最接近理想y值的整数值。

正如Bresenham的算法通过执行初始旋转来防止间隙,我们将通过执行两次初始旋转来避免间隙。

  1. 获取表示三角形所在平面的法线向量和点。提示:使用矢量的(从p0到p1的线)和(从p0到p2的线)的叉积,并使用任何角点作为点。
  2. 你希望飞机足够不陡,以避免漏洞。您必须满足以下条件:

    • -1 >= norm.x / norm.y >= 1
    • -1 >= norm.z / norm.y >= 1

    将法线矢量和初始点绕x轴旋转90度,绕z轴旋转90度,直到满足这些条件。我不知道如何以最少的转数做到这一点,但我相当确定你能满足任何飞机的这些条件。

  3. 创建一个函数f(x,z),它表示旋转三角形现在所在的平面。它应该返回任何一对X和Z值的Y值。
  4. 将三角形投影到XZ平面上(即将所有y值设置为0),并使用您最喜欢的2d三角形绘制算法来获取x和z坐标的集合。
  5. 对于步骤4中的每个像素值,将x和z值传递到步骤3中的函数f(x,z)。将结果舍入到最接近的整数,并将x,y和z值存储为体素某处。
  6. 如果您在步骤2中执行了任何旋转,请在体素集合中按相反顺序执行相反的旋转。

答案 1 :(得分:0)

从检查三角形/体素交叉点的函数开始。现在你可以扫描一个体积并找到与三角形相交的体素 - 这些是你感兴趣的体素。这是一个糟糕的算法,但也是你尝试的其他任何东西的回归测试。使用SAT(分离轴定理)并考虑三角形的简并体积(1面,3条边)并考虑体素对称性(仅3面法线),可以很容易地实现该测试。

我使用octtrees,所以我首选的方法是测试一个大体素的三角形并找出它相交的8个子八分圆中的哪一个。然后对相交的子项使用递归,直到达到所需的细分级别。提示:最多6个孩子可以与三角形相交,并且通常少于三角形。这很棘手,但会产生与第一种方法相同的结果,但速度要快得多。

3D中的光栅化可能是最快的,但恕我直言,更难保证在所有情况下都没有漏洞。再次,使用第一种方法进行比较。