• Stars
    star
    188
  • Rank 205,563 (Top 5 %)
  • Language
    C#
  • License
    MIT License
  • Created about 3 years ago
  • Updated 3 months ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

2d Delaunay triangulation with mesh refinement for Unity with Burst compiler

Burst Triangulator

logo-light-mode logo-dark-mode

Build Tests openupm

A single-file package which provides simple Delaunay triangulation of the given set of points (float2) with mesh refinement.

Implemented classic Delaunay triangulation is based on delaunator and delaunator-sharp. Refinement algorithm is based on Ruppert's algorithm1 with Bowyer–Watson algorithm2 3 point insertion. Refinement procedure is inspired by Shewchuk's terminator algorithm4. The package provides also constrained triangulation (with mesh refinement) which is based on Sloan's algorithm5.

As an illustrative example, we present the triangulation of Lake Superior with various refinement parameters. The top-left image shows the result without any refinement.

lake-preview-light lake-preview-dark

Table of contents

Getting started

Install the package using one of the following methods

Using scoped registry (recommended) Use OpenUPM CLI or add corresponding entries to the project's manifest.json manually. Add or modify scoped registries in the manifest
  "scopedRegistries": [
    {
      "name": "OpenUPM",
      "url": "https://package.openupm.com/",
      "scopes": [
        "com.andywiecko"
      ]
    }
  ]
and in the dependencies provide selected version of the package
"dependencies": {
    "com.andywiecko.burst.triangulator": "2.4.0",
    ...
See Unity docs for more details https://docs.unity3d.com/2021.1/Documentation/Manual/upm-scoped.html
git install Use package manager via git install: https://github.com/andywiecko/BurstTriangulator.git.
Manual instalation Clone or download this repository and then select package.json using Package Manager (Window/Package Manager).
Copy Runtime/Triangulator.cs Since the package is single-file only, one can put the file Runtime/Triangulator.cs somewhere in the project to use it independently.

Example usage

Below one can find example usage of the Triangulator with input set as four points that form the unit square:

using var positions = new NativeArray<float2>(new float2[]
{
  new(0, 0),
  new(1, 0),
  new(1, 1),
  new(0, 1),
}, Allocator.Persistent);

using var triangulator = new Triangulator(capacity: 1024, Allocator.Persistent)
{
  Input = { Positions = positions }
};

triangulator.Run();

var outputTriangles = triangulator.Output.Triangles;
var outputPositions = triangulator.Output.Positions;

The result of the triangulation procedure will depend on selected settings. There are a few settings of the triangulation, shortly described below:

using var triangulator = new Triangulator(1024, Allocator.Persistent)
{
  Settings = 
  {
    RefinementThresholds = {
      // Specifies the maximum area constraint for triangles in the resulting mesh refinement.
      // Ensures that no triangle in the mesh has an area larger than the specified value.
      Area = 1f,
      // Specifies the refinement angle constraint for triangles in the resulting mesh.
      // Ensures that no triangle in the mesh has an angle smaller than the specified value.
      Angle = math.radians(20),
    },
    // Batch count used in parallel job.
    BatchCount = 64,
    // If true refines mesh using Ruppert's algorithm.
    RefineMesh = true,
    // If true the mesh boundary is restored using Input constraint edges.
    RestoreBoundary = false,
    // If true and provided Triangulator.Input is not valid, it will throw an exception.
    // The error can be catch by using the `Triangulator.Output.Status`.
    ValidateInput = true,
    // Type of preprocessing algorithm, see the section below for more details.
    Preprocessor = Triangulator.Preprocessor.None,
  }
};

If the triangulation algorithm fails, checking the status and handling it in the job pipeline can be considered. For example:

[BurstCompile]
private struct Job : IJob
{
  NativeReference<Triangulator.Status>.ReadOnly status;

  public Job(Triangulator triangulator)
  {
    status = triangulator.Output.Status.AsReadOnly();
  }

  public void Execute()
  {
    if(status != Triangulator.Status.OK)
    {
      return;
    }

    ...
  }
}

...

var dependencies = default(JobHandle);
dependencies = triangulator.Schedule(dependencies);
dependencies = new Job(triangulator).Schedule(dependencies);

...

Below, you can find the results of the triangulation for different selected options. The cool guitar was used as an input test case.

guitar-light guitar-dark

Delaunay triangulation

To use classic, i.e. non-constrained without refinement, Delaunay triangulation one can use the following

using var positions = new NativeArray<float2>(..., Allocator.Persistent);
using var triangulator = new Triangulator(Allocator.Persistent)
{
  Input = { Positions = positions }
};

triangulator.Schedule().Complete();

var triangles = triangulator.Output.Triangles;

The result without mesh refinement (Delaunay triangulation):

guitar-light-dt guitar-dark-dt

Delaunay triangulation with mesh refinement

To proceed with triangulation with the mesh refinement one has to set a proper refinement option

using var positions = new NativeArray<float2>(..., Allocator.Persistent);
using var triangulator = new Triangulator(Allocator.Persistent)
{
  Input = { Positions = positions },
  Settings = {
    RefineMesh = true,
    RefinementThresholds = {
      Area = 1f,
      Angle = math.radians(20f)
    },
  }
};

triangulator.Schedule().Complete();

var triangles = triangulator.Output.Triangles;

The result with mesh refinement:

guitar-light-dtr guitar-dark-dtr

The refinement process is controlled by two threshold parameters:

  • Area: denoted as $C_\triangle$
  • Angle: denoted as $C_\theta$

These parameters allow fine-tuning of the refinement results based on specific criteria. Below, you can observe a set of results obtained by applying the refinement process to input data from Lake Superior (open image in a new tab to see the details).

lake-full-light lake-full-dark

Constrained Delaunay triangulation

It is not guaranteed that the boundary of the input will be present in the classic Delaunay triangulation result. One needs to specify the constraints to resolve this issue. To specify the edges which should be present in the final triangulation provide the additional input data

// Provided input of constraint edges
// (a0, a1), (b0, b1), (c0, c1), ...
// should be in the following form
// constraintEdges elements:
// [0]: a0, [1]: a1, [2]: b0, [3]: b1, ...
using var constraintEdges = new NativeArray<int>(64, Allocator.Persistent);
using var positions = new NativeArray<float2>(..., Allocator.Persistent);
using var triangulator = new Triangulator(Allocator.Persistent)
{
  Input = { 
    Positions = positions,
    ConstraintEdges = constraintEdges,
  },
};

triangulator.Schedule().Complete();

var triangles = triangulator.Output.Triangles;

After providing the corresponding input for the constraints, the result of the constrained triangulation fully covers all specified edges by the user

guitar-light-cdt guitar-dark-cdt

Constrained Delaunay triangulation with mesh refinement

Constrained triangulation can be also refined in the same manner as non-constrained one, by enabling corresponding options in triangulation settings:

triangulator.Settings.RefineMesh = true;

After enabling the refinement and the constraint and providing the input, the result of the constrained triangulation fully covers all specified edges by the user and the mesh is refined with the given refinement conditions.

guitar-light-cdtr guitar-dark-cdtr

Support for holes and boundaries

The package provides also an option for restoring the boundaries. One has to enable corresponding options and provide the constraints

settings.RestoreBoundary = true;

The package provides also an option for creating holes. Except for setting the ConstraintEdges, a user needs to provide positions of the holes in the same space as the Input.Positions. Enabling RestoringBoundary option is not mandatory, holes could be introduced independently of preserving the boundaries

settings.RestoreBoundary = true; // optional

using var holes = new NativeArray<float2>(new[]{ math.float2(0.5f, 0.5f) }, Allocator.Persistent);
input.HoleSeeds = holes;

guitar-light-cdtrbh guitar-dark-cdtrbh

Summary

Below one can find the comparison of the results of all possible settings which are available in the package.

summary

Input validation

If Triangulator.Settings.ValidateInput is set to true, the provided data will be validated before running the triangulation procedure. Input positions, as well as input constraints, have a few restrictions:

  • Points count must be greater/equal 3.
  • Points positions cannot be duplicated.
  • Points cannot contain NaNs or infinities.
  • Constraint edges cannot intersect with each other.
  • Constraint edges cannot be duplicated or swapped duplicated.
  • Zero-length constraint edges are forbidden.
  • Constraint edges cannot intersect with points other than the points for which they are defined.

If one of the conditions fails, then triangulation will not be calculated. One could catch this as an error by using triangulator.Output.Status (native, can be used in jobs).

using var triangulator = new Triangulator(Allocator.Persistent)
{
  Input = { ... },
  Settings = {
    ValidateInput = true
  },
};

triangulator.Run();

var status = triangulator.Output.Status.Value;

Generating input in a job

BurstTriangulation input can be generated with job pipeline. One has to use DeferredJobArrays, see the example snippet:

using var positions = new NativeList<float2>(64, Allocator.Persistent);
using var constraints = new NativeList<int>(64, Allocator.Persistent);
using var holes = new NativeList<float2>(64, Allocator.Persistent);
using var triangulator = new Triangulator(64, Allocator.Persistent)
{
  Input = 
  {
    Positions = positions.AsDeferredJobArray(),
    ConstraintEdges = constraints.AsDeferredJobArray(),
    HoleSeeds = holes.AsDeferredJobArray()
  }
}

var dependencies = new JobHandle();
dependencies = new GenerateInputJob(positions, constraints, holes).Schedule(dependencies); // Lists are fed here.
dependencies = triangulator.Schedule(dependencies);
dependencies.Complete();

Reduce the effect of roundoff error

Triangulation for non-uniform data can be demanding, and a few algorithm steps may get stuck if the data is not preprocessed properly. It is highly recommended that the user should prepare the input data on his own, however, this project provides a few built-in methods.

Preprocessor Description
None Default, no effect.
COM Transforms input into normalized local space, i.e. [-1, 1] box.
PCA Transforms input into normalized coordinate systems obtained with principal component analysis.

To use one of the following preprocessors use corresponding settings

triangulator.Settings.Preprocessor = Triangulator.Preprocessor.COM;

PCA transformation

The algorithm usually can help in situations when the Sloan algorithm gets stuck. The transformation can be applied using the following steps:

  1. Calculate com: $\mu = \displaystyle\frac1n\sum_{i=1}^n x_i$.
  2. Transform points: $x_i \to x_i -\mu$.
  3. Calculate covariance matrix: $\text{cov} = \frac1n\sum_i x_i x_i^{\mathsf T}$.
  4. Solve eigenproblem for $\text{cov}$: $\text{cov}u_i =v_i u_i$.
  5. Transform points using matrix $U = [u_i]$: $x_i \to U^{\mathsf T} .x_i$.
  6. Calculate vector center $c = \frac12[\max(x_i) + \min(x_i)]$ and vector scale $s=2/[\max(x_i) - \min(x_i)]$, where $\min$, $\max$, and "$/$" are component wise operators.
  7. Transform points: $x_i \to s (x_i-c)$, assuming component wise multiplication.

To summarize the transformation is given by:

$$ \boxed{x_i \to s[U^{\mathsf T}(x_i - \mu) - c]} $$

and inverse transformation

$$ \boxed{x_i \to U(x_i / s + c) + \mu}. $$

Note

The PCA transformation does not preserve the Settings.MinimumAngle used for refinement. As a result, triangles can be classified as bad in the PCA local space.

Benchmark

The package utilizes the Burst compiler, which generates highly optimized native code using LLVM.

Below, you'll find a performance comparison (with Burst enabled) between v2.0.0 and v2.1.0, as well as a comparison with delaunator-sharp for classic Delaunay triangulation (without refinement or constraints).

Delaunay Benchmark

Below, you can find a benchmark for constrained triangulation for both v2.1 and v2.2. The test specimen consists of a 100×100 grid with additional #constraints-points distributed in a circle at the center of the grid. In some cases of v2.1, the algorithm gets stuck. Reference timings for non-constrained triangulation are marked with a gray line. In the figure below, you can also see example test cases: red represents resulting triangles, and blue represents constrained edges.

Constraint Benchmark

Furthermore, we present a performance comparison (with Burst enabled) between v1.0, v2.0, v2.3, and v2.4 for the refinement task.

Refinement Benchmark

Note
Since v2.4, the triangulation refinement algorithm has been updated, resulting in improved mesh quality.

Dependencies

Known Issues

  • (#103) In the Unity Editor, you may encounter the following log message:
Leak Detected : Persistent allocates 257 individual allocations. To find out more please enable 'Jobs/LeakDetection/Full StackTraces' and reproduce the leak again.

Not to worry, this issue is likely related to an internal bug in the Unity.Collections or Unity.Burst package (related to NativeQueue<> allocation).

Roadmap v3.0

  • Adapt Delaunay triangluation to halfedges approach.
  • Adapt constrained triangulation to halfedges approach.
  • Improve performance of the constraint algorithm.
  • Adapt refinement algorithm to halfedges approach.
  • Remove super-triangle approach.
  • Improve quality of the refinement algorithm.
  • Improve performance of the refinement algorithm.
  • Simplify generics for planting job.
  • Mark ConstrainEdges as obsolete.
  • Adapt constraint and planting jobs for constrainedHalfedges.
  • Clean-up refine job after recent changes.
  • Implement SplitPermitted for terminator.
  • (?) Partially restore Delaunay property after Sloan's algorithm.
  • (?) Add mesh coarsening algorithm.
  • (?) Auto holes detection algorithm.

Bibliography

Footnotes

  1. J. Ruppert. "A Delaunay Refinement Algorithm for Quality 2-Dimensional Mesh Generation". J. Algorithms 18(3):548-585 (1995).

  2. A. Bowyer. "Computing Dirichlet tessellations". Comput. J. 24 (2): 162–166 (1981).

  3. D. F. Watson. "Computing the n-dimensional Delaunay tessellation with application to Voronoi polytopes". Comput. J. 24 (2): 167–172 (1981).

  4. J. R. Shewchuk. "Delaunay refinement algorithms for triangular mesh generation." Comput. Geom. 22.1-3 (2002).

  5. S. W. Sloan. "A fast algorithm for generating constrained Delaunay triangulations." Comput. Struct. 47.3:441-450 (1993).

More Repositories

1

PBD2D

Unity Position Based Dynamics in two dimensions
C#
75
star
2

BurstCollections

Burst friendly (special) native collections for Unity
C#
66
star
3

MultiLangCV

Multi language LaTeX CV template
TeX
25
star
4

BurstMathUtils

Burst compatible miscellaneous math related utility functions.
C#
22
star
5

plotex

script to plot nice gnuplot figures like latex display quality
Python
5
star
6

GnuplotSnake

Classic game Snake implemented using gnuplot
Gnuplot
5
star
7

Flocking

Simple flocking simulation using Unity.Burst
C#
4
star
8

DiceAndChaos

Studying chaos of the rolling dice using game physics
C#
4
star
9

ECS

Custom Entity Component System architecture designed to work with "large" entities.
C#
4
star
10

PBD2D-examples

Example scenes for PBD2D https://github.com/andywiecko/PBD2D
C#
3
star
11

Majoranapp

Codes for Majorana zero modes identification in non-interacting systems
C++
3
star
12

andywiecko.github.io

andywiecko site
HTML
3
star
13

Techniki-Programowania

Introduction to C++ (PL) @ WUST, Wrocław, Poland
C++
3
star
14

RotateVectorToLieOnPlane

C#
2
star
15

hacks

collection of useful hacks
1
star
16

TowerDefence

The first project in Unity Engine based on excellent Brakeys's course: https://youtu.be/beuoNuK2tbk
ASP
1
star
17

TicTacToeDemo

Demo solution for the TicTacToe problem for Programming Techniques classes @ WUST
C++
1
star
18

beers

Personal project for my beers labels collection
HTML
1
star
19

LBM-project-us

CFD classes (University of Silesia)
Jupyter Notebook
1
star
20

Classy-PhD-thesis-template

LaTeX PhD thesis template
TeX
1
star
21

Maple-classes-the-final-project

The final project for Maple classes @ Wrocław University of Science and Techology (Phd studies)
1
star
22

gaussfit

Program to fit multiple gausses
Python
1
star
23

MyLinuxConfig

my linux config repository
Makefile
1
star