Home » c# » c# – Triangle.NET – How to add vertex to existing triangulation?

c# – Triangle.NET – How to add vertex to existing triangulation?

Posted by: admin February 21, 2020 Leave a comment

Questions:

I’ve looked through what seems like every question and resource there is for Triangle.NET trying to find an answer to how to insert a vertex into an existing triangulation. The closest I’ve gotten was in the discussion archives for Traingle.Net where someone asked a similar question (discussion id 632458) but unfortunately, the answer was not what I was looking for.

My goal here is to make a destructible wall in Unity where, when the player shoots the wall, it will create a hole in the wall (like in Rainbow Six Siege).

Here’s what I did for my original implementation:

  1. Create initial triangulation using the four corners of the wall.
  2. When the player shoots, perform a raycast, if the raycast intersects with the wall then add the point of intersection to the polygon variable and re-triangulate the entire mesh using that variable.
  3. Draw new triangulation on the wall as a texture to visualise what’s happening.
  4. Repeat.

As you can see, step 2 is the problem.

Because I re-triangulate the entire mesh every time the player hits the wall, the more times the player hits the wall the slower the triangulation gets as the number of vertices rises. This could be fine I guess, but I want destructible walls to play a major role in my game so this is not acceptable.

So, digging through the Triangle.Net source code, I find an internal method called InsertVertex. The summary for this method states:

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

This would mean I wouldn’t have to re-triangulate every time the player shoots!

So I get to implementing this method, and…it doesn’t work. I get an error like the one below:

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)

I have been stuck on this problem for days and I cannot solve it for the life of me! If anyone who is knowledgeable enough with the Triangle.NET library would be willing to help me I would be so grateful! Along with that, if there is a better alternative to either the implementation or library I’m using (for my purpose which I outlined above) that would also be awesome!

Currently, how I’ve set up the scene is really simple, I just have a quad which I scaled up and added the script below to it as a component. I then linked that component to a shoot raycast script attached to the Main Camera:

How the scene is setup.

What it looks like in Play Mode.

The exact Triangle.Net repo I cloned is this one.

My code is posted below:

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;
    }
}
How to&Answers: