Initial commit

This commit is contained in:
AbstractConcept 2022-09-13 00:36:34 -05:00
commit 3c7cc0c973
8391 changed files with 704313 additions and 0 deletions

View file

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 4637d45a8a4db4499b2c164b519ca3c3
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,697 @@
// -----------------------------------------------------------------------
// <copyright file="Dwyer.cs">
// Original Triangle code by Jonathan Richard Shewchuk, http://www.cs.cmu.edu/~quake/triangle.html
// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/
// </copyright>
// -----------------------------------------------------------------------
namespace UnityEngine.U2D.Animation.TriangleNet
.Meshing.Algorithm
{
using System;
using System.Collections.Generic;
using Animation.TriangleNet.Geometry;
using Animation.TriangleNet.Tools;
using Animation.TriangleNet.Topology;
/// <summary>
/// Builds a delaunay triangulation using the divide-and-conquer algorithm.
/// </summary>
/// <remarks>
/// The divide-and-conquer bounding box
///
/// I originally implemented the divide-and-conquer and incremental Delaunay
/// triangulations using the edge-based data structure presented by Guibas
/// and Stolfi. Switching to a triangle-based data structure doubled the
/// speed. However, I had to think of a few extra tricks to maintain the
/// elegance of the original algorithms.
///
/// The "bounding box" used by my variant of the divide-and-conquer
/// algorithm uses one triangle for each edge of the convex hull of the
/// triangulation. These bounding triangles all share a common apical
/// vertex, which is represented by NULL and which represents nothing.
/// The bounding triangles are linked in a circular fan about this NULL
/// vertex, and the edges on the convex hull of the triangulation appear
/// opposite the NULL vertex. You might find it easiest to imagine that
/// the NULL vertex is a point in 3D space behind the center of the
/// triangulation, and that the bounding triangles form a sort of cone.
///
/// This bounding box makes it easy to represent degenerate cases. For
/// instance, the triangulation of two vertices is a single edge. This edge
/// is represented by two bounding box triangles, one on each "side" of the
/// edge. These triangles are also linked together in a fan about the NULL
/// vertex.
///
/// The bounding box also makes it easy to traverse the convex hull, as the
/// divide-and-conquer algorithm needs to do.
/// </remarks>
internal class Dwyer : ITriangulator
{
// Random is not threadsafe, so don't make this static.
// Random rand = new Random(DateTime.Now.Millisecond);
IPredicates predicates;
public bool UseDwyer = true;
Vertex[] sortarray;
Mesh mesh;
/// <summary>
/// Form a Delaunay triangulation by the divide-and-conquer method.
/// </summary>
/// <returns></returns>
/// <remarks>
/// Sorts the vertices, calls a recursive procedure to triangulate them, and
/// removes the bounding box, setting boundary markers as appropriate.
/// </remarks>
public IMesh Triangulate(IList<Vertex> points, Configuration config)
{
this.predicates = config.Predicates();
this.mesh = new Mesh(config);
this.mesh.TransferNodes(points);
Otri hullleft = default(Otri), hullright = default(Otri);
int i, j, n = points.Count;
// Allocate an array of pointers to vertices for sorting.
this.sortarray = new Vertex[n];
i = 0;
foreach (var v in points)
{
sortarray[i++] = v;
}
// Sort the vertices.
VertexSorter.Sort(sortarray);
// Discard duplicate vertices, which can really mess up the algorithm.
i = 0;
for (j = 1; j < n; j++)
{
if ((sortarray[i].x == sortarray[j].x) && (sortarray[i].y == sortarray[j].y))
{
if (Log.Verbose)
{
Log.Instance.Warning(
String.Format("A duplicate vertex appeared and was ignored (ID {0}).", sortarray[j].id),
"Dwyer.Triangulate()");
}
sortarray[j].type = VertexType.UndeadVertex;
mesh.undeads++;
}
else
{
i++;
sortarray[i] = sortarray[j];
}
}
i++;
if (UseDwyer)
{
// Re-sort the array of vertices to accommodate alternating cuts.
VertexSorter.Alternate(sortarray, i);
}
// Form the Delaunay triangulation.
DivconqRecurse(0, i - 1, 0, ref hullleft, ref hullright);
this.mesh.hullsize = RemoveGhosts(ref hullleft);
return this.mesh;
}
/// <summary>
/// Merge two adjacent Delaunay triangulations into a single Delaunay triangulation.
/// </summary>
/// <param name="farleft">Bounding triangles of the left triangulation.</param>
/// <param name="innerleft">Bounding triangles of the left triangulation.</param>
/// <param name="innerright">Bounding triangles of the right triangulation.</param>
/// <param name="farright">Bounding triangles of the right triangulation.</param>
/// <param name="axis"></param>
/// <remarks>
/// This is similar to the algorithm given by Guibas and Stolfi, but uses
/// a triangle-based, rather than edge-based, data structure.
///
/// The algorithm walks up the gap between the two triangulations, knitting
/// them together. As they are merged, some of their bounding triangles
/// are converted into real triangles of the triangulation. The procedure
/// pulls each hull's bounding triangles apart, then knits them together
/// like the teeth of two gears. The Delaunay property determines, at each
/// step, whether the next "tooth" is a bounding triangle of the left hull
/// or the right. When a bounding triangle becomes real, its apex is
/// changed from NULL to a real vertex.
///
/// Only two new triangles need to be allocated. These become new bounding
/// triangles at the top and bottom of the seam. They are used to connect
/// the remaining bounding triangles (those that have not been converted
/// into real triangles) into a single fan.
///
/// On label, 'farleft' and 'innerleft' are bounding triangles of the left
/// triangulation. The origin of 'farleft' is the leftmost vertex, and
/// the destination of 'innerleft' is the rightmost vertex of the
/// triangulation. Similarly, 'innerright' and 'farright' are bounding
/// triangles of the right triangulation. The origin of 'innerright' and
/// destination of 'farright' are the leftmost and rightmost vertices.
///
/// On completion, the origin of 'farleft' is the leftmost vertex of the
/// merged triangulation, and the destination of 'farright' is the rightmost
/// vertex.
/// </remarks>
void MergeHulls(ref Otri farleft, ref Otri innerleft, ref Otri innerright,
ref Otri farright, int axis)
{
Otri leftcand = default(Otri), rightcand = default(Otri);
Otri nextedge = default(Otri);
Otri sidecasing = default(Otri), topcasing = default(Otri), outercasing = default(Otri);
Otri checkedge = default(Otri);
Otri baseedge = default(Otri);
Vertex innerleftdest;
Vertex innerrightorg;
Vertex innerleftapex, innerrightapex;
Vertex farleftpt, farrightpt;
Vertex farleftapex, farrightapex;
Vertex lowerleft, lowerright;
Vertex upperleft, upperright;
Vertex nextapex;
Vertex checkvertex;
bool changemade;
bool badedge;
bool leftfinished, rightfinished;
innerleftdest = innerleft.Dest();
innerleftapex = innerleft.Apex();
innerrightorg = innerright.Org();
innerrightapex = innerright.Apex();
// Special treatment for horizontal cuts.
if (UseDwyer && (axis == 1))
{
farleftpt = farleft.Org();
farleftapex = farleft.Apex();
farrightpt = farright.Dest();
farrightapex = farright.Apex();
// The pointers to the extremal vertices are shifted to point to the
// topmost and bottommost vertex of each hull, rather than the
// leftmost and rightmost vertices.
while (farleftapex.y < farleftpt.y)
{
farleft.Lnext();
farleft.Sym();
farleftpt = farleftapex;
farleftapex = farleft.Apex();
}
innerleft.Sym(ref checkedge);
checkvertex = checkedge.Apex();
while (checkvertex.y > innerleftdest.y)
{
checkedge.Lnext(ref innerleft);
innerleftapex = innerleftdest;
innerleftdest = checkvertex;
innerleft.Sym(ref checkedge);
checkvertex = checkedge.Apex();
}
while (innerrightapex.y < innerrightorg.y)
{
innerright.Lnext();
innerright.Sym();
innerrightorg = innerrightapex;
innerrightapex = innerright.Apex();
}
farright.Sym(ref checkedge);
checkvertex = checkedge.Apex();
while (checkvertex.y > farrightpt.y)
{
checkedge.Lnext(ref farright);
farrightapex = farrightpt;
farrightpt = checkvertex;
farright.Sym(ref checkedge);
checkvertex = checkedge.Apex();
}
}
// Find a line tangent to and below both hulls.
do
{
changemade = false;
// Make innerleftdest the "bottommost" vertex of the left hull.
if (predicates.CounterClockwise(innerleftdest, innerleftapex, innerrightorg) > 0.0)
{
innerleft.Lprev();
innerleft.Sym();
innerleftdest = innerleftapex;
innerleftapex = innerleft.Apex();
changemade = true;
}
// Make innerrightorg the "bottommost" vertex of the right hull.
if (predicates.CounterClockwise(innerrightapex, innerrightorg, innerleftdest) > 0.0)
{
innerright.Lnext();
innerright.Sym();
innerrightorg = innerrightapex;
innerrightapex = innerright.Apex();
changemade = true;
}
}
while (changemade);
// Find the two candidates to be the next "gear tooth."
innerleft.Sym(ref leftcand);
innerright.Sym(ref rightcand);
// Create the bottom new bounding triangle.
mesh.MakeTriangle(ref baseedge);
// Connect it to the bounding boxes of the left and right triangulations.
baseedge.Bond(ref innerleft);
baseedge.Lnext();
baseedge.Bond(ref innerright);
baseedge.Lnext();
baseedge.SetOrg(innerrightorg);
baseedge.SetDest(innerleftdest);
// Apex is intentionally left NULL.
// Fix the extreme triangles if necessary.
farleftpt = farleft.Org();
if (innerleftdest == farleftpt)
{
baseedge.Lnext(ref farleft);
}
farrightpt = farright.Dest();
if (innerrightorg == farrightpt)
{
baseedge.Lprev(ref farright);
}
// The vertices of the current knitting edge.
lowerleft = innerleftdest;
lowerright = innerrightorg;
// The candidate vertices for knitting.
upperleft = leftcand.Apex();
upperright = rightcand.Apex();
// Walk up the gap between the two triangulations, knitting them together.
while (true)
{
// Have we reached the top? (This isn't quite the right question,
// because even though the left triangulation might seem finished now,
// moving up on the right triangulation might reveal a new vertex of
// the left triangulation. And vice-versa.)
leftfinished = predicates.CounterClockwise(upperleft, lowerleft, lowerright) <= 0.0;
rightfinished = predicates.CounterClockwise(upperright, lowerleft, lowerright) <= 0.0;
if (leftfinished && rightfinished)
{
// Create the top new bounding triangle.
mesh.MakeTriangle(ref nextedge);
nextedge.SetOrg(lowerleft);
nextedge.SetDest(lowerright);
// Apex is intentionally left NULL.
// Connect it to the bounding boxes of the two triangulations.
nextedge.Bond(ref baseedge);
nextedge.Lnext();
nextedge.Bond(ref rightcand);
nextedge.Lnext();
nextedge.Bond(ref leftcand);
// Special treatment for horizontal cuts.
if (UseDwyer && (axis == 1))
{
farleftpt = farleft.Org();
farleftapex = farleft.Apex();
farrightpt = farright.Dest();
farrightapex = farright.Apex();
farleft.Sym(ref checkedge);
checkvertex = checkedge.Apex();
// The pointers to the extremal vertices are restored to the
// leftmost and rightmost vertices (rather than topmost and
// bottommost).
while (checkvertex.x < farleftpt.x)
{
checkedge.Lprev(ref farleft);
farleftapex = farleftpt;
farleftpt = checkvertex;
farleft.Sym(ref checkedge);
checkvertex = checkedge.Apex();
}
while (farrightapex.x > farrightpt.x)
{
farright.Lprev();
farright.Sym();
farrightpt = farrightapex;
farrightapex = farright.Apex();
}
}
return;
}
// Consider eliminating edges from the left triangulation.
if (!leftfinished)
{
// What vertex would be exposed if an edge were deleted?
leftcand.Lprev(ref nextedge);
nextedge.Sym();
nextapex = nextedge.Apex();
// If nextapex is NULL, then no vertex would be exposed; the
// triangulation would have been eaten right through.
if (nextapex != null)
{
// Check whether the edge is Delaunay.
badedge = predicates.InCircle(lowerleft, lowerright, upperleft, nextapex) > 0.0;
while (badedge)
{
// Eliminate the edge with an edge flip. As a result, the
// left triangulation will have one more boundary triangle.
nextedge.Lnext();
nextedge.Sym(ref topcasing);
nextedge.Lnext();
nextedge.Sym(ref sidecasing);
nextedge.Bond(ref topcasing);
leftcand.Bond(ref sidecasing);
leftcand.Lnext();
leftcand.Sym(ref outercasing);
nextedge.Lprev();
nextedge.Bond(ref outercasing);
// Correct the vertices to reflect the edge flip.
leftcand.SetOrg(lowerleft);
leftcand.SetDest(null);
leftcand.SetApex(nextapex);
nextedge.SetOrg(null);
nextedge.SetDest(upperleft);
nextedge.SetApex(nextapex);
// Consider the newly exposed vertex.
upperleft = nextapex;
// What vertex would be exposed if another edge were deleted?
sidecasing.Copy(ref nextedge);
nextapex = nextedge.Apex();
if (nextapex != null)
{
// Check whether the edge is Delaunay.
badedge = predicates.InCircle(lowerleft, lowerright, upperleft, nextapex) > 0.0;
}
else
{
// Avoid eating right through the triangulation.
badedge = false;
}
}
}
}
// Consider eliminating edges from the right triangulation.
if (!rightfinished)
{
// What vertex would be exposed if an edge were deleted?
rightcand.Lnext(ref nextedge);
nextedge.Sym();
nextapex = nextedge.Apex();
// If nextapex is NULL, then no vertex would be exposed; the
// triangulation would have been eaten right through.
if (nextapex != null)
{
// Check whether the edge is Delaunay.
badedge = predicates.InCircle(lowerleft, lowerright, upperright, nextapex) > 0.0;
while (badedge)
{
// Eliminate the edge with an edge flip. As a result, the
// right triangulation will have one more boundary triangle.
nextedge.Lprev();
nextedge.Sym(ref topcasing);
nextedge.Lprev();
nextedge.Sym(ref sidecasing);
nextedge.Bond(ref topcasing);
rightcand.Bond(ref sidecasing);
rightcand.Lprev();
rightcand.Sym(ref outercasing);
nextedge.Lnext();
nextedge.Bond(ref outercasing);
// Correct the vertices to reflect the edge flip.
rightcand.SetOrg(null);
rightcand.SetDest(lowerright);
rightcand.SetApex(nextapex);
nextedge.SetOrg(upperright);
nextedge.SetDest(null);
nextedge.SetApex(nextapex);
// Consider the newly exposed vertex.
upperright = nextapex;
// What vertex would be exposed if another edge were deleted?
sidecasing.Copy(ref nextedge);
nextapex = nextedge.Apex();
if (nextapex != null)
{
// Check whether the edge is Delaunay.
badedge = predicates.InCircle(lowerleft, lowerright, upperright, nextapex) > 0.0;
}
else
{
// Avoid eating right through the triangulation.
badedge = false;
}
}
}
}
if (leftfinished || (!rightfinished &&
(predicates.InCircle(upperleft, lowerleft, lowerright, upperright) > 0.0)))
{
// Knit the triangulations, adding an edge from 'lowerleft'
// to 'upperright'.
baseedge.Bond(ref rightcand);
rightcand.Lprev(ref baseedge);
baseedge.SetDest(lowerleft);
lowerright = upperright;
baseedge.Sym(ref rightcand);
upperright = rightcand.Apex();
}
else
{
// Knit the triangulations, adding an edge from 'upperleft'
// to 'lowerright'.
baseedge.Bond(ref leftcand);
leftcand.Lnext(ref baseedge);
baseedge.SetOrg(lowerright);
lowerleft = upperleft;
baseedge.Sym(ref leftcand);
upperleft = leftcand.Apex();
}
}
}
/// <summary>
/// Recursively form a Delaunay triangulation by the divide-and-conquer method.
/// </summary>
/// <param name="left"></param>
/// <param name="right"></param>
/// <param name="axis"></param>
/// <param name="farleft"></param>
/// <param name="farright"></param>
/// <remarks>
/// Recursively breaks down the problem into smaller pieces, which are
/// knitted together by mergehulls(). The base cases (problems of two or
/// three vertices) are handled specially here.
///
/// On completion, 'farleft' and 'farright' are bounding triangles such that
/// the origin of 'farleft' is the leftmost vertex (breaking ties by
/// choosing the highest leftmost vertex), and the destination of
/// 'farright' is the rightmost vertex (breaking ties by choosing the
/// lowest rightmost vertex).
/// </remarks>
void DivconqRecurse(int left, int right, int axis,
ref Otri farleft, ref Otri farright)
{
Otri midtri = default(Otri);
Otri tri1 = default(Otri);
Otri tri2 = default(Otri);
Otri tri3 = default(Otri);
Otri innerleft = default(Otri), innerright = default(Otri);
double area;
int vertices = right - left + 1;
int divider;
if (vertices == 2)
{
// The triangulation of two vertices is an edge. An edge is
// represented by two bounding triangles.
mesh.MakeTriangle(ref farleft);
farleft.SetOrg(sortarray[left]);
farleft.SetDest(sortarray[left + 1]);
// The apex is intentionally left NULL.
mesh.MakeTriangle(ref farright);
farright.SetOrg(sortarray[left + 1]);
farright.SetDest(sortarray[left]);
// The apex is intentionally left NULL.
farleft.Bond(ref farright);
farleft.Lprev();
farright.Lnext();
farleft.Bond(ref farright);
farleft.Lprev();
farright.Lnext();
farleft.Bond(ref farright);
// Ensure that the origin of 'farleft' is sortarray[0].
farright.Lprev(ref farleft);
return;
}
else if (vertices == 3)
{
// The triangulation of three vertices is either a triangle (with
// three bounding triangles) or two edges (with four bounding
// triangles). In either case, four triangles are created.
mesh.MakeTriangle(ref midtri);
mesh.MakeTriangle(ref tri1);
mesh.MakeTriangle(ref tri2);
mesh.MakeTriangle(ref tri3);
area = predicates.CounterClockwise(sortarray[left], sortarray[left + 1], sortarray[left + 2]);
if (area == 0.0)
{
// Three collinear vertices; the triangulation is two edges.
midtri.SetOrg(sortarray[left]);
midtri.SetDest(sortarray[left + 1]);
tri1.SetOrg(sortarray[left + 1]);
tri1.SetDest(sortarray[left]);
tri2.SetOrg(sortarray[left + 2]);
tri2.SetDest(sortarray[left + 1]);
tri3.SetOrg(sortarray[left + 1]);
tri3.SetDest(sortarray[left + 2]);
// All apices are intentionally left NULL.
midtri.Bond(ref tri1);
tri2.Bond(ref tri3);
midtri.Lnext();
tri1.Lprev();
tri2.Lnext();
tri3.Lprev();
midtri.Bond(ref tri3);
tri1.Bond(ref tri2);
midtri.Lnext();
tri1.Lprev();
tri2.Lnext();
tri3.Lprev();
midtri.Bond(ref tri1);
tri2.Bond(ref tri3);
// Ensure that the origin of 'farleft' is sortarray[0].
tri1.Copy(ref farleft);
// Ensure that the destination of 'farright' is sortarray[2].
tri2.Copy(ref farright);
}
else
{
// The three vertices are not collinear; the triangulation is one
// triangle, namely 'midtri'.
midtri.SetOrg(sortarray[left]);
tri1.SetDest(sortarray[left]);
tri3.SetOrg(sortarray[left]);
// Apices of tri1, tri2, and tri3 are left NULL.
if (area > 0.0)
{
// The vertices are in counterclockwise order.
midtri.SetDest(sortarray[left + 1]);
tri1.SetOrg(sortarray[left + 1]);
tri2.SetDest(sortarray[left + 1]);
midtri.SetApex(sortarray[left + 2]);
tri2.SetOrg(sortarray[left + 2]);
tri3.SetDest(sortarray[left + 2]);
}
else
{
// The vertices are in clockwise order.
midtri.SetDest(sortarray[left + 2]);
tri1.SetOrg(sortarray[left + 2]);
tri2.SetDest(sortarray[left + 2]);
midtri.SetApex(sortarray[left + 1]);
tri2.SetOrg(sortarray[left + 1]);
tri3.SetDest(sortarray[left + 1]);
}
// The topology does not depend on how the vertices are ordered.
midtri.Bond(ref tri1);
midtri.Lnext();
midtri.Bond(ref tri2);
midtri.Lnext();
midtri.Bond(ref tri3);
tri1.Lprev();
tri2.Lnext();
tri1.Bond(ref tri2);
tri1.Lprev();
tri3.Lprev();
tri1.Bond(ref tri3);
tri2.Lnext();
tri3.Lprev();
tri2.Bond(ref tri3);
// Ensure that the origin of 'farleft' is sortarray[0].
tri1.Copy(ref farleft);
// Ensure that the destination of 'farright' is sortarray[2].
if (area > 0.0)
{
tri2.Copy(ref farright);
}
else
{
farleft.Lnext(ref farright);
}
}
return;
}
else
{
// Split the vertices in half.
divider = vertices >> 1;
// Recursively triangulate each half.
DivconqRecurse(left, left + divider - 1, 1 - axis, ref farleft, ref innerleft);
DivconqRecurse(left + divider, right, 1 - axis, ref innerright, ref farright);
// Merge the two triangulations into one.
MergeHulls(ref farleft, ref innerleft, ref innerright, ref farright, axis);
}
}
/// <summary>
/// Removes ghost triangles.
/// </summary>
/// <param name="startghost"></param>
/// <returns>Number of vertices on the hull.</returns>
int RemoveGhosts(ref Otri startghost)
{
Otri searchedge = default(Otri);
Otri dissolveedge = default(Otri);
Otri deadtriangle = default(Otri);
Vertex markorg;
int hullsize;
bool noPoly = !mesh.behavior.Poly;
// Find an edge on the convex hull to start point location from.
startghost.Lprev(ref searchedge);
searchedge.Sym();
mesh.dummytri.neighbors[0] = searchedge;
// Remove the bounding box and count the convex hull edges.
startghost.Copy(ref dissolveedge);
hullsize = 0;
do
{
hullsize++;
dissolveedge.Lnext(ref deadtriangle);
dissolveedge.Lprev();
dissolveedge.Sym();
// If no PSLG is involved, set the boundary markers of all the vertices
// on the convex hull. If a PSLG is used, this step is done later.
if (noPoly)
{
// Watch out for the case where all the input vertices are collinear.
if (dissolveedge.tri.id != Mesh.DUMMY)
{
markorg = dissolveedge.Org();
if (markorg.label == 0)
{
markorg.label = 1;
}
}
}
// Remove a bounding triangle from a convex hull triangle.
dissolveedge.Dissolve(mesh.dummytri);
// Find the next bounding triangle.
deadtriangle.Sym(ref dissolveedge);
// Delete the bounding triangle.
mesh.TriangleDealloc(deadtriangle.tri);
}
while (!dissolveedge.Equals(startghost));
return hullsize;
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 9c2c3aea8fdba4a288f591cbf028963d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,192 @@
// -----------------------------------------------------------------------
// <copyright file="Incremental.cs">
// Original Triangle code by Jonathan Richard Shewchuk, http://www.cs.cmu.edu/~quake/triangle.html
// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/
// </copyright>
// -----------------------------------------------------------------------
namespace UnityEngine.U2D.Animation.TriangleNet
.Meshing.Algorithm
{
using System.Collections.Generic;
using Animation.TriangleNet.Topology;
using Animation.TriangleNet.Geometry;
/// <summary>
/// Builds a delaunay triangulation using the incremental algorithm.
/// </summary>
internal class Incremental : ITriangulator
{
Mesh mesh;
/// <summary>
/// Form a Delaunay triangulation by incrementally inserting vertices.
/// </summary>
/// <returns>Returns the number of edges on the convex hull of the
/// triangulation.</returns>
public IMesh Triangulate(IList<Vertex> points, Configuration config)
{
this.mesh = new Mesh(config);
this.mesh.TransferNodes(points);
Otri starttri = new Otri();
// Create a triangular bounding box.
GetBoundingBox();
foreach (var v in mesh.vertices.Values)
{
starttri.tri = mesh.dummytri;
Osub tmp = default(Osub);
if (mesh.InsertVertex(v, ref starttri, ref tmp, false, false) == InsertVertexResult.Duplicate)
{
if (Log.Verbose)
{
Log.Instance.Warning("A duplicate vertex appeared and was ignored.",
"Incremental.Triangulate()");
}
v.type = VertexType.UndeadVertex;
mesh.undeads++;
}
}
// Remove the bounding box.
this.mesh.hullsize = RemoveBox();
return this.mesh;
}
/// <summary>
/// Form an "infinite" bounding triangle to insert vertices into.
/// </summary>
/// <remarks>
/// The vertices at "infinity" are assigned finite coordinates, which are
/// used by the point location routines, but (mostly) ignored by the
/// Delaunay edge flip routines.
/// </remarks>
void GetBoundingBox()
{
Otri inftri = default(Otri); // Handle for the triangular bounding box.
Rectangle box = mesh.bounds;
// Find the width (or height, whichever is larger) of the triangulation.
double width = box.Width;
if (box.Height > width)
{
width = box.Height;
}
if (width == 0.0)
{
width = 1.0;
}
// Create the vertices of the bounding box.
mesh.infvertex1 = new Vertex(box.Left - 50.0 * width, box.Bottom - 40.0 * width);
mesh.infvertex2 = new Vertex(box.Right + 50.0 * width, box.Bottom - 40.0 * width);
mesh.infvertex3 = new Vertex(0.5 * (box.Left + box.Right), box.Top + 60.0 * width);
// Create the bounding box.
mesh.MakeTriangle(ref inftri);
inftri.SetOrg(mesh.infvertex1);
inftri.SetDest(mesh.infvertex2);
inftri.SetApex(mesh.infvertex3);
// Link dummytri to the bounding box so we can always find an
// edge to begin searching (point location) from.
mesh.dummytri.neighbors[0] = inftri;
}
/// <summary>
/// Remove the "infinite" bounding triangle, setting boundary markers as appropriate.
/// </summary>
/// <returns>Returns the number of edges on the convex hull of the triangulation.</returns>
/// <remarks>
/// The triangular bounding box has three boundary triangles (one for each
/// side of the bounding box), and a bunch of triangles fanning out from
/// the three bounding box vertices (one triangle for each edge of the
/// convex hull of the inner mesh). This routine removes these triangles.
/// </remarks>
int RemoveBox()
{
Otri deadtriangle = default(Otri);
Otri searchedge = default(Otri);
Otri checkedge = default(Otri);
Otri nextedge = default(Otri), finaledge = default(Otri), dissolveedge = default(Otri);
Vertex markorg;
int hullsize;
bool noPoly = !mesh.behavior.Poly;
// Find a boundary triangle.
nextedge.tri = mesh.dummytri;
nextedge.orient = 0;
nextedge.Sym();
// Mark a place to stop.
nextedge.Lprev(ref finaledge);
nextedge.Lnext();
nextedge.Sym();
// Find a triangle (on the boundary of the vertex set) that isn't
// a bounding box triangle.
nextedge.Lprev(ref searchedge);
searchedge.Sym();
// Check whether nextedge is another boundary triangle
// adjacent to the first one.
nextedge.Lnext(ref checkedge);
checkedge.Sym();
if (checkedge.tri.id == Mesh.DUMMY)
{
// Go on to the next triangle. There are only three boundary
// triangles, and this next triangle cannot be the third one,
// so it's safe to stop here.
searchedge.Lprev();
searchedge.Sym();
}
// Find a new boundary edge to search from, as the current search
// edge lies on a bounding box triangle and will be deleted.
mesh.dummytri.neighbors[0] = searchedge;
hullsize = -2;
while (!nextedge.Equals(finaledge))
{
hullsize++;
nextedge.Lprev(ref dissolveedge);
dissolveedge.Sym();
// If not using a PSLG, the vertices should be marked now.
// (If using a PSLG, markhull() will do the job.)
if (noPoly)
{
// Be careful! One must check for the case where all the input
// vertices are collinear, and thus all the triangles are part of
// the bounding box. Otherwise, the setvertexmark() call below
// will cause a bad pointer reference.
if (dissolveedge.tri.id != Mesh.DUMMY)
{
markorg = dissolveedge.Org();
if (markorg.label == 0)
{
markorg.label = 1;
}
}
}
// Disconnect the bounding box triangle from the mesh triangle.
dissolveedge.Dissolve(mesh.dummytri);
nextedge.Lnext(ref deadtriangle);
deadtriangle.Sym(ref nextedge);
// Get rid of the bounding box triangle.
mesh.TriangleDealloc(deadtriangle.tri);
// Do we need to turn the corner?
if (nextedge.tri.id == Mesh.DUMMY)
{
// Turn the corner.
dissolveedge.Copy(ref nextedge);
}
}
mesh.TriangleDealloc(finaledge.tri);
return hullsize;
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 3ffa994dccc684238af9f51c4b71a053
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,812 @@
// -----------------------------------------------------------------------
// <copyright file="SweepLine.cs">
// Original Triangle code by Jonathan Richard Shewchuk, http://www.cs.cmu.edu/~quake/triangle.html
// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/
// </copyright>
// -----------------------------------------------------------------------
namespace UnityEngine.U2D.Animation.TriangleNet
.Meshing.Algorithm
{
using System;
using System.Collections.Generic;
using Animation.TriangleNet.Topology;
using Animation.TriangleNet.Geometry;
using Animation.TriangleNet.Tools;
/// <summary>
/// Builds a delaunay triangulation using the sweepline algorithm.
/// </summary>
internal class SweepLine : ITriangulator
{
static int randomseed = 1;
static int SAMPLERATE = 10;
static int randomnation(int choices)
{
randomseed = (randomseed * 1366 + 150889) % 714025;
return randomseed / (714025 / choices + 1);
}
IPredicates predicates;
Mesh mesh;
double xminextreme; // Nonexistent x value used as a flag in sweepline.
List<SplayNode> splaynodes;
public IMesh Triangulate(IList<Vertex> points, Configuration config)
{
this.predicates = config.Predicates();
this.mesh = new Mesh(config);
this.mesh.TransferNodes(points);
// Nonexistent x value used as a flag to mark circle events in sweepline
// Delaunay algorithm.
xminextreme = 10 * mesh.bounds.Left - 9 * mesh.bounds.Right;
SweepEvent[] eventheap;
SweepEvent nextevent;
SweepEvent newevent;
SplayNode splayroot;
Otri bottommost = default(Otri);
Otri searchtri = default(Otri);
Otri fliptri;
Otri lefttri = default(Otri);
Otri righttri = default(Otri);
Otri farlefttri = default(Otri);
Otri farrighttri = default(Otri);
Otri inserttri = default(Otri);
Vertex firstvertex, secondvertex;
Vertex nextvertex, lastvertex;
Vertex connectvertex;
Vertex leftvertex, midvertex, rightvertex;
double lefttest, righttest;
int heapsize;
bool check4events, farrightflag = false;
splaynodes = new List<SplayNode>();
splayroot = null;
heapsize = points.Count;
CreateHeap(out eventheap, heapsize);//, out events, out freeevents);
mesh.MakeTriangle(ref lefttri);
mesh.MakeTriangle(ref righttri);
lefttri.Bond(ref righttri);
lefttri.Lnext();
righttri.Lprev();
lefttri.Bond(ref righttri);
lefttri.Lnext();
righttri.Lprev();
lefttri.Bond(ref righttri);
firstvertex = eventheap[0].vertexEvent;
HeapDelete(eventheap, heapsize, 0);
heapsize--;
do
{
if (heapsize == 0)
{
Log.Instance.Error("Input vertices are all identical.", "SweepLine.Triangulate()");
throw new Exception("Input vertices are all identical.");
}
secondvertex = eventheap[0].vertexEvent;
HeapDelete(eventheap, heapsize, 0);
heapsize--;
if ((firstvertex.x == secondvertex.x) &&
(firstvertex.y == secondvertex.y))
{
if (Log.Verbose)
{
Log.Instance.Warning("A duplicate vertex appeared and was ignored (ID " + secondvertex.id + ").",
"SweepLine.Triangulate().1");
}
secondvertex.type = VertexType.UndeadVertex;
mesh.undeads++;
}
}
while ((firstvertex.x == secondvertex.x) &&
(firstvertex.y == secondvertex.y));
lefttri.SetOrg(firstvertex);
lefttri.SetDest(secondvertex);
righttri.SetOrg(secondvertex);
righttri.SetDest(firstvertex);
lefttri.Lprev(ref bottommost);
lastvertex = secondvertex;
while (heapsize > 0)
{
nextevent = eventheap[0];
HeapDelete(eventheap, heapsize, 0);
heapsize--;
check4events = true;
if (nextevent.xkey < mesh.bounds.Left)
{
fliptri = nextevent.otriEvent;
fliptri.Oprev(ref farlefttri);
Check4DeadEvent(ref farlefttri, eventheap, ref heapsize);
fliptri.Onext(ref farrighttri);
Check4DeadEvent(ref farrighttri, eventheap, ref heapsize);
if (farlefttri.Equals(bottommost))
{
fliptri.Lprev(ref bottommost);
}
mesh.Flip(ref fliptri);
fliptri.SetApex(null);
fliptri.Lprev(ref lefttri);
fliptri.Lnext(ref righttri);
lefttri.Sym(ref farlefttri);
if (randomnation(SAMPLERATE) == 0)
{
fliptri.Sym();
leftvertex = fliptri.Dest();
midvertex = fliptri.Apex();
rightvertex = fliptri.Org();
splayroot = CircleTopInsert(splayroot, lefttri, leftvertex, midvertex, rightvertex, nextevent.ykey);
}
}
else
{
nextvertex = nextevent.vertexEvent;
if ((nextvertex.x == lastvertex.x) &&
(nextvertex.y == lastvertex.y))
{
if (Log.Verbose)
{
Log.Instance.Warning("A duplicate vertex appeared and was ignored (ID " + nextvertex.id + ").",
"SweepLine.Triangulate().2");
}
nextvertex.type = VertexType.UndeadVertex;
mesh.undeads++;
check4events = false;
}
else
{
lastvertex = nextvertex;
splayroot = FrontLocate(splayroot, bottommost, nextvertex, ref searchtri, ref farrightflag);
//bottommost.Copy(ref searchtri);
//farrightflag = false;
//while (!farrightflag && RightOfHyperbola(ref searchtri, nextvertex))
//{
// searchtri.OnextSelf();
// farrightflag = searchtri.Equal(bottommost);
//}
Check4DeadEvent(ref searchtri, eventheap, ref heapsize);
searchtri.Copy(ref farrighttri);
searchtri.Sym(ref farlefttri);
mesh.MakeTriangle(ref lefttri);
mesh.MakeTriangle(ref righttri);
connectvertex = farrighttri.Dest();
lefttri.SetOrg(connectvertex);
lefttri.SetDest(nextvertex);
righttri.SetOrg(nextvertex);
righttri.SetDest(connectvertex);
lefttri.Bond(ref righttri);
lefttri.Lnext();
righttri.Lprev();
lefttri.Bond(ref righttri);
lefttri.Lnext();
righttri.Lprev();
lefttri.Bond(ref farlefttri);
righttri.Bond(ref farrighttri);
if (!farrightflag && farrighttri.Equals(bottommost))
{
lefttri.Copy(ref bottommost);
}
if (randomnation(SAMPLERATE) == 0)
{
splayroot = SplayInsert(splayroot, lefttri, nextvertex);
}
else if (randomnation(SAMPLERATE) == 0)
{
righttri.Lnext(ref inserttri);
splayroot = SplayInsert(splayroot, inserttri, nextvertex);
}
}
}
if (check4events)
{
leftvertex = farlefttri.Apex();
midvertex = lefttri.Dest();
rightvertex = lefttri.Apex();
lefttest = predicates.CounterClockwise(leftvertex, midvertex, rightvertex);
if (lefttest > 0.0)
{
newevent = new SweepEvent();
newevent.xkey = xminextreme;
newevent.ykey = CircleTop(leftvertex, midvertex, rightvertex, lefttest);
newevent.otriEvent = lefttri;
HeapInsert(eventheap, heapsize, newevent);
heapsize++;
lefttri.SetOrg(new SweepEventVertex(newevent));
}
leftvertex = righttri.Apex();
midvertex = righttri.Org();
rightvertex = farrighttri.Apex();
righttest = predicates.CounterClockwise(leftvertex, midvertex, rightvertex);
if (righttest > 0.0)
{
newevent = new SweepEvent();
newevent.xkey = xminextreme;
newevent.ykey = CircleTop(leftvertex, midvertex, rightvertex, righttest);
newevent.otriEvent = farrighttri;
HeapInsert(eventheap, heapsize, newevent);
heapsize++;
farrighttri.SetOrg(new SweepEventVertex(newevent));
}
}
}
splaynodes.Clear();
bottommost.Lprev();
this.mesh.hullsize = RemoveGhosts(ref bottommost);
return this.mesh;
}
#region Heap
void HeapInsert(SweepEvent[] heap, int heapsize, SweepEvent newevent)
{
double eventx, eventy;
int eventnum;
int parent;
bool notdone;
eventx = newevent.xkey;
eventy = newevent.ykey;
eventnum = heapsize;
notdone = eventnum > 0;
while (notdone)
{
parent = (eventnum - 1) >> 1;
if ((heap[parent].ykey < eventy) ||
((heap[parent].ykey == eventy)
&& (heap[parent].xkey <= eventx)))
{
notdone = false;
}
else
{
heap[eventnum] = heap[parent];
heap[eventnum].heapposition = eventnum;
eventnum = parent;
notdone = eventnum > 0;
}
}
heap[eventnum] = newevent;
newevent.heapposition = eventnum;
}
void Heapify(SweepEvent[] heap, int heapsize, int eventnum)
{
SweepEvent thisevent;
double eventx, eventy;
int leftchild, rightchild;
int smallest;
bool notdone;
thisevent = heap[eventnum];
eventx = thisevent.xkey;
eventy = thisevent.ykey;
leftchild = 2 * eventnum + 1;
notdone = leftchild < heapsize;
while (notdone)
{
if ((heap[leftchild].ykey < eventy) ||
((heap[leftchild].ykey == eventy)
&& (heap[leftchild].xkey < eventx)))
{
smallest = leftchild;
}
else
{
smallest = eventnum;
}
rightchild = leftchild + 1;
if (rightchild < heapsize)
{
if ((heap[rightchild].ykey < heap[smallest].ykey) ||
((heap[rightchild].ykey == heap[smallest].ykey)
&& (heap[rightchild].xkey < heap[smallest].xkey)))
{
smallest = rightchild;
}
}
if (smallest == eventnum)
{
notdone = false;
}
else
{
heap[eventnum] = heap[smallest];
heap[eventnum].heapposition = eventnum;
heap[smallest] = thisevent;
thisevent.heapposition = smallest;
eventnum = smallest;
leftchild = 2 * eventnum + 1;
notdone = leftchild < heapsize;
}
}
}
void HeapDelete(SweepEvent[] heap, int heapsize, int eventnum)
{
SweepEvent moveevent;
double eventx, eventy;
int parent;
bool notdone;
moveevent = heap[heapsize - 1];
if (eventnum > 0)
{
eventx = moveevent.xkey;
eventy = moveevent.ykey;
do
{
parent = (eventnum - 1) >> 1;
if ((heap[parent].ykey < eventy) ||
((heap[parent].ykey == eventy)
&& (heap[parent].xkey <= eventx)))
{
notdone = false;
}
else
{
heap[eventnum] = heap[parent];
heap[eventnum].heapposition = eventnum;
eventnum = parent;
notdone = eventnum > 0;
}
}
while (notdone);
}
heap[eventnum] = moveevent;
moveevent.heapposition = eventnum;
Heapify(heap, heapsize - 1, eventnum);
}
void CreateHeap(out SweepEvent[] eventheap, int size)
{
Vertex thisvertex;
int maxevents;
int i;
SweepEvent evt;
maxevents = (3 * size) / 2;
eventheap = new SweepEvent[maxevents];
i = 0;
foreach (var v in mesh.vertices.Values)
{
thisvertex = v;
evt = new SweepEvent();
evt.vertexEvent = thisvertex;
evt.xkey = thisvertex.x;
evt.ykey = thisvertex.y;
HeapInsert(eventheap, i++, evt);
}
}
#endregion
#region Splaytree
SplayNode Splay(SplayNode splaytree, Point searchpoint, ref Otri searchtri)
{
SplayNode child, grandchild;
SplayNode lefttree, righttree;
SplayNode leftright;
Vertex checkvertex;
bool rightofroot, rightofchild;
if (splaytree == null)
{
return null;
}
checkvertex = splaytree.keyedge.Dest();
if (checkvertex == splaytree.keydest)
{
rightofroot = RightOfHyperbola(ref splaytree.keyedge, searchpoint);
if (rightofroot)
{
splaytree.keyedge.Copy(ref searchtri);
child = splaytree.rchild;
}
else
{
child = splaytree.lchild;
}
if (child == null)
{
return splaytree;
}
checkvertex = child.keyedge.Dest();
if (checkvertex != child.keydest)
{
child = Splay(child, searchpoint, ref searchtri);
if (child == null)
{
if (rightofroot)
{
splaytree.rchild = null;
}
else
{
splaytree.lchild = null;
}
return splaytree;
}
}
rightofchild = RightOfHyperbola(ref child.keyedge, searchpoint);
if (rightofchild)
{
child.keyedge.Copy(ref searchtri);
grandchild = Splay(child.rchild, searchpoint, ref searchtri);
child.rchild = grandchild;
}
else
{
grandchild = Splay(child.lchild, searchpoint, ref searchtri);
child.lchild = grandchild;
}
if (grandchild == null)
{
if (rightofroot)
{
splaytree.rchild = child.lchild;
child.lchild = splaytree;
}
else
{
splaytree.lchild = child.rchild;
child.rchild = splaytree;
}
return child;
}
if (rightofchild)
{
if (rightofroot)
{
splaytree.rchild = child.lchild;
child.lchild = splaytree;
}
else
{
splaytree.lchild = grandchild.rchild;
grandchild.rchild = splaytree;
}
child.rchild = grandchild.lchild;
grandchild.lchild = child;
}
else
{
if (rightofroot)
{
splaytree.rchild = grandchild.lchild;
grandchild.lchild = splaytree;
}
else
{
splaytree.lchild = child.rchild;
child.rchild = splaytree;
}
child.lchild = grandchild.rchild;
grandchild.rchild = child;
}
return grandchild;
}
else
{
lefttree = Splay(splaytree.lchild, searchpoint, ref searchtri);
righttree = Splay(splaytree.rchild, searchpoint, ref searchtri);
splaynodes.Remove(splaytree);
if (lefttree == null)
{
return righttree;
}
else if (righttree == null)
{
return lefttree;
}
else if (lefttree.rchild == null)
{
lefttree.rchild = righttree.lchild;
righttree.lchild = lefttree;
return righttree;
}
else if (righttree.lchild == null)
{
righttree.lchild = lefttree.rchild;
lefttree.rchild = righttree;
return lefttree;
}
else
{
// printf("Holy Toledo!!!\n");
leftright = lefttree.rchild;
while (leftright.rchild != null)
{
leftright = leftright.rchild;
}
leftright.rchild = righttree;
return lefttree;
}
}
}
SplayNode SplayInsert(SplayNode splayroot, Otri newkey, Point searchpoint)
{
SplayNode newsplaynode;
newsplaynode = new SplayNode(); //poolalloc(m.splaynodes);
splaynodes.Add(newsplaynode);
newkey.Copy(ref newsplaynode.keyedge);
newsplaynode.keydest = newkey.Dest();
if (splayroot == null)
{
newsplaynode.lchild = null;
newsplaynode.rchild = null;
}
else if (RightOfHyperbola(ref splayroot.keyedge, searchpoint))
{
newsplaynode.lchild = splayroot;
newsplaynode.rchild = splayroot.rchild;
splayroot.rchild = null;
}
else
{
newsplaynode.lchild = splayroot.lchild;
newsplaynode.rchild = splayroot;
splayroot.lchild = null;
}
return newsplaynode;
}
SplayNode FrontLocate(SplayNode splayroot, Otri bottommost, Vertex searchvertex,
ref Otri searchtri, ref bool farright)
{
bool farrightflag;
bottommost.Copy(ref searchtri);
splayroot = Splay(splayroot, searchvertex, ref searchtri);
farrightflag = false;
while (!farrightflag && RightOfHyperbola(ref searchtri, searchvertex))
{
searchtri.Onext();
farrightflag = searchtri.Equals(bottommost);
}
farright = farrightflag;
return splayroot;
}
SplayNode CircleTopInsert(SplayNode splayroot, Otri newkey,
Vertex pa, Vertex pb, Vertex pc, double topy)
{
double ccwabc;
double xac, yac, xbc, ybc;
double aclen2, bclen2;
Point searchpoint = new Point(); // TODO: mesh.nextras
Otri dummytri = default(Otri);
ccwabc = predicates.CounterClockwise(pa, pb, pc);
xac = pa.x - pc.x;
yac = pa.y - pc.y;
xbc = pb.x - pc.x;
ybc = pb.y - pc.y;
aclen2 = xac * xac + yac * yac;
bclen2 = xbc * xbc + ybc * ybc;
searchpoint.x = pc.x - (yac * bclen2 - ybc * aclen2) / (2.0 * ccwabc);
searchpoint.y = topy;
return SplayInsert(Splay(splayroot, searchpoint, ref dummytri), newkey, searchpoint);
}
#endregion
bool RightOfHyperbola(ref Otri fronttri, Point newsite)
{
Vertex leftvertex, rightvertex;
double dxa, dya, dxb, dyb;
Statistic.HyperbolaCount++;
leftvertex = fronttri.Dest();
rightvertex = fronttri.Apex();
if ((leftvertex.y < rightvertex.y) ||
((leftvertex.y == rightvertex.y) &&
(leftvertex.x < rightvertex.x)))
{
if (newsite.x >= rightvertex.x)
{
return true;
}
}
else
{
if (newsite.x <= leftvertex.x)
{
return false;
}
}
dxa = leftvertex.x - newsite.x;
dya = leftvertex.y - newsite.y;
dxb = rightvertex.x - newsite.x;
dyb = rightvertex.y - newsite.y;
return dya * (dxb * dxb + dyb * dyb) > dyb * (dxa * dxa + dya * dya);
}
double CircleTop(Vertex pa, Vertex pb, Vertex pc, double ccwabc)
{
double xac, yac, xbc, ybc, xab, yab;
double aclen2, bclen2, ablen2;
Statistic.CircleTopCount++;
xac = pa.x - pc.x;
yac = pa.y - pc.y;
xbc = pb.x - pc.x;
ybc = pb.y - pc.y;
xab = pa.x - pb.x;
yab = pa.y - pb.y;
aclen2 = xac * xac + yac * yac;
bclen2 = xbc * xbc + ybc * ybc;
ablen2 = xab * xab + yab * yab;
return pc.y + (xac * bclen2 - xbc * aclen2 + Math.Sqrt(aclen2 * bclen2 * ablen2)) / (2.0 * ccwabc);
}
void Check4DeadEvent(ref Otri checktri, SweepEvent[] eventheap, ref int heapsize)
{
SweepEvent deadevent;
SweepEventVertex eventvertex;
int eventnum = -1;
eventvertex = checktri.Org() as SweepEventVertex;
if (eventvertex != null)
{
deadevent = eventvertex.evt;
eventnum = deadevent.heapposition;
HeapDelete(eventheap, heapsize, eventnum);
heapsize--;
checktri.SetOrg(null);
}
}
/// <summary>
/// Removes ghost triangles.
/// </summary>
/// <param name="startghost"></param>
/// <returns>Number of vertices on the hull.</returns>
int RemoveGhosts(ref Otri startghost)
{
Otri searchedge = default(Otri);
Otri dissolveedge = default(Otri);
Otri deadtriangle = default(Otri);
Vertex markorg;
int hullsize;
bool noPoly = !mesh.behavior.Poly;
var dummytri = mesh.dummytri;
// Find an edge on the convex hull to start point location from.
startghost.Lprev(ref searchedge);
searchedge.Sym();
dummytri.neighbors[0] = searchedge;
// Remove the bounding box and count the convex hull edges.
startghost.Copy(ref dissolveedge);
hullsize = 0;
do
{
hullsize++;
dissolveedge.Lnext(ref deadtriangle);
dissolveedge.Lprev();
dissolveedge.Sym();
// If no PSLG is involved, set the boundary markers of all the vertices
// on the convex hull. If a PSLG is used, this step is done later.
if (noPoly)
{
// Watch out for the case where all the input vertices are collinear.
if (dissolveedge.tri.id != Mesh.DUMMY)
{
markorg = dissolveedge.Org();
if (markorg.label == 0)
{
markorg.label = 1;
}
}
}
// Remove a bounding triangle from a convex hull triangle.
dissolveedge.Dissolve(dummytri);
// Find the next bounding triangle.
deadtriangle.Sym(ref dissolveedge);
// Delete the bounding triangle.
mesh.TriangleDealloc(deadtriangle.tri);
}
while (!dissolveedge.Equals(startghost));
return hullsize;
}
#region Internal classes
/// <summary>
/// A node in a heap used to store events for the sweepline Delaunay algorithm.
/// </summary>
/// <remarks>
/// Only used in the sweepline algorithm.
///
/// Nodes do not point directly to their parents or children in the heap. Instead, each
/// node knows its position in the heap, and can look up its parent and children in a
/// separate array. To distinguish site events from circle events, all circle events are
/// given an invalid (smaller than 'xmin') x-coordinate 'xkey'.
/// </remarks>
class SweepEvent
{
public double xkey, ykey; // Coordinates of the event.
public Vertex vertexEvent; // Vertex event.
public Otri otriEvent; // Circle event.
public int heapposition; // Marks this event's position in the heap.
}
/// <summary>
/// Introducing a new class which aggregates a sweep event is the easiest way
/// to handle the pointer magic of the original code (casting a sweep event
/// to vertex etc.).
/// </summary>
class SweepEventVertex : Vertex
{
public SweepEvent evt;
public SweepEventVertex(SweepEvent e)
{
evt = e;
}
}
/// <summary>
/// A node in the splay tree.
/// </summary>
/// <remarks>
/// Only used in the sweepline algorithm.
///
/// Each node holds an oriented ghost triangle that represents a boundary edge
/// of the growing triangulation. When a circle event covers two boundary edges
/// with a triangle, so that they are no longer boundary edges, those edges are
/// not immediately deleted from the tree; rather, they are lazily deleted when
/// they are next encountered. (Since only a random sample of boundary edges are
/// kept in the tree, lazy deletion is faster.) 'keydest' is used to verify that
/// a triangle is still the same as when it entered the splay tree; if it has
/// been rotated (due to a circle event), it no longer represents a boundary
/// edge and should be deleted.
/// </remarks>
class SplayNode
{
public Otri keyedge; // Lprev of an edge on the front.
public Vertex keydest; // Used to verify that splay node is still live.
public SplayNode lchild, rchild; // Children in splay tree.
}
#endregion
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 0e71212805f8c4fc7a7db8dd552b73a6
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: efa4ba0c38e16414f80d3ccfb6cbd821
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,40 @@
namespace UnityEngine.U2D.Animation.TriangleNet
.Meshing
{
/// <summary>
/// Mesh constraint options for polygon triangulation.
/// </summary>
internal class ConstraintOptions
{
// TODO: remove ConstraintOptions.UseRegions
/// <summary>
/// Gets or sets a value indicating whether to use regions.
/// </summary>
[System.Obsolete("Not used anywhere, will be removed in beta 4.")]
public bool UseRegions { get; set; }
/// <summary>
/// Gets or sets a value indicating whether to create a Conforming
/// Delaunay triangulation.
/// </summary>
public bool ConformingDelaunay { get; set; }
/// <summary>
/// Gets or sets a value indicating whether to enclose the convex
/// hull with segments.
/// </summary>
public bool Convex { get; set; }
/// <summary>
/// Gets or sets a flag indicating whether to suppress boundary
/// segment splitting.
/// </summary>
/// <remarks>
/// 0 = split segments (default)
/// 1 = no new vertices on the boundary
/// 2 = prevent all segment splitting, including internal boundaries
/// </remarks>
public int SegmentSplitting { get; set; }
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 8bb3407583ec744c0ba8307cebefdc1d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,489 @@
// -----------------------------------------------------------------------
// <copyright file="Converter.cs" company="">
// Original Triangle code by Jonathan Richard Shewchuk, http://www.cs.cmu.edu/~quake/triangle.html
// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/
// </copyright>
// -----------------------------------------------------------------------
namespace UnityEngine.U2D.Animation.TriangleNet
.Meshing
{
using System;
using System.Collections.Generic;
using System.Linq;
using Animation.TriangleNet.Geometry;
using Animation.TriangleNet.Topology;
using Animation.TriangleNet.Topology.DCEL;
using HVertex = Animation.TriangleNet.Topology.DCEL.Vertex;
using TVertex = Animation.TriangleNet.Geometry.Vertex;
/// <summary>
/// The Converter class provides methods for mesh reconstruction and conversion.
/// </summary>
internal static class Converter
{
#region Triangle mesh conversion
/// <summary>
/// Reconstruct a triangulation from its raw data representation.
/// </summary>
internal static Mesh ToMesh(Polygon polygon, IList<ITriangle> triangles)
{
return ToMesh(polygon, triangles.ToArray());
}
/// <summary>
/// Reconstruct a triangulation from its raw data representation.
/// </summary>
internal static Mesh ToMesh(Polygon polygon, ITriangle[] triangles)
{
Otri tri = default(Otri);
Osub subseg = default(Osub);
int i = 0;
int elements = triangles == null ? 0 : triangles.Length;
int segments = polygon.Segments.Count;
// TODO: Configuration should be a function argument.
var mesh = new Mesh(new Configuration());
mesh.TransferNodes(polygon.Points);
mesh.regions.AddRange(polygon.Regions);
mesh.behavior.useRegions = polygon.Regions.Count > 0;
if (polygon.Segments.Count > 0)
{
mesh.behavior.Poly = true;
mesh.holes.AddRange(polygon.Holes);
}
// Create the triangles.
for (i = 0; i < elements; i++)
{
mesh.MakeTriangle(ref tri);
}
if (mesh.behavior.Poly)
{
mesh.insegments = segments;
// Create the subsegments.
for (i = 0; i < segments; i++)
{
mesh.MakeSegment(ref subseg);
}
}
var vertexarray = SetNeighbors(mesh, triangles);
SetSegments(mesh, polygon, vertexarray);
return mesh;
}
/// <summary>
/// Finds the adjacencies between triangles by forming a stack of triangles for
/// each vertex. Each triangle is on three different stacks simultaneously.
/// </summary>
private static List<Otri>[] SetNeighbors(Mesh mesh, ITriangle[] triangles)
{
Otri tri = default(Otri);
Otri triangleleft = default(Otri);
Otri checktri = default(Otri);
Otri checkleft = default(Otri);
Otri nexttri;
TVertex tdest, tapex;
TVertex checkdest, checkapex;
int[] corner = new int[3];
int aroundvertex;
int i;
// Allocate a temporary array that maps each vertex to some adjacent triangle.
var vertexarray = new List<Otri>[mesh.vertices.Count];
// Each vertex is initially unrepresented.
for (i = 0; i < mesh.vertices.Count; i++)
{
Otri tmp = default(Otri);
tmp.tri = mesh.dummytri;
vertexarray[i] = new List<Otri>(3);
vertexarray[i].Add(tmp);
}
i = 0;
// Read the triangles from the .ele file, and link
// together those that share an edge.
foreach (var item in mesh.triangles)
{
tri.tri = item;
// Copy the triangle's three corners.
for (int j = 0; j < 3; j++)
{
corner[j] = triangles[i].GetVertexID(j);
if ((corner[j] < 0) || (corner[j] >= mesh.invertices))
{
Log.Instance.Error("Triangle has an invalid vertex index.", "MeshReader.Reconstruct()");
throw new Exception("Triangle has an invalid vertex index.");
}
}
// Read the triangle's attributes.
tri.tri.label = triangles[i].Label;
// TODO: VarArea
if (mesh.behavior.VarArea)
{
tri.tri.area = triangles[i].Area;
}
// Set the triangle's vertices.
tri.orient = 0;
tri.SetOrg(mesh.vertices[corner[0]]);
tri.SetDest(mesh.vertices[corner[1]]);
tri.SetApex(mesh.vertices[corner[2]]);
// Try linking the triangle to others that share these vertices.
for (tri.orient = 0; tri.orient < 3; tri.orient++)
{
// Take the number for the origin of triangleloop.
aroundvertex = corner[tri.orient];
int index = vertexarray[aroundvertex].Count - 1;
// Look for other triangles having this vertex.
nexttri = vertexarray[aroundvertex][index];
// Push the current triangle onto the stack.
vertexarray[aroundvertex].Add(tri);
checktri = nexttri;
if (checktri.tri.id != Mesh.DUMMY)
{
tdest = tri.Dest();
tapex = tri.Apex();
// Look for other triangles that share an edge.
do
{
checkdest = checktri.Dest();
checkapex = checktri.Apex();
if (tapex == checkdest)
{
// The two triangles share an edge; bond them together.
tri.Lprev(ref triangleleft);
triangleleft.Bond(ref checktri);
}
if (tdest == checkapex)
{
// The two triangles share an edge; bond them together.
checktri.Lprev(ref checkleft);
tri.Bond(ref checkleft);
}
// Find the next triangle in the stack.
index--;
nexttri = vertexarray[aroundvertex][index];
checktri = nexttri;
}
while (checktri.tri.id != Mesh.DUMMY);
}
}
i++;
}
return vertexarray;
}
/// <summary>
/// Finds the adjacencies between triangles and subsegments.
/// </summary>
private static void SetSegments(Mesh mesh, Polygon polygon, List<Otri>[] vertexarray)
{
Otri checktri = default(Otri);
Otri nexttri; // Triangle
TVertex checkdest;
Otri checkneighbor = default(Otri);
Osub subseg = default(Osub);
Otri prevlink; // Triangle
TVertex tmp;
TVertex sorg, sdest;
bool notfound;
//bool segmentmarkers = false;
int boundmarker;
int aroundvertex;
int i;
int hullsize = 0;
// Prepare to count the boundary edges.
if (mesh.behavior.Poly)
{
// Link the segments to their neighboring triangles.
boundmarker = 0;
i = 0;
foreach (var item in mesh.subsegs.Values)
{
subseg.seg = item;
sorg = polygon.Segments[i].GetVertex(0);
sdest = polygon.Segments[i].GetVertex(1);
boundmarker = polygon.Segments[i].Label;
if ((sorg.id < 0 || sorg.id >= mesh.invertices) || (sdest.id < 0 || sdest.id >= mesh.invertices))
{
Log.Instance.Error("Segment has an invalid vertex index.", "MeshReader.Reconstruct()");
throw new Exception("Segment has an invalid vertex index.");
}
// set the subsegment's vertices.
subseg.orient = 0;
subseg.SetOrg(sorg);
subseg.SetDest(sdest);
subseg.SetSegOrg(sorg);
subseg.SetSegDest(sdest);
subseg.seg.boundary = boundmarker;
// Try linking the subsegment to triangles that share these vertices.
for (subseg.orient = 0; subseg.orient < 2; subseg.orient++)
{
// Take the number for the destination of subsegloop.
aroundvertex = subseg.orient == 1 ? sorg.id : sdest.id;
int index = vertexarray[aroundvertex].Count - 1;
// Look for triangles having this vertex.
prevlink = vertexarray[aroundvertex][index];
nexttri = vertexarray[aroundvertex][index];
checktri = nexttri;
tmp = subseg.Org();
notfound = true;
// Look for triangles having this edge. Note that I'm only
// comparing each triangle's destination with the subsegment;
// each triangle's apex is handled through a different vertex.
// Because each triangle appears on three vertices' lists, each
// occurrence of a triangle on a list can (and does) represent
// an edge. In this way, most edges are represented twice, and
// every triangle-subsegment bond is represented once.
while (notfound && (checktri.tri.id != Mesh.DUMMY))
{
checkdest = checktri.Dest();
if (tmp == checkdest)
{
// We have a match. Remove this triangle from the list.
//prevlink = vertexarray[aroundvertex][index];
vertexarray[aroundvertex].Remove(prevlink);
// Bond the subsegment to the triangle.
checktri.SegBond(ref subseg);
// Check if this is a boundary edge.
checktri.Sym(ref checkneighbor);
if (checkneighbor.tri.id == Mesh.DUMMY)
{
// The next line doesn't insert a subsegment (because there's
// already one there), but it sets the boundary markers of
// the existing subsegment and its vertices.
mesh.InsertSubseg(ref checktri, 1);
hullsize++;
}
notfound = false;
}
index--;
// Find the next triangle in the stack.
prevlink = vertexarray[aroundvertex][index];
nexttri = vertexarray[aroundvertex][index];
checktri = nexttri;
}
}
i++;
}
}
// Mark the remaining edges as not being attached to any subsegment.
// Also, count the (yet uncounted) boundary edges.
for (i = 0; i < mesh.vertices.Count; i++)
{
// Search the stack of triangles adjacent to a vertex.
int index = vertexarray[i].Count - 1;
nexttri = vertexarray[i][index];
checktri = nexttri;
while (checktri.tri.id != Mesh.DUMMY)
{
// Find the next triangle in the stack before this
// information gets overwritten.
index--;
nexttri = vertexarray[i][index];
// No adjacent subsegment. (This overwrites the stack info.)
checktri.SegDissolve(mesh.dummysub);
checktri.Sym(ref checkneighbor);
if (checkneighbor.tri.id == Mesh.DUMMY)
{
mesh.InsertSubseg(ref checktri, 1);
hullsize++;
}
checktri = nexttri;
}
}
mesh.hullsize = hullsize;
}
#endregion
#region DCEL conversion
internal static DcelMesh ToDCEL(Mesh mesh)
{
var dcel = new DcelMesh();
var vertices = new HVertex[mesh.vertices.Count];
var faces = new Face[mesh.triangles.Count];
dcel.HalfEdges.Capacity = 2 * mesh.NumberOfEdges;
mesh.Renumber();
HVertex vertex;
foreach (var v in mesh.vertices.Values)
{
vertex = new HVertex(v.x, v.y);
vertex.id = v.id;
vertex.label = v.label;
vertices[v.id] = vertex;
}
// Maps a triangle to its 3 edges (used to set next pointers).
var map = new List<HalfEdge>[mesh.triangles.Count];
Face face;
foreach (var t in mesh.triangles)
{
face = new Face(null);
face.id = t.id;
faces[t.id] = face;
map[t.id] = new List<HalfEdge>(3);
}
Otri tri = default(Otri), neighbor = default(Otri);
Animation.TriangleNet.Geometry.Vertex org, dest;
int id, nid, count = mesh.triangles.Count;
HalfEdge edge, twin, next;
var edges = dcel.HalfEdges;
// Count half-edges (edge ids).
int k = 0;
// Maps a vertex to its leaving boundary edge.
var boundary = new Dictionary<int, HalfEdge>();
foreach (var t in mesh.triangles)
{
id = t.id;
tri.tri = t;
for (int i = 0; i < 3; i++)
{
tri.orient = i;
tri.Sym(ref neighbor);
nid = neighbor.tri.id;
if (id < nid || nid < 0)
{
face = faces[id];
// Get the endpoints of the current triangle edge.
org = tri.Org();
dest = tri.Dest();
// Create half-edges.
edge = new HalfEdge(vertices[org.id], face);
twin = new HalfEdge(vertices[dest.id], nid < 0 ? Face.Empty : faces[nid]);
map[id].Add(edge);
if (nid >= 0)
{
map[nid].Add(twin);
}
else
{
boundary.Add(dest.id, twin);
}
// Set leaving edges.
edge.origin.leaving = edge;
twin.origin.leaving = twin;
// Set twin edges.
edge.twin = twin;
twin.twin = edge;
edge.id = k++;
twin.id = k++;
edges.Add(edge);
edges.Add(twin);
}
}
}
// Set next pointers for each triangle face.
foreach (var t in map)
{
edge = t[0];
next = t[1];
if (edge.twin.origin.id == next.origin.id)
{
edge.next = next;
next.next = t[2];
t[2].next = edge;
}
else
{
edge.next = t[2];
next.next = edge;
t[2].next = next;
}
}
// Resolve boundary edges.
foreach (var e in boundary.Values)
{
e.next = boundary[e.twin.origin.id];
}
dcel.Vertices.AddRange(vertices);
dcel.Faces.AddRange(faces);
return dcel;
}
#endregion
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 5e1a1f401d8254b9386c06b5f3e122a4
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: a36d23163bf694f5b9953efa040989db
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,37 @@
// -----------------------------------------------------------------------
// <copyright file="BadSubseg.cs" company="">
// Original Triangle code by Jonathan Richard Shewchuk, http://www.cs.cmu.edu/~quake/triangle.html
// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/
// </copyright>
// -----------------------------------------------------------------------
namespace UnityEngine.U2D.Animation.TriangleNet
.Meshing.Data
{
using System;
using Animation.TriangleNet.Geometry;
using Animation.TriangleNet.Topology;
/// <summary>
/// A queue used to store encroached subsegments.
/// </summary>
/// <remarks>
/// Each subsegment's vertices are stored so that we can check whether a
/// subsegment is still the same.
/// </remarks>
class BadSubseg
{
public Osub subseg; // An encroached subsegment.
public Vertex org, dest; // Its two vertices.
public override int GetHashCode()
{
return subseg.seg.hash;
}
public override string ToString()
{
return String.Format("B-SID {0}", subseg.seg.hash);
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: ab0e12b6f8d3e4699af8cc8018397a8d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,195 @@
// -----------------------------------------------------------------------
// <copyright file="BadTriQueue.cs">
// Original Triangle code by Jonathan Richard Shewchuk, http://www.cs.cmu.edu/~quake/triangle.html
// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/
// </copyright>
// -----------------------------------------------------------------------
namespace UnityEngine.U2D.Animation.TriangleNet
.Meshing.Data
{
using Animation.TriangleNet.Geometry;
using Animation.TriangleNet.Topology;
/// <summary>
/// A (priority) queue for bad triangles.
/// </summary>
/// <remarks>
// The queue is actually a set of 4096 queues. I use multiple queues to
// give priority to smaller angles. I originally implemented a heap, but
// the queues are faster by a larger margin than I'd suspected.
/// </remarks>
class BadTriQueue
{
const double SQRT2 = 1.4142135623730950488016887242096980785696718753769480732;
public int Count { get { return this.count; } }
// Variables that maintain the bad triangle queues. The queues are
// ordered from 4095 (highest priority) to 0 (lowest priority).
BadTriangle[] queuefront;
BadTriangle[] queuetail;
int[] nextnonemptyq;
int firstnonemptyq;
int count;
public BadTriQueue()
{
queuefront = new BadTriangle[4096];
queuetail = new BadTriangle[4096];
nextnonemptyq = new int[4096];
firstnonemptyq = -1;
count = 0;
}
/// <summary>
/// Add a bad triangle data structure to the end of a queue.
/// </summary>
/// <param name="badtri">The bad triangle to enqueue.</param>
public void Enqueue(BadTriangle badtri)
{
double length, multiplier;
int exponent, expincrement;
int queuenumber;
int posexponent;
int i;
this.count++;
// Determine the appropriate queue to put the bad triangle into.
// Recall that the key is the square of its shortest edge length.
if (badtri.key >= 1.0)
{
length = badtri.key;
posexponent = 1;
}
else
{
// 'badtri.key' is 2.0 to a negative exponent, so we'll record that
// fact and use the reciprocal of 'badtri.key', which is > 1.0.
length = 1.0 / badtri.key;
posexponent = 0;
}
// 'length' is approximately 2.0 to what exponent? The following code
// determines the answer in time logarithmic in the exponent.
exponent = 0;
while (length > 2.0)
{
// Find an approximation by repeated squaring of two.
expincrement = 1;
multiplier = 0.5;
while (length * multiplier * multiplier > 1.0)
{
expincrement *= 2;
multiplier *= multiplier;
}
// Reduce the value of 'length', then iterate if necessary.
exponent += expincrement;
length *= multiplier;
}
// 'length' is approximately squareroot(2.0) to what exponent?
exponent = 2 * exponent + (length > SQRT2 ? 1 : 0);
// 'exponent' is now in the range 0...2047 for IEEE double precision.
// Choose a queue in the range 0...4095. The shortest edges have the
// highest priority (queue 4095).
if (posexponent > 0)
{
queuenumber = 2047 - exponent;
}
else
{
queuenumber = 2048 + exponent;
}
// Are we inserting into an empty queue?
if (queuefront[queuenumber] == null)
{
// Yes, we are inserting into an empty queue.
// Will this become the highest-priority queue?
if (queuenumber > firstnonemptyq)
{
// Yes, this is the highest-priority queue.
nextnonemptyq[queuenumber] = firstnonemptyq;
firstnonemptyq = queuenumber;
}
else
{
// No, this is not the highest-priority queue.
// Find the queue with next higher priority.
i = queuenumber + 1;
while (queuefront[i] == null)
{
i++;
}
// Mark the newly nonempty queue as following a higher-priority queue.
nextnonemptyq[queuenumber] = nextnonemptyq[i];
nextnonemptyq[i] = queuenumber;
}
// Put the bad triangle at the beginning of the (empty) queue.
queuefront[queuenumber] = badtri;
}
else
{
// Add the bad triangle to the end of an already nonempty queue.
queuetail[queuenumber].next = badtri;
}
// Maintain a pointer to the last triangle of the queue.
queuetail[queuenumber] = badtri;
// Newly enqueued bad triangle has no successor in the queue.
badtri.next = null;
}
/// <summary>
/// Add a bad triangle to the end of a queue.
/// </summary>
/// <param name="enqtri"></param>
/// <param name="minedge"></param>
/// <param name="apex"></param>
/// <param name="org"></param>
/// <param name="dest"></param>
public void Enqueue(ref Otri enqtri, double minedge, Vertex apex, Vertex org, Vertex dest)
{
// Allocate space for the bad triangle.
BadTriangle newbad = new BadTriangle();
newbad.poortri = enqtri;
newbad.key = minedge;
newbad.apex = apex;
newbad.org = org;
newbad.dest = dest;
Enqueue(newbad);
}
/// <summary>
/// Remove a triangle from the front of the queue.
/// </summary>
/// <returns></returns>
public BadTriangle Dequeue()
{
// If no queues are nonempty, return NULL.
if (firstnonemptyq < 0)
{
return null;
}
this.count--;
// Find the first triangle of the highest-priority queue.
BadTriangle result = queuefront[firstnonemptyq];
// Remove the triangle from the queue.
queuefront[firstnonemptyq] = result.next;
// If this queue is now empty, note the new highest-priority
// nonempty queue.
if (result == queuetail[firstnonemptyq])
{
firstnonemptyq = nextnonemptyq[firstnonemptyq];
}
return result;
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 427677fae20f0460e9d05b080a3d93ad
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,35 @@
// -----------------------------------------------------------------------
// <copyright file="BadTriangle.cs" company="">
// Original Triangle code by Jonathan Richard Shewchuk, http://www.cs.cmu.edu/~quake/triangle.html
// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/
// </copyright>
// -----------------------------------------------------------------------
namespace UnityEngine.U2D.Animation.TriangleNet
.Meshing.Data
{
using System;
using Animation.TriangleNet.Geometry;
using Animation.TriangleNet.Topology;
/// <summary>
/// A queue used to store bad triangles.
/// </summary>
/// <remarks>
/// The key is the square of the cosine of the smallest angle of the triangle.
/// Each triangle's vertices are stored so that one can check whether a
/// triangle is still the same.
/// </remarks>
class BadTriangle
{
public Otri poortri; // A skinny or too-large triangle.
public double key; // cos^2 of smallest (apical) angle.
public Vertex org, dest, apex; // Its three vertices.
public BadTriangle next; // Pointer to next bad triangle.
public override string ToString()
{
return String.Format("B-TID {0}", poortri.tri.hash);
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 99e6114d2bbce43ca94c406f19ba2eaf
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,234 @@
// -----------------------------------------------------------------------
// <copyright file="GenericMesher.cs">
// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/
// </copyright>
// -----------------------------------------------------------------------
namespace UnityEngine.U2D.Animation.TriangleNet
.Meshing
{
using System;
using System.Collections.Generic;
using Animation.TriangleNet.Geometry;
using Animation.TriangleNet.IO;
using Animation.TriangleNet.Meshing.Algorithm;
/// <summary>
/// Create meshes of point sets or polygons.
/// </summary>
internal class GenericMesher
{
Configuration config;
ITriangulator triangulator;
public GenericMesher()
: this(new Dwyer(), new Configuration())
{
}
public GenericMesher(ITriangulator triangulator)
: this(triangulator, new Configuration())
{
}
public GenericMesher(Configuration config)
: this(new Dwyer(), config)
{
}
public GenericMesher(ITriangulator triangulator, Configuration config)
{
this.config = config;
this.triangulator = triangulator;
}
/// <inheritdoc />
public IMesh Triangulate(IList<Vertex> points)
{
return triangulator.Triangulate(points, config);
}
/// <inheritdoc />
public IMesh Triangulate(IPolygon polygon)
{
return Triangulate(polygon, null, null);
}
/// <inheritdoc />
public IMesh Triangulate(IPolygon polygon, ConstraintOptions options)
{
return Triangulate(polygon, options, null);
}
/// <inheritdoc />
public IMesh Triangulate(IPolygon polygon, QualityOptions quality)
{
return Triangulate(polygon, null, quality);
}
/// <inheritdoc />
public IMesh Triangulate(IPolygon polygon, ConstraintOptions options, QualityOptions quality)
{
var mesh = (Mesh)triangulator.Triangulate(polygon.Points, config);
var cmesher = new ConstraintMesher(mesh, config);
var qmesher = new QualityMesher(mesh, config);
mesh.SetQualityMesher(qmesher);
// Insert segments.
cmesher.Apply(polygon, options);
// Refine mesh.
qmesher.Apply(quality);
return mesh;
}
/// <summary>
/// Generates a structured mesh with bounds [0, 0, width, height].
/// </summary>
/// <param name="width">Width of the mesh (must be > 0).</param>
/// <param name="height">Height of the mesh (must be > 0).</param>
/// <param name="nx">Number of segments in x direction.</param>
/// <param name="ny">Number of segments in y direction.</param>
/// <returns>Mesh</returns>
internal static IMesh StructuredMesh(double width, double height, int nx, int ny)
{
if (width <= 0.0)
{
throw new ArgumentException("width");
}
if (height <= 0.0)
{
throw new ArgumentException("height");
}
return StructuredMesh(new Rectangle(0.0, 0.0, width, height), nx, ny);
}
/// <summary>
/// Generates a structured mesh.
/// </summary>
/// <param name="bounds">Bounds of the mesh.</param>
/// <param name="nx">Number of segments in x direction.</param>
/// <param name="ny">Number of segments in y direction.</param>
/// <returns>Mesh</returns>
internal static IMesh StructuredMesh(Rectangle bounds, int nx, int ny)
{
var polygon = new Polygon((nx + 1) * (ny + 1));
double x, y, dx, dy, left, bottom;
dx = bounds.Width / nx;
dy = bounds.Height / ny;
left = bounds.Left;
bottom = bounds.Bottom;
int i, j, k, l, n = 0;
// Add vertices.
var points = new Vertex[(nx + 1) * (ny + 1)];
for (i = 0; i <= nx; i++)
{
x = left + i * dx;
for (j = 0; j <= ny; j++)
{
y = bottom + j * dy;
points[n++] = new Vertex(x, y);
}
}
polygon.Points.AddRange(points);
n = 0;
// Set vertex hash and id.
foreach (var v in points)
{
v.hash = v.id = n++;
}
// Add boundary segments.
var segments = polygon.Segments;
segments.Capacity = 2 * (nx + ny);
Vertex a, b;
for (j = 0; j < ny; j++)
{
// Left
a = points[j];
b = points[j + 1];
segments.Add(new Segment(a, b, 1));
a.Label = b.Label = 1;
// Right
a = points[nx * (ny + 1) + j];
b = points[nx * (ny + 1) + (j + 1)];
segments.Add(new Segment(a, b, 1));
a.Label = b.Label = 1;
}
for (i = 0; i < nx; i++)
{
// Bottom
a = points[(ny + 1) * i];
b = points[(ny + 1) * (i + 1)];
segments.Add(new Segment(a, b, 1));
a.Label = b.Label = 1;
// Top
a = points[ny + (ny + 1) * i];
b = points[ny + (ny + 1) * (i + 1)];
segments.Add(new Segment(a, b, 1));
a.Label = b.Label = 1;
}
// Add triangles.
var triangles = new InputTriangle[2 * nx * ny];
n = 0;
for (i = 0; i < nx; i++)
{
for (j = 0; j < ny; j++)
{
k = j + (ny + 1) * i;
l = j + (ny + 1) * (i + 1);
// Create 2 triangles in rectangle [k, l, l + 1, k + 1].
if ((i + j) % 2 == 0)
{
// Diagonal from bottom left to top right.
triangles[n++] = new InputTriangle(k, l, l + 1);
triangles[n++] = new InputTriangle(k, l + 1, k + 1);
}
else
{
// Diagonal from top left to bottom right.
triangles[n++] = new InputTriangle(k, l, k + 1);
triangles[n++] = new InputTriangle(l, l + 1, k + 1);
}
}
}
return Converter.ToMesh(polygon, triangles);
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 4da748bcb2b2d41bbad9ec2561d1d2e9
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,26 @@
namespace UnityEngine.U2D.Animation.TriangleNet
.Meshing
{
using Animation.TriangleNet.Geometry;
/// <summary>
/// Interface for polygon triangulation.
/// </summary>
internal interface IConstraintMesher
{
/// <summary>
/// Triangulates a polygon.
/// </summary>
/// <param name="polygon">The polygon.</param>
/// <returns>Mesh</returns>
IMesh Triangulate(IPolygon polygon);
/// <summary>
/// Triangulates a polygon, applying constraint options.
/// </summary>
/// <param name="polygon">The polygon.</param>
/// <param name="options">Constraint options.</param>
/// <returns>Mesh</returns>
IMesh Triangulate(IPolygon polygon, ConstraintOptions options);
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 4cd626f08811c47b092c491b658d4ef1
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,57 @@
namespace UnityEngine.U2D.Animation.TriangleNet
.Meshing
{
using System.Collections.Generic;
using Animation.TriangleNet.Topology;
using Animation.TriangleNet.Geometry;
/// <summary>
/// Mesh interface.
/// </summary>
internal interface IMesh
{
/// <summary>
/// Gets the vertices of the mesh.
/// </summary>
ICollection<Vertex> Vertices { get; }
/// <summary>
/// Gets the edges of the mesh.
/// </summary>
IEnumerable<Edge> Edges { get; }
/// <summary>
/// Gets the segments (constraint edges) of the mesh.
/// </summary>
ICollection<SubSegment> Segments { get; }
/// <summary>
/// Gets the triangles of the mesh.
/// </summary>
ICollection<Triangle> Triangles { get; }
/// <summary>
/// Gets the holes of the mesh.
/// </summary>
IList<Point> Holes { get; }
/// <summary>
/// Gets the bounds of the mesh.
/// </summary>
Rectangle Bounds { get; }
/// <summary>
/// Renumber mesh vertices and triangles.
/// </summary>
void Renumber();
/// <summary>
/// Refine the mesh.
/// </summary>
/// <param name="quality">The quality constraints.</param>
/// <param name="conforming">
/// A value indicating, if the refined mesh should be Conforming Delaunay.
/// </param>
void Refine(QualityOptions quality, bool delaunay);
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: afa9255f65ec947029394580abdc1070
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,28 @@
namespace UnityEngine.U2D.Animation.TriangleNet
.Meshing
{
using Animation.TriangleNet.Geometry;
/// <summary>
/// Interface for polygon triangulation with quality constraints.
/// </summary>
internal interface IQualityMesher
{
/// <summary>
/// Triangulates a polygon, applying quality options.
/// </summary>
/// <param name="polygon">The polygon.</param>
/// <param name="quality">Quality options.</param>
/// <returns>Mesh</returns>
IMesh Triangulate(IPolygon polygon, QualityOptions quality);
/// <summary>
/// Triangulates a polygon, applying quality and constraint options.
/// </summary>
/// <param name="polygon">The polygon.</param>
/// <param name="options">Constraint options.</param>
/// <param name="quality">Quality options.</param>
/// <returns>Mesh</returns>
IMesh Triangulate(IPolygon polygon, ConstraintOptions options, QualityOptions quality);
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 6f606e975498d44149054ec8fedf092e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,26 @@
// -----------------------------------------------------------------------
// <copyright file="ITriangulator.cs" company="">
// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/
// </copyright>
// -----------------------------------------------------------------------
namespace UnityEngine.U2D.Animation.TriangleNet
.Meshing
{
using System.Collections.Generic;
using Animation.TriangleNet.Geometry;
/// <summary>
/// Interface for point set triangulation.
/// </summary>
internal interface ITriangulator
{
/// <summary>
/// Triangulates a point set.
/// </summary>
/// <param name="points">Collection of points.</param>
/// <param name="config"></param>
/// <returns>Mesh</returns>
IMesh Triangulate(IList<Vertex> points, Configuration config);
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 22395c0668d29483bbb5685bf1385ecb
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 7c5150d54e763414287e3bea1fbda552
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,102 @@
// -----------------------------------------------------------------------
// <copyright file="EdgeEnumerator.cs" company="">
// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/
// </copyright>
// -----------------------------------------------------------------------
namespace UnityEngine.U2D.Animation.TriangleNet
.Meshing.Iterators
{
using System.Collections.Generic;
using Animation.TriangleNet.Topology;
using Animation.TriangleNet.Geometry;
/// <summary>
/// Enumerates the edges of a triangulation.
/// </summary>
internal class EdgeIterator : IEnumerator<Edge>
{
IEnumerator<Triangle> triangles;
Otri tri = default(Otri);
Otri neighbor = default(Otri);
Osub sub = default(Osub);
Edge current;
Vertex p1, p2;
/// <summary>
/// Initializes a new instance of the <see cref="EdgeIterator" /> class.
/// </summary>
public EdgeIterator(Mesh mesh)
{
triangles = mesh.triangles.GetEnumerator();
triangles.MoveNext();
tri.tri = triangles.Current;
tri.orient = 0;
}
public Edge Current
{
get { return current; }
}
public void Dispose()
{
this.triangles.Dispose();
}
object System.Collections.IEnumerator.Current
{
get { return current; }
}
public bool MoveNext()
{
if (tri.tri == null)
{
return false;
}
current = null;
while (current == null)
{
if (tri.orient == 3)
{
if (triangles.MoveNext())
{
tri.tri = triangles.Current;
tri.orient = 0;
}
else
{
// Finally no more triangles
return false;
}
}
tri.Sym(ref neighbor);
if ((tri.tri.id < neighbor.tri.id) || (neighbor.tri.id == Mesh.DUMMY))
{
p1 = tri.Org();
p2 = tri.Dest();
tri.Pivot(ref sub);
// Boundary mark of dummysub is 0, so we don't need to worry about that.
current = new Edge(p1.id, p2.id, sub.seg.boundary);
}
tri.orient++;
}
return true;
}
public void Reset()
{
this.triangles.Reset();
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 43e16ced10076476cafe049a9a276377
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,123 @@
// -----------------------------------------------------------------------
// <copyright file="RegionIterator.cs" company="">
// Original Matlab code by John Burkardt, Florida State University
// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/
// </copyright>
// -----------------------------------------------------------------------
namespace UnityEngine.U2D.Animation.TriangleNet
.Meshing.Iterators
{
using System;
using System.Collections.Generic;
using Animation.TriangleNet.Topology;
/// <summary>
/// Iterates the region a given triangle belongs to and applies an action
/// to each connected trianlge in that region.
/// </summary>
/// <remarks>
/// The default action is to set the region id and area constraint.
/// </remarks>
internal class RegionIterator
{
List<Triangle> region;
public RegionIterator(Mesh mesh)
{
this.region = new List<Triangle>();
}
/// <summary>
/// Set the region attribute of all trianlges connected to given triangle.
/// </summary>
/// <param name="triangle">The triangle seed.</param>
/// <param name="boundary">If non-zero, process all triangles of the
/// region that is enclosed by segments with given boundary label.</param>
public void Process(Triangle triangle, int boundary = 0)
{
this.Process(triangle, (tri) =>
{
// Set the region id and area constraint.
tri.label = triangle.label;
tri.area = triangle.area;
}, boundary);
}
/// <summary>
/// Process all trianlges connected to given triangle and apply given action.
/// </summary>
/// <param name="triangle">The seeding triangle.</param>
/// <param name="action">The action to apply to each triangle.</param>
/// <param name="boundary">If non-zero, process all triangles of the
/// region that is enclosed by segments with given boundary label.</param>
public void Process(Triangle triangle, Action<Triangle> action, int boundary = 0)
{
// Make sure the triangle under consideration still exists.
// It may have been eaten by the virus.
if (triangle.id == Mesh.DUMMY || Otri.IsDead(triangle))
{
return;
}
// Add the seeding triangle to the region.
region.Add(triangle);
triangle.infected = true;
if (boundary == 0)
{
// Stop at any subsegment.
ProcessRegion(action, seg => seg.hash == Mesh.DUMMY);
}
else
{
// Stop at segments that have the given boundary label.
ProcessRegion(action, seg => seg.boundary != boundary);
}
// Free up memory (virus pool should be empty anyway).
region.Clear();
}
/// <summary>
/// Apply given action to each triangle of selected region.
/// </summary>
/// <param name="action"></param>
/// <param name="protector"></param>
void ProcessRegion(Action<Triangle> action, Func<SubSegment, bool> protector)
{
Otri testtri = default(Otri);
Otri neighbor = default(Otri);
Osub neighborsubseg = default(Osub);
// Loop through all the infected triangles, spreading the attribute
// and/or area constraint to their neighbors, then to their neighbors'
// neighbors.
for (int i = 0; i < region.Count; i++)
{
// WARNING: Don't use foreach, viri list gets modified.
testtri.tri = region[i];
// Apply function.
action(testtri.tri);
// Check each of the triangle's three neighbors.
for (testtri.orient = 0; testtri.orient < 3; testtri.orient++)
{
// Find the neighbor.
testtri.Sym(ref neighbor);
// Check for a subsegment between the triangle and its neighbor.
testtri.Pivot(ref neighborsubseg);
// Make sure the neighbor exists, is not already infected, and
// isn't protected by a subsegment.
if ((neighbor.tri.id != Mesh.DUMMY) && !neighbor.IsInfected()
&& protector(neighborsubseg.seg))
{
// Infect the neighbor.
neighbor.Infect();
// Ensure that the neighbor's neighbors will be infected.
region.Add(neighbor.tri);
}
}
}
// Uninfect all triangles.
foreach (var virus in region)
{
virus.infected = false;
}
// Empty the virus pool.
region.Clear();
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: a82556ec642d74b2f83095af97dce435
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,899 @@
// -----------------------------------------------------------------------
// <copyright file="QualityMesher.cs">
// Original Triangle code by Jonathan Richard Shewchuk, http://www.cs.cmu.edu/~quake/triangle.html
// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/
// </copyright>
// -----------------------------------------------------------------------
namespace UnityEngine.U2D.Animation.TriangleNet
.Meshing
{
using System;
using System.Collections.Generic;
using Animation.TriangleNet.Geometry;
using Animation.TriangleNet.Logging;
using Animation.TriangleNet.Meshing.Data;
using Animation.TriangleNet.Tools;
using Animation.TriangleNet.Topology;
/// <summary>
/// Provides methods for mesh quality enforcement and testing.
/// </summary>
class QualityMesher
{
IPredicates predicates;
Queue<BadSubseg> badsubsegs;
BadTriQueue queue;
Mesh mesh;
Behavior behavior;
NewLocation newLocation;
ILog<LogItem> logger;
// Stores the vertices of the triangle that contains newvertex
// in SplitTriangle method.
Triangle newvertex_tri;
public QualityMesher(Mesh mesh, Configuration config)
{
logger = Log.Instance;
badsubsegs = new Queue<BadSubseg>();
queue = new BadTriQueue();
this.mesh = mesh;
this.predicates = config.Predicates();
this.behavior = mesh.behavior;
newLocation = new NewLocation(mesh, predicates);
newvertex_tri = new Triangle();
}
/// <summary>
/// Apply quality constraints to a mesh.
/// </summary>
/// <param name="quality">The quality constraints.</param>
/// <param name="delaunay">A value indicating, if the refined mesh should be Conforming Delaunay.</param>
public void Apply(QualityOptions quality, bool delaunay = false)
{
// Copy quality options
if (quality != null)
{
behavior.Quality = true;
behavior.MinAngle = quality.MinimumAngle;
behavior.MaxAngle = quality.MaximumAngle;
behavior.MaxArea = quality.MaximumArea;
behavior.UserTest = quality.UserTest;
behavior.VarArea = quality.VariableArea;
behavior.ConformingDelaunay = behavior.ConformingDelaunay || delaunay;
mesh.steinerleft = quality.SteinerPoints == 0 ? -1 : quality.SteinerPoints;
}
// TODO: remove
if (!behavior.Poly)
{
// Be careful not to allocate space for element area constraints that
// will never be assigned any value (other than the default -1.0).
behavior.VarArea = false;
}
// Ensure that no vertex can be mistaken for a triangular bounding
// box vertex in insertvertex().
mesh.infvertex1 = null;
mesh.infvertex2 = null;
mesh.infvertex3 = null;
if (behavior.useSegments)
{
mesh.checksegments = true;
}
if (behavior.Quality && mesh.triangles.Count > 0)
{
// Enforce angle and area constraints.
EnforceQuality();
}
}
/// <summary>
/// Add a bad subsegment to the queue.
/// </summary>
/// <param name="badseg">Bad subsegment.</param>
public void AddBadSubseg(BadSubseg badseg)
{
badsubsegs.Enqueue(badseg);
}
#region Check
/// <summary>
/// Check a subsegment to see if it is encroached; add it to the list if it is.
/// </summary>
/// <param name="testsubseg">The subsegment to check.</param>
/// <returns>Returns a nonzero value if the subsegment is encroached.</returns>
/// <remarks>
/// A subsegment is encroached if there is a vertex in its diametral lens.
/// For Ruppert's algorithm (-D switch), the "diametral lens" is the
/// diametral circle. For Chew's algorithm (default), the diametral lens is
/// just big enough to enclose two isosceles triangles whose bases are the
/// subsegment. Each of the two isosceles triangles has two angles equal
/// to 'b.minangle'.
///
/// Chew's algorithm does not require diametral lenses at all--but they save
/// time. Any vertex inside a subsegment's diametral lens implies that the
/// triangle adjoining the subsegment will be too skinny, so it's only a
/// matter of time before the encroaching vertex is deleted by Chew's
/// algorithm. It's faster to simply not insert the doomed vertex in the
/// first place, which is why I use diametral lenses with Chew's algorithm.
/// </remarks>
public int CheckSeg4Encroach(ref Osub testsubseg)
{
Otri neighbortri = default(Otri);
Osub testsym = default(Osub);
BadSubseg encroachedseg;
double dotproduct;
int encroached;
int sides;
Vertex eorg, edest, eapex;
encroached = 0;
sides = 0;
eorg = testsubseg.Org();
edest = testsubseg.Dest();
// Check one neighbor of the subsegment.
testsubseg.Pivot(ref neighbortri);
// Does the neighbor exist, or is this a boundary edge?
if (neighbortri.tri.id != Mesh.DUMMY)
{
sides++;
// Find a vertex opposite this subsegment.
eapex = neighbortri.Apex();
// Check whether the apex is in the diametral lens of the subsegment
// (the diametral circle if 'conformdel' is set). A dot product
// of two sides of the triangle is used to check whether the angle
// at the apex is greater than (180 - 2 'minangle') degrees (for
// lenses; 90 degrees for diametral circles).
dotproduct = (eorg.x - eapex.x) * (edest.x - eapex.x) +
(eorg.y - eapex.y) * (edest.y - eapex.y);
if (dotproduct < 0.0)
{
if (behavior.ConformingDelaunay ||
(dotproduct * dotproduct >=
(2.0 * behavior.goodAngle - 1.0) * (2.0 * behavior.goodAngle - 1.0) *
((eorg.x - eapex.x) * (eorg.x - eapex.x) +
(eorg.y - eapex.y) * (eorg.y - eapex.y)) *
((edest.x - eapex.x) * (edest.x - eapex.x) +
(edest.y - eapex.y) * (edest.y - eapex.y))))
{
encroached = 1;
}
}
}
// Check the other neighbor of the subsegment.
testsubseg.Sym(ref testsym);
testsym.Pivot(ref neighbortri);
// Does the neighbor exist, or is this a boundary edge?
if (neighbortri.tri.id != Mesh.DUMMY)
{
sides++;
// Find the other vertex opposite this subsegment.
eapex = neighbortri.Apex();
// Check whether the apex is in the diametral lens of the subsegment
// (or the diametral circle, if 'conformdel' is set).
dotproduct = (eorg.x - eapex.x) * (edest.x - eapex.x) +
(eorg.y - eapex.y) * (edest.y - eapex.y);
if (dotproduct < 0.0)
{
if (behavior.ConformingDelaunay ||
(dotproduct * dotproduct >=
(2.0 * behavior.goodAngle - 1.0) * (2.0 * behavior.goodAngle - 1.0) *
((eorg.x - eapex.x) * (eorg.x - eapex.x) +
(eorg.y - eapex.y) * (eorg.y - eapex.y)) *
((edest.x - eapex.x) * (edest.x - eapex.x) +
(edest.y - eapex.y) * (edest.y - eapex.y))))
{
encroached += 2;
}
}
}
if (encroached > 0 && (behavior.NoBisect == 0 || ((behavior.NoBisect == 1) && (sides == 2))))
{
// Add the subsegment to the list of encroached subsegments.
// Be sure to get the orientation right.
encroachedseg = new BadSubseg();
if (encroached == 1)
{
encroachedseg.subseg = testsubseg;
encroachedseg.org = eorg;
encroachedseg.dest = edest;
}
else
{
encroachedseg.subseg = testsym;
encroachedseg.org = edest;
encroachedseg.dest = eorg;
}
badsubsegs.Enqueue(encroachedseg);
}
return encroached;
}
/// <summary>
/// Test a triangle for quality and size.
/// </summary>
/// <param name="testtri">Triangle to check.</param>
/// <remarks>
/// Tests a triangle to see if it satisfies the minimum angle condition and
/// the maximum area condition. Triangles that aren't up to spec are added
/// to the bad triangle queue.
/// </remarks>
public void TestTriangle(ref Otri testtri)
{
Otri tri1 = default(Otri), tri2 = default(Otri);
Osub testsub = default(Osub);
Vertex torg, tdest, tapex;
Vertex base1, base2;
Vertex org1, dest1, org2, dest2;
Vertex joinvertex;
double dxod, dyod, dxda, dyda, dxao, dyao;
double dxod2, dyod2, dxda2, dyda2, dxao2, dyao2;
double apexlen, orglen, destlen, minedge;
double angle;
double area;
double dist1, dist2;
double maxangle;
torg = testtri.Org();
tdest = testtri.Dest();
tapex = testtri.Apex();
dxod = torg.x - tdest.x;
dyod = torg.y - tdest.y;
dxda = tdest.x - tapex.x;
dyda = tdest.y - tapex.y;
dxao = tapex.x - torg.x;
dyao = tapex.y - torg.y;
dxod2 = dxod * dxod;
dyod2 = dyod * dyod;
dxda2 = dxda * dxda;
dyda2 = dyda * dyda;
dxao2 = dxao * dxao;
dyao2 = dyao * dyao;
// Find the lengths of the triangle's three edges.
apexlen = dxod2 + dyod2;
orglen = dxda2 + dyda2;
destlen = dxao2 + dyao2;
if ((apexlen < orglen) && (apexlen < destlen))
{
// The edge opposite the apex is shortest.
minedge = apexlen;
// Find the square of the cosine of the angle at the apex.
angle = dxda * dxao + dyda * dyao;
angle = angle * angle / (orglen * destlen);
base1 = torg;
base2 = tdest;
testtri.Copy(ref tri1);
}
else if (orglen < destlen)
{
// The edge opposite the origin is shortest.
minedge = orglen;
// Find the square of the cosine of the angle at the origin.
angle = dxod * dxao + dyod * dyao;
angle = angle * angle / (apexlen * destlen);
base1 = tdest;
base2 = tapex;
testtri.Lnext(ref tri1);
}
else
{
// The edge opposite the destination is shortest.
minedge = destlen;
// Find the square of the cosine of the angle at the destination.
angle = dxod * dxda + dyod * dyda;
angle = angle * angle / (apexlen * orglen);
base1 = tapex;
base2 = torg;
testtri.Lprev(ref tri1);
}
if (behavior.VarArea || behavior.fixedArea || (behavior.UserTest != null))
{
// Check whether the area is larger than permitted.
area = 0.5 * (dxod * dyda - dyod * dxda);
if (behavior.fixedArea && (area > behavior.MaxArea))
{
// Add this triangle to the list of bad triangles.
queue.Enqueue(ref testtri, minedge, tapex, torg, tdest);
return;
}
// Nonpositive area constraints are treated as unconstrained.
if ((behavior.VarArea) && (area > testtri.tri.area) && (testtri.tri.area > 0.0))
{
// Add this triangle to the list of bad triangles.
queue.Enqueue(ref testtri, minedge, tapex, torg, tdest);
return;
}
// Check whether the user thinks this triangle is too large.
if ((behavior.UserTest != null) && behavior.UserTest(testtri.tri, area))
{
queue.Enqueue(ref testtri, minedge, tapex, torg, tdest);
return;
}
}
// find the maximum edge and accordingly the pqr orientation
if ((apexlen > orglen) && (apexlen > destlen))
{
// The edge opposite the apex is longest.
// maxedge = apexlen;
// Find the cosine of the angle at the apex.
maxangle = (orglen + destlen - apexlen) / (2 * Math.Sqrt(orglen * destlen));
}
else if (orglen > destlen)
{
// The edge opposite the origin is longest.
// maxedge = orglen;
// Find the cosine of the angle at the origin.
maxangle = (apexlen + destlen - orglen) / (2 * Math.Sqrt(apexlen * destlen));
}
else
{
// The edge opposite the destination is longest.
// maxedge = destlen;
// Find the cosine of the angle at the destination.
maxangle = (apexlen + orglen - destlen) / (2 * Math.Sqrt(apexlen * orglen));
}
// Check whether the angle is smaller than permitted.
if ((angle > behavior.goodAngle) || (maxangle < behavior.maxGoodAngle && behavior.MaxAngle != 0.0))
{
// Use the rules of Miller, Pav, and Walkington to decide that certain
// triangles should not be split, even if they have bad angles.
// A skinny triangle is not split if its shortest edge subtends a
// small input angle, and both endpoints of the edge lie on a
// concentric circular shell. For convenience, I make a small
// adjustment to that rule: I check if the endpoints of the edge
// both lie in segment interiors, equidistant from the apex where
// the two segments meet.
// First, check if both points lie in segment interiors.
if ((base1.type == VertexType.SegmentVertex) &&
(base2.type == VertexType.SegmentVertex))
{
// Check if both points lie in a common segment. If they do, the
// skinny triangle is enqueued to be split as usual.
tri1.Pivot(ref testsub);
if (testsub.seg.hash == Mesh.DUMMY)
{
// No common segment. Find a subsegment that contains 'torg'.
tri1.Copy(ref tri2);
do
{
tri1.Oprev();
tri1.Pivot(ref testsub);
}
while (testsub.seg.hash == Mesh.DUMMY);
// Find the endpoints of the containing segment.
org1 = testsub.SegOrg();
dest1 = testsub.SegDest();
// Find a subsegment that contains 'tdest'.
do
{
tri2.Dnext();
tri2.Pivot(ref testsub);
}
while (testsub.seg.hash == Mesh.DUMMY);
// Find the endpoints of the containing segment.
org2 = testsub.SegOrg();
dest2 = testsub.SegDest();
// Check if the two containing segments have an endpoint in common.
joinvertex = null;
if ((dest1.x == org2.x) && (dest1.y == org2.y))
{
joinvertex = dest1;
}
else if ((org1.x == dest2.x) && (org1.y == dest2.y))
{
joinvertex = org1;
}
if (joinvertex != null)
{
// Compute the distance from the common endpoint (of the two
// segments) to each of the endpoints of the shortest edge.
dist1 = ((base1.x - joinvertex.x) * (base1.x - joinvertex.x) +
(base1.y - joinvertex.y) * (base1.y - joinvertex.y));
dist2 = ((base2.x - joinvertex.x) * (base2.x - joinvertex.x) +
(base2.y - joinvertex.y) * (base2.y - joinvertex.y));
// If the two distances are equal, don't split the triangle.
if ((dist1 < 1.001 * dist2) && (dist1 > 0.999 * dist2))
{
// Return now to avoid enqueueing the bad triangle.
return;
}
}
}
}
// Add this triangle to the list of bad triangles.
queue.Enqueue(ref testtri, minedge, tapex, torg, tdest);
}
}
#endregion
#region Maintanance
/// <summary>
/// Traverse the entire list of subsegments, and check each to see if it
/// is encroached. If so, add it to the list.
/// </summary>
private void TallyEncs()
{
Osub subsegloop = default(Osub);
subsegloop.orient = 0;
foreach (var seg in mesh.subsegs.Values)
{
subsegloop.seg = seg;
// If the segment is encroached, add it to the list.
CheckSeg4Encroach(ref subsegloop);
}
}
/// <summary>
/// Split all the encroached subsegments.
/// </summary>
/// <param name="triflaws">A flag that specifies whether one should take
/// note of new bad triangles that result from inserting vertices to repair
/// encroached subsegments.</param>
/// <remarks>
/// Each encroached subsegment is repaired by splitting it - inserting a
/// vertex at or near its midpoint. Newly inserted vertices may encroach
/// upon other subsegments; these are also repaired.
/// </remarks>
private void SplitEncSegs(bool triflaws)
{
Otri enctri = default(Otri);
Otri testtri = default(Otri);
Osub testsh = default(Osub);
Osub currentenc = default(Osub);
BadSubseg seg;
Vertex eorg, edest, eapex;
Vertex newvertex;
InsertVertexResult success;
double segmentlength, nearestpoweroftwo;
double split;
double multiplier, divisor;
bool acuteorg, acuteorg2, acutedest, acutedest2;
// Note that steinerleft == -1 if an unlimited number
// of Steiner points is allowed.
while (badsubsegs.Count > 0)
{
if (mesh.steinerleft == 0)
{
break;
}
seg = badsubsegs.Dequeue();
currentenc = seg.subseg;
eorg = currentenc.Org();
edest = currentenc.Dest();
// Make sure that this segment is still the same segment it was
// when it was determined to be encroached. If the segment was
// enqueued multiple times (because several newly inserted
// vertices encroached it), it may have already been split.
if (!Osub.IsDead(currentenc.seg) && (eorg == seg.org) && (edest == seg.dest))
{
// To decide where to split a segment, we need to know if the
// segment shares an endpoint with an adjacent segment.
// The concern is that, if we simply split every encroached
// segment in its center, two adjacent segments with a small
// angle between them might lead to an infinite loop; each
// vertex added to split one segment will encroach upon the
// other segment, which must then be split with a vertex that
// will encroach upon the first segment, and so on forever.
// To avoid this, imagine a set of concentric circles, whose
// radii are powers of two, about each segment endpoint.
// These concentric circles determine where the segment is
// split. (If both endpoints are shared with adjacent
// segments, split the segment in the middle, and apply the
// concentric circles for later splittings.)
// Is the origin shared with another segment?
currentenc.Pivot(ref enctri);
enctri.Lnext(ref testtri);
testtri.Pivot(ref testsh);
acuteorg = testsh.seg.hash != Mesh.DUMMY;
// Is the destination shared with another segment?
testtri.Lnext();
testtri.Pivot(ref testsh);
acutedest = testsh.seg.hash != Mesh.DUMMY;
// If we're using Chew's algorithm (rather than Ruppert's)
// to define encroachment, delete free vertices from the
// subsegment's diametral circle.
if (!behavior.ConformingDelaunay && !acuteorg && !acutedest)
{
eapex = enctri.Apex();
while ((eapex.type == VertexType.FreeVertex) &&
((eorg.x - eapex.x) * (edest.x - eapex.x) +
(eorg.y - eapex.y) * (edest.y - eapex.y) < 0.0))
{
mesh.DeleteVertex(ref testtri);
currentenc.Pivot(ref enctri);
eapex = enctri.Apex();
enctri.Lprev(ref testtri);
}
}
// Now, check the other side of the segment, if there's a triangle there.
enctri.Sym(ref testtri);
if (testtri.tri.id != Mesh.DUMMY)
{
// Is the destination shared with another segment?
testtri.Lnext();
testtri.Pivot(ref testsh);
acutedest2 = testsh.seg.hash != Mesh.DUMMY;
acutedest = acutedest || acutedest2;
// Is the origin shared with another segment?
testtri.Lnext();
testtri.Pivot(ref testsh);
acuteorg2 = testsh.seg.hash != Mesh.DUMMY;
acuteorg = acuteorg || acuteorg2;
// Delete free vertices from the subsegment's diametral circle.
if (!behavior.ConformingDelaunay && !acuteorg2 && !acutedest2)
{
eapex = testtri.Org();
while ((eapex.type == VertexType.FreeVertex) &&
((eorg.x - eapex.x) * (edest.x - eapex.x) +
(eorg.y - eapex.y) * (edest.y - eapex.y) < 0.0))
{
mesh.DeleteVertex(ref testtri);
enctri.Sym(ref testtri);
eapex = testtri.Apex();
testtri.Lprev();
}
}
}
// Use the concentric circles if exactly one endpoint is shared
// with another adjacent segment.
if (acuteorg || acutedest)
{
segmentlength = Math.Sqrt((edest.x - eorg.x) * (edest.x - eorg.x) +
(edest.y - eorg.y) * (edest.y - eorg.y));
// Find the power of two that most evenly splits the segment.
// The worst case is a 2:1 ratio between subsegment lengths.
nearestpoweroftwo = 1.0;
while (segmentlength > 3.0 * nearestpoweroftwo)
{
nearestpoweroftwo *= 2.0;
}
while (segmentlength < 1.5 * nearestpoweroftwo)
{
nearestpoweroftwo *= 0.5;
}
// Where do we split the segment?
split = nearestpoweroftwo / segmentlength;
if (acutedest)
{
split = 1.0 - split;
}
}
else
{
// If we're not worried about adjacent segments, split
// this segment in the middle.
split = 0.5;
}
// Create the new vertex (interpolate coordinates).
newvertex = new Vertex(
eorg.x + split * (edest.x - eorg.x),
eorg.y + split * (edest.y - eorg.y),
currentenc.seg.boundary
#if USE_ATTRIBS
, mesh.nextras
#endif
);
newvertex.type = VertexType.SegmentVertex;
newvertex.hash = mesh.hash_vtx++;
newvertex.id = newvertex.hash;
mesh.vertices.Add(newvertex.hash, newvertex);
#if USE_ATTRIBS
// Interpolate attributes.
for (int i = 0; i < mesh.nextras; i++)
{
newvertex.attributes[i] = eorg.attributes[i]
+ split * (edest.attributes[i] - eorg.attributes[i]);
}
#endif
#if USE_Z
newvertex.z = eorg.z + split * (edest.z - eorg.z);
#endif
if (!Behavior.NoExact)
{
// Roundoff in the above calculation may yield a 'newvertex'
// that is not precisely collinear with 'eorg' and 'edest'.
// Improve collinearity by one step of iterative refinement.
multiplier = predicates.CounterClockwise(eorg, edest, newvertex);
divisor = ((eorg.x - edest.x) * (eorg.x - edest.x) +
(eorg.y - edest.y) * (eorg.y - edest.y));
if ((multiplier != 0.0) && (divisor != 0.0))
{
multiplier = multiplier / divisor;
// Watch out for NANs.
if (!double.IsNaN(multiplier))
{
newvertex.x += multiplier * (edest.y - eorg.y);
newvertex.y += multiplier * (eorg.x - edest.x);
}
}
}
// Check whether the new vertex lies on an endpoint.
if (((newvertex.x == eorg.x) && (newvertex.y == eorg.y)) ||
((newvertex.x == edest.x) && (newvertex.y == edest.y)))
{
logger.Error("Ran out of precision: I attempted to split a"
+ " segment to a smaller size than can be accommodated by"
+ " the finite precision of floating point arithmetic.",
"Quality.SplitEncSegs()");
throw new Exception("Ran out of precision");
}
// Insert the splitting vertex. This should always succeed.
success = mesh.InsertVertex(newvertex, ref enctri, ref currentenc, true, triflaws);
if ((success != InsertVertexResult.Successful) && (success != InsertVertexResult.Encroaching))
{
logger.Error("Failure to split a segment.", "Quality.SplitEncSegs()");
throw new Exception("Failure to split a segment.");
}
if (mesh.steinerleft > 0)
{
mesh.steinerleft--;
}
// Check the two new subsegments to see if they're encroached.
CheckSeg4Encroach(ref currentenc);
currentenc.Next();
CheckSeg4Encroach(ref currentenc);
}
// Set subsegment's origin to NULL. This makes it possible to detect dead
// badsubsegs when traversing the list of all badsubsegs.
seg.org = null;
}
}
/// <summary>
/// Test every triangle in the mesh for quality measures.
/// </summary>
private void TallyFaces()
{
Otri triangleloop = default(Otri);
triangleloop.orient = 0;
foreach (var tri in mesh.triangles)
{
triangleloop.tri = tri;
// If the triangle is bad, enqueue it.
TestTriangle(ref triangleloop);
}
}
/// <summary>
/// Inserts a vertex at the circumcenter of a triangle. Deletes
/// the newly inserted vertex if it encroaches upon a segment.
/// </summary>
/// <param name="badtri"></param>
private void SplitTriangle(BadTriangle badtri)
{
Otri badotri = default(Otri);
Vertex borg, bdest, bapex;
Point newloc; // Location of the new vertex
double xi = 0, eta = 0;
InsertVertexResult success;
bool errorflag;
badotri = badtri.poortri;
borg = badotri.Org();
bdest = badotri.Dest();
bapex = badotri.Apex();
// Make sure that this triangle is still the same triangle it was
// when it was tested and determined to be of bad quality.
// Subsequent transformations may have made it a different triangle.
if (!Otri.IsDead(badotri.tri) && (borg == badtri.org) &&
(bdest == badtri.dest) && (bapex == badtri.apex))
{
errorflag = false;
// Create a new vertex at the triangle's circumcenter.
// Using the original (simpler) Steiner point location method
// for mesh refinement.
// TODO: NewLocation doesn't work for refinement. Why? Maybe
// reset VertexType?
if (behavior.fixedArea || behavior.VarArea)
{
newloc = predicates.FindCircumcenter(borg, bdest, bapex, ref xi, ref eta, behavior.offconstant);
}
else
{
newloc = newLocation.FindLocation(borg, bdest, bapex, ref xi, ref eta, true, badotri);
}
// Check whether the new vertex lies on a triangle vertex.
if (((newloc.x == borg.x) && (newloc.y == borg.y)) ||
((newloc.x == bdest.x) && (newloc.y == bdest.y)) ||
((newloc.x == bapex.x) && (newloc.y == bapex.y)))
{
if (Log.Verbose)
{
logger.Warning("New vertex falls on existing vertex.", "Quality.SplitTriangle()");
errorflag = true;
}
}
else
{
// The new vertex must be in the interior, and therefore is a
// free vertex with a marker of zero.
Vertex newvertex = new Vertex(newloc.x, newloc.y, 0
#if USE_ATTRIBS
, mesh.nextras
#endif
);
newvertex.type = VertexType.FreeVertex;
// Ensure that the handle 'badotri' does not represent the longest
// edge of the triangle. This ensures that the circumcenter must
// fall to the left of this edge, so point location will work.
// (If the angle org-apex-dest exceeds 90 degrees, then the
// circumcenter lies outside the org-dest edge, and eta is
// negative. Roundoff error might prevent eta from being
// negative when it should be, so I test eta against xi.)
if (eta < xi)
{
badotri.Lprev();
}
// Assign triangle for attributes interpolation.
newvertex.tri.tri = newvertex_tri;
// Insert the circumcenter, searching from the edge of the triangle,
// and maintain the Delaunay property of the triangulation.
Osub tmp = default(Osub);
success = mesh.InsertVertex(newvertex, ref badotri, ref tmp, true, true);
if (success == InsertVertexResult.Successful)
{
newvertex.hash = mesh.hash_vtx++;
newvertex.id = newvertex.hash;
#if USE_ATTRIBS
if (mesh.nextras > 0)
{
Interpolation.InterpolateAttributes(newvertex, newvertex.tri.tri, mesh.nextras);
}
#endif
#if USE_Z
Interpolation.InterpolateZ(newvertex, newvertex.tri.tri);
#endif
mesh.vertices.Add(newvertex.hash, newvertex);
if (mesh.steinerleft > 0)
{
mesh.steinerleft--;
}
}
else if (success == InsertVertexResult.Encroaching)
{
// If the newly inserted vertex encroaches upon a subsegment,
// delete the new vertex.
mesh.UndoVertex();
}
else if (success == InsertVertexResult.Violating)
{
// Failed to insert the new vertex, but some subsegment was
// marked as being encroached.
}
else
{ // success == DUPLICATEVERTEX
// Couldn't insert the new vertex because a vertex is already there.
if (Log.Verbose)
{
logger.Warning("New vertex falls on existing vertex.", "Quality.SplitTriangle()");
errorflag = true;
}
}
}
if (errorflag)
{
logger.Error("The new vertex is at the circumcenter of triangle: This probably "
+ "means that I am trying to refine triangles to a smaller size than can be "
+ "accommodated by the finite precision of floating point arithmetic.",
"Quality.SplitTriangle()");
throw new Exception("The new vertex is at the circumcenter of triangle.");
}
}
}
/// <summary>
/// Remove all the encroached subsegments and bad triangles from the triangulation.
/// </summary>
private void EnforceQuality()
{
BadTriangle badtri;
// Test all segments to see if they're encroached.
TallyEncs();
// Fix encroached subsegments without noting bad triangles.
SplitEncSegs(false);
// At this point, if we haven't run out of Steiner points, the
// triangulation should be (conforming) Delaunay.
// Next, we worry about enforcing triangle quality.
if ((behavior.MinAngle > 0.0) || behavior.VarArea || behavior.fixedArea || behavior.UserTest != null)
{
// TODO: Reset queue? (Or is it always empty at this point)
// Test all triangles to see if they're bad.
TallyFaces();
mesh.checkquality = true;
while ((queue.Count > 0) && (mesh.steinerleft != 0))
{
// Fix one bad triangle by inserting a vertex at its circumcenter.
badtri = queue.Dequeue();
SplitTriangle(badtri);
if (badsubsegs.Count > 0)
{
// Put bad triangle back in queue for another try later.
queue.Enqueue(badtri);
// Fix any encroached subsegments that resulted.
// Record any new bad triangles that result.
SplitEncSegs(true);
}
}
}
// At this point, if the "-D" switch was selected and we haven't run out
// of Steiner points, the triangulation should be (conforming) Delaunay
// and have no low-quality triangles.
// Might we have run out of Steiner points too soon?
if (Log.Verbose && behavior.ConformingDelaunay && (badsubsegs.Count > 0) && (mesh.steinerleft == 0))
{
logger.Warning("I ran out of Steiner points, but the mesh has encroached subsegments, "
+ "and therefore might not be truly Delaunay. If the Delaunay property is important "
+ "to you, try increasing the number of Steiner points.",
"Quality.EnforceQuality()");
}
}
#endregion
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 54f9a28ad3c3f4bb79ec4dea2d3d987b
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,55 @@
namespace UnityEngine.U2D.Animation.TriangleNet
.Meshing
{
using System;
using Animation.TriangleNet.Geometry;
/// <summary>
/// Mesh constraint options for quality triangulation.
/// </summary>
internal class QualityOptions
{
/// <summary>
/// Gets or sets a maximum angle constraint.
/// </summary>
public double MaximumAngle { get; set; }
/// <summary>
/// Gets or sets a minimum angle constraint.
/// </summary>
public double MinimumAngle { get; set; }
/// <summary>
/// Gets or sets a maximum triangle area constraint.
/// </summary>
public double MaximumArea { get; set; }
/// <summary>
/// Gets or sets a user-defined triangle constraint.
/// </summary>
/// <remarks>
/// The test function will be called for each triangle in the mesh. The
/// second argument is the area of the triangle tested. If the function
/// returns true, the triangle is considered bad and will be refined.
/// </remarks>
public Func<ITriangle, double, bool> UserTest { get; set; }
/// <summary>
/// Gets or sets an area constraint per triangle.
/// </summary>
/// <remarks>
/// If this flag is set to true, the <see cref="ITriangle.Area"/> value will
/// be used to check if a triangle needs refinement.
/// </remarks>
public bool VariableArea { get; set; }
/// <summary>
/// Gets or sets the maximum number of Steiner points to be inserted into the mesh.
/// </summary>
/// <remarks>
/// If the value is 0 (default), an unknown number of Steiner points may be inserted
/// to meet the other quality constraints.
/// </remarks>
public int SteinerPoints { get; set; }
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 2e9e350cb0a504f889ef5d8575817e56
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: