LibTressDotNet Example

Hi guys,

I have started using Clipper library in my project. And I would like to use LibTress lib for tessellation.
But I can’t figure out how it works?
Do you have an example ? Say I have a list of 5 Vector 3. How to take them and make a mesh GamePbject using LibTress?
I should be able to hook up Clipper paths later after execute function.

Thank you

For mesh to be constructed in Unity I need at list two arrays of points and tri indexes. Just wonder how to get to this point using LibTress

Maybe you guys have an idea @_geo1 @MelvMay ?

The lib only generates tesselation data (tris) for you from one or more polygons. You will be in charge of actually constructing a mesh from it.

Docs: GitHub - speps/LibTessDotNet: C# port of the famous GLU Tessellator - prebuilt binaries now available in "releases" tab

Here is the method I used in some of my code:

/// <summary>
/// Generate Mesh using LibTess.
/// </summary>
/// <param name="pointLists"></param>
/// <param name="textureScale"></param>
public void GenerateGeometryWithLibTess(List<List<Vector3>> pointLists, float textureScale)
{
    // Libtess stuff
    var triangulatedShape = new TriangulatedShape2D(pointLists);

    // Your own mesh generation code (this one generates a mesh and applies it to a MeshFilter component)
    int triCount = triangulatedShape.TriangleCount;
    int[] tris = triangulatedShape.Triangles;
    ContourVertex[] vertices = triangulatedShape.Vertices;
    Vec3 p0, p1, p2;

    Vector3[] meshVertices = new Vector3[triCount * 3];
    Vector2[] meshUVs = new Vector2[triCount * 3];
    int[] meshTriangles = new int[triCount * 3];

    // uv start point
    Vector3 uvPoint03D, uvPoint13D, uvPoint23D;

    for (int i = 0; i < triCount; i++)
    {
        p0 = vertices[tris[i * 3]].Position;
        p1 = vertices[tris[i * 3 + 1]].Position;
        p2 = vertices[tris[i * 3 + 2]].Position;

        meshVertices[i * 3] = new Vector3(p0.X, p0.Y, p0.Z);
        meshVertices[i * 3 + 1] = new Vector3(p1.X, p1.Y, p1.Z);
        meshVertices[i * 3 + 2] = new Vector3(p2.X, p2.Y, p2.Z);

        uvPoint03D = this.transform.TransformPoint(new Vector3(p0.X, p0.Y, 0));
        uvPoint13D = this.transform.TransformPoint(new Vector3(p1.X, p1.Y, 0));
        uvPoint23D = this.transform.TransformPoint(new Vector3(p2.X, p2.Y, 0));
        meshUVs[i * 3] = new Vector2(uvPoint03D.x / textureScale, uvPoint03D.y / textureScale);
        meshUVs[i * 3 + 1] = new Vector2(uvPoint13D.x / textureScale, uvPoint13D.y / textureScale);
        meshUVs[i * 3 + 2] = new Vector2(uvPoint23D.x / textureScale, uvPoint23D.y / textureScale);

        meshTriangles[i * 3] = i * 3;
        meshTriangles[i * 3 + 1] = i * 3 + 1;
        meshTriangles[i * 3 + 2] = i * 3 + 2;
    }

    Mesh mesh;
    if (MeshFilter.sharedMesh == null)
    {
        mesh = new Mesh();
    }
    else
    {
        mesh = MeshFilter.sharedMesh;
        mesh.Clear();
    }
    mesh.vertices = meshVertices;
    mesh.uv = meshUVs;
    mesh.triangles = meshTriangles;
    mesh.RecalculateNormals();
    //mesh.RecalculateBounds();
    MeshFilter.sharedMesh = mesh;
}
using UnityEngine;
using System.Collections.Generic;

namespace Kamgam.SBR2.LibTessDotNet
{
    public class TriangulatedShape2D
    {
        protected Tess _tess;

        public int[] Triangles
        {
            get { return _tess.Elements; }
            set { }
        }

        public int TriangleCount
        {
            get { return _tess.ElementCount; }
            set { }
        }

        public ContourVertex[] Vertices
        {
            get { return _tess.Vertices; }
            set { }
        }

        public int VertexCount
        {
            get { return _tess.VertexCount; }
            set { }
        }

        public TriangulatedShape2D(List<Vector2> pointList)
        {
            tesselate(new List<List<Vector2>>() { pointList });
        }

        public TriangulatedShape2D(List<List<Vector2>> pointLists)
        {
            tesselate(pointLists);
        }

        public TriangulatedShape2D(List<Vector3> pointList)
        {
            tesselate(new List<List<Vector3>>() { pointList });
        }

        public TriangulatedShape2D(List<List<Vector3>> pointLists)
        {
            tesselate(pointLists);
        }

        // First list is the contour. Second, third, ... are holes iirc.
        protected void tesselate(List<List<Vector3>> pointLists)
        {
            _tess = new LibTessDotNet.Tess();
            for (int p = 0; p < pointLists.Count; p++)
            {
                var contour = new LibTessDotNet.ContourVertex[pointLists[p].Count];
                for (int i = 0; i < pointLists[p].Count; i++)
                {
                    contour[i].Position = new LibTessDotNet.Vec3(pointLists[p][i].x, pointLists[p][i].y, pointLists[p][i].z);
                }
                _tess.AddContour(contour, LibTessDotNet.ContourOrientation.Original);
            }
            _tess.Tessellate(LibTessDotNet.WindingRule.EvenOdd, LibTessDotNet.ElementType.Polygons, 3);
        }

        protected void tesselate(List<List<Vector2>> pointLists)
        {
            _tess = new LibTessDotNet.Tess();
            for (int p = 0; p < pointLists.Count; p++)
            {
                var contour = new LibTessDotNet.ContourVertex[pointLists[p].Count];
                for (int i = 0; i < pointLists[p].Count; i++)
                {
                    contour[i].Position = new LibTessDotNet.Vec3(pointLists[p][i].x, pointLists[p][i].y, 0);
                }
                _tess.AddContour(contour, LibTessDotNet.ContourOrientation.Original);
            }
            _tess.Tessellate(LibTessDotNet.WindingRule.EvenOdd, LibTessDotNet.ElementType.Polygons, 3);
        }
    }
}

The code is just an excerpt, don’t expect it to work out of the box. It should help you to get started.

You will have to write your own mesh generation code based on your needs anyhow. I think I only used tri-planar shaders on those mehses, so I am not sure if the generated UVs are any good.

3 Likes

Thank you @_geo1 this is a good start for me.
Ok I see… Now it is clear. Thank you

int triCount = triangulatedShape.TriangleCount;
    int[] tris = triangulatedShape.Triangles;

Hi @_geo1 I think I got the hang of it.
What I can’t understand is why the order of indexes in triangles is wrong. It creates a mesh with normals pointing down for me?

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using LibTessDotNet;

[RequireComponent(typeof(MeshFilter))]
[RequireComponent(typeof(MeshRenderer))]

public class PlanarMeshGenerator : MonoBehaviour
{
    [SerializeField] private SiteDataSO data;
    private Mesh mesh;
    private Vector3[] vertices;
    private int[] triangles;

    // Start is called before the first frame update
    void Start()
    {
        mesh = GetComponent<MeshFilter>().mesh;
        MakeMeshData();
        CreateMesh();  
    }

    private void MakeMeshData()
    {
        // 1. Create an instance of tessellator
        Tess tess = new LibTessDotNet.Tess();

        // 2. Construct the contour from data
        ContourVertex[] contour = new LibTessDotNet.ContourVertex[data.Points.Count];
        for (int i = 0; i < data.Points.Count; i++)
        {
            contour[i].Position = new LibTessDotNet.Vec3(data.Points[i].x, data.Points[i].y, data.Points[i].z);
        }

        // 3. Add contour
        tess.AddContour(contour, LibTessDotNet.ContourOrientation.Original);

        // 4. Tessellate
        tess.Tessellate(WindingRule.EvenOdd, ElementType.Polygons, 3);

        // 5. Add mesh data
        vertices = new Vector3[tess.ElementCount * 3];
        triangles = new int[tess.ElementCount * 3];

        for (int i = 0; i < tess.ElementCount; i++)
        {
            var v0 = tess.Vertices[tess.Elements[i * 3]].Position;
            var v1 = tess.Vertices[tess.Elements[i * 3 + 1]].Position;
            var v2 = tess.Vertices[tess.Elements[i * 3 + 2]].Position;

            vertices[i * 3] = new Vector3(v0.X, v0.Y, v0.Z);
            vertices[i * 3 + 1] = new Vector3(v1.X, v1.Y, v1.Z);
            vertices[i * 3 + 2] = new Vector3(v2.X, v2.Y, v2.Z);

            triangles[i * 3] = i * 3;
            triangles[i * 3 + 1] = i * 3 + 1;
            triangles[i * 3 + 2] = i * 3 + 2;
        }
    }

    private void CreateMesh()
    {
        mesh.Clear();
        mesh.vertices = vertices;
        mesh.triangles = triangles;
    }

Also to update the tessellation at game play should I just run Tessellate() again and update my mesh?

So if I am using a simple ordered list of points it works
7957068--1019607--Screenshot 2022-03-11 at 14.51.02.png

But if I feed a different list, it creates a mesh upside down (with the wrong winding)

7957068--1019610--Screenshot 2022-03-11 at 14.51.44.png

Ok Fixed it with a normal parameter…

tess.Tessellate(LibTessDotNet.WindingRule.EvenOdd, LibTessDotNet.ElementType.Polygons,3, null, new Vec3(0,1,0));

Hi @_geo1
Apologies, I have another quick question for you. I am working with Clipper LIb now, And as far as I can tell it works only with ints.
I created float to int conversion functions, but I’m not sure they are correct, I have to use up to 1000 scale to get results closer to reality.

Have you got one of your examples? Thank you

// Convert input Vecto3 List to intPoints. Including float precision
    // http://www.angusj.com/delphi/clipper/documentation/Docs/Overview/FAQ.htm
    private void ConvertToIntPoints()
    {
        foreach (var point in PathPoints)
        {
            float scaledX = point.x * scale; // 10000
            //Debug.Log("Original float: " + point.x);
            //Debug.Log("Converted int: " + scaledX);
            float scaledY = point.z * scale;
            IntPoints.Add(new IntPoint(scaledX, scaledY));
        }
    }

    // Convert paths intPoints back to Vecto3s. Including float precision
    private void ConvertToFloat()
    {
        foreach (var path in offsetPaths)
        {
            for (int i = 0; i < path.Count; i++)
            {
                float scaledX = path[i].X / scale; // 10000
                float scaledZ = path[i].Y / scale;
                //Debug.Log("Converted int back to float: " + scaledX);
                OffsetPoints.Add(new Vector3(scaledX, 0.0f, scaledZ));
            }   
        }
    }

Can’t help you much except asking whether “scale” is an int or a float. If it’s an int then use a float instead to avoid int division.

1 Like

Just in case anyone is looking at this in the future as I was, this can be simplified down quite a bit.
tess.Elements already contains the order of the vertices to create triangles. All that is required is to convert the array of Vec3 to Vector3 and use tess.Elements as the triangles indices.

private Mesh CreateTriangulatedMesh()
    {
        tess.Tessellate(WindingRule.EvenOdd, ElementType.Polygons, 3);
        Vector3[] verts = new Vector3[tess.VertexCount];
        for (int i = 0; i < tess.VertexCount; i++)
        {
            Vec3 v = tess.Vertices[i].Position;
            verts[i] = new Vector3(v.X, v.Y, v.Z);
        }

        Mesh mesh = new Mesh();
        mesh.vertices = verts;
        mesh.triangles = tess.Elements;
        mesh.RecalculateBounds();
        mesh.RecalculateNormals();

        mesh.name = GENERATED_MESH_NAME;
        return mesh;
    }
3 Likes

Cool Thank you @LapidistCubed
btw any advice regarding Clipper Lib conversion from int to float and back ?

I didn’t use Clipper at all in my project, so I’m afraid not! Best of luck!

1 Like