Triangle.NET - 如何将顶点添加到现有三角剖分?

Triangle.NET - How to add vertex to existing triangulation?

我已经查看了似乎每个问题和资源,Triangle.NET 试图找到如何将顶点插入现有三角剖分的答案。我得到的最接近的是 Traingle.Net 的讨论档案,有人问了类似的问题(讨论 ID 632458),但不幸的是,答案不是我要找的.

我的目标是在 Unity 中制作一堵可破坏的墙,当玩家射击墙时,它会在墙上创建一个洞(就像在彩虹六号围攻中一样)。

这是我为最初的实现所做的:

  1. 使用墙的四个角创建初始三角测量。
  2. 当玩家射击时,执行光线投射,如果光线投射与墙壁相交,则将交点添加到多边形变量并使用该变量重新三角化整个网格。
  3. 在墙上绘制新的三角剖分作为纹理以可视化正在发生的事情。
  4. 重复。

如您所见,第 2 步是问题所在。

因为每次玩家撞墙时我都会重新对整个网格进行三角剖分,所以玩家撞墙的次数越多,随着顶点数量的增加,三角剖分的速度就越慢。我想这可能没问题,但我希望可破坏的墙壁在我的游戏中扮演重要角色,所以这是不可接受的。

因此,通过挖掘 Triangle.Net 源代码,我找到了一个名为 InsertVertex 的内部方法。此方法的摘要指出:

Insert a vertex into a Delaunay triangulation, performing flips as necessary to maintain the Delaunay property.

这意味着我不必在玩家每次射击时都重新进行三角测量!

所以我开始实施这个方法,但是...它不起作用。我收到如下错误:

NullReferenceException: Object reference not set to an instance of an object TriangleNet.TriangleLocator.PreciseLocate (TriangleNet.Geometry.Point searchpoint, TriangleNet.Topology.Otri& searchtri, System.Boolean stopatsubsegment) (at Assets/Triangle.NET/TriangleLocator.cs:146)

我已经被这个问题卡住了,我一辈子都解决不了!如果任何对 Triangle.NET 库有足够了解的人愿意帮助我,我将不胜感激!除此之外,如果我正在使用的实现或库有更好的替代方案(出于我上面概述的目的),那也很棒!

目前,我设置场景的方式非常简单,我只有一个四边形,我将其放大并将下面的脚本作为组件添加到其中。然后我将该组件链接到附加到主相机的拍摄光线投射脚本:

How the scene is setup.

What it looks like in Play Mode.

我克隆的确切 Triangle.Net 回购是 this one

我的代码贴在下面:

using UnityEngine;
using TriangleNet.Geometry;
using TriangleNet.Topology;
using TriangleNet.Meshing;

public class Delaunay : MonoBehaviour
{
    [SerializeField]
    private int randomPoints = 150;
    [SerializeField]
    private int width = 512;
    [SerializeField]
    private int height = 512;

    private TriangleNet.Mesh mesh;
    Polygon polygon = new Polygon();

    Otri otri = default(Otri);
    Osub osub = default(Osub);

    ConstraintOptions constraintOptions = new ConstraintOptions() { ConformingDelaunay = true };
    QualityOptions qualityOptions = new QualityOptions() { MinimumAngle = 25 };

    void Start()
    {
        osub.seg = null;

        Mesh objMesh = GetComponent<MeshFilter>().mesh;

        // Add four corners of wall (quad in this case) to polygon.
        //foreach (Vector3 vert in objMesh.vertices)
        //{
        //    Vector2 temp = new Vector2();
        //    temp.x = map(vert.x, -0.5f, 0.5f, 0, 512);
        //    temp.y = map(vert.y, -0.5f, 0.5f, 0, 512);
        //    polygon.Add(new Vertex(temp.x, temp.y));
        //}

        // Generate random points and add to polygon.
        for (int i = 0; i < randomPoints; i++)
        {
            polygon.Add(new Vertex(Random.Range(0.0f, width), Random.Range(0.0f, height)));
        }

        // Triangulate polygon.
        delaunayTriangulation();
    }

    // When left click is pressed, a raycast is sent out. If that raycast hits the wall, updatePoints() is called and is passed in the location of the hit (hit.point).
    public void updatePoints(Vector3 pos)
    {
        // Convert pos to local coords of wall.
        pos = transform.InverseTransformPoint(pos);
        Vertex newVert = new Vertex(pos.x, pos.y);

        //// Give new vertex a unique id.
        //if (mesh != null)
        //{
        //    newVert.id = mesh.NumberOfInputPoints;
        //}

        // Insert new vertex into existing triangulation.
        otri.tri = mesh.dummytri;
        mesh.InsertVertex(newVert, ref otri, ref osub, false, false);

        // Draw result as a texture onto the wall so to visualise what is happening.
        draw();
    }

    private void delaunayTriangulation()
    {
        mesh = (TriangleNet.Mesh)polygon.Triangulate(constraintOptions, qualityOptions);
        draw();
    }

    void draw()
    {
        Texture2D tx = new Texture2D(width, height);

        // Draw triangulation.
        if (mesh.Edges != null)
        {
            foreach (Edge edge in mesh.Edges)
            {
                Vertex v0 = mesh.vertices[edge.P0];
                Vertex v1 = mesh.vertices[edge.P1];

                DrawLine(new Vector2((float)v0.x, (float)v0.y), new Vector2((float)v1.x, (float)v1.y), tx, Color.black);
            }
        }

        tx.Apply();
        this.GetComponent<Renderer>().sharedMaterial.mainTexture = tx;
    }

    // Bresenham line algorithm
    private void DrawLine(Vector2 p0, Vector2 p1, Texture2D tx, Color c, int offset = 0)
    {
        int x0 = (int)p0.x;
        int y0 = (int)p0.y;
        int x1 = (int)p1.x;
        int y1 = (int)p1.y;

        int dx = Mathf.Abs(x1 - x0);
        int dy = Mathf.Abs(y1 - y0);
        int sx = x0 < x1 ? 1 : -1;
        int sy = y0 < y1 ? 1 : -1;
        int err = dx - dy;

        while (true)
        {
            tx.SetPixel(x0 + offset, y0 + offset, c);

            if (x0 == x1 && y0 == y1) break;
            int e2 = 2 * err;
            if (e2 > -dy)
            {
                err -= dy;
                x0 += sx;
            }
            if (e2 < dx)
            {
                err += dx;
                y0 += sy;
            }
        }
    }

    private float map(float from, float fromMin, float fromMax, float toMin, float toMax)
    {
        float fromAbs = from - fromMin;
        float fromMaxAbs = fromMax - fromMin;

        float normal = fromAbs / fromMaxAbs;

        float toMaxAbs = toMax - toMin;
        float toAbs = toMaxAbs * normal;

        float to = toAbs + toMin;

        return to;
    }
}

好消息!我已经设法解决了这个问题。 InsertVertex() 实际上并没有将新顶点添加到顶点列表中!所以这意味着当它尝试三角测量时,它试图指向新的顶点但它不能(因为该顶点不在列表中)。因此,为了解决这个问题,我只是手动将我的新顶点添加到网格中的顶点列表中, 调用InsertVertex() 之前。注意:执行此操作时,还需要手动设置顶点的 id。我将 id 设置为顶点列表的大小,因为我将所有新顶点添加到列表的末尾。

// When left click is pressed, a raycast is sent out. If that raycast hits the wall, updatePoints() is called and is passed in the location of the hit (hit.point).
public void updatePoints(Vector3 pos)
{
    // Convert pos to local coords of wall. You don't need to do this, i do it because of my draw() method where i map everything out onto a texture and display it.
    pos = transform.InverseTransformPoint(pos);
    pos.x = map(pos.x, -0.5f, 0.5f, 0, 512);
    pos.y = map(pos.y, -0.5f, 0.5f, 0, 512);
    Vertex newVert = new Vertex(pos.x, pos.y);

    // Manually add new vertex to list of vertices.
    newVert.id = mesh.vertices.Count;
    mesh.vertices.Add(newVert.id, newVert);

    //Doing just the first line gave me a null pointer exception. Adding the two extra lines below it fixed it for me.
    otri.tri = mesh.dummytri;
    otri.orient = 0;
    otri.Sym();

    // Insert new vertex into existing triangulation.
    mesh.InsertVertex(newVert, ref otri, ref osub, false, false);

    // Draw result as a texture onto the wall so to visualise what is happening.
    draw();
}

希望这对完成道路的人有所帮助!