fcb6057f3d
git-svn-id: https://triangle.svn.codeplex.com/svn@68260 0e2699bc-83d4-4a8f-98e7-55e24ab8c7a5
524 lines
23 KiB
C#
524 lines
23 KiB
C#
// -----------------------------------------------------------------------
|
|
// <copyright file="Carver.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 TriangleNet
|
|
{
|
|
using TriangleNet.Data;
|
|
using System;
|
|
using TriangleNet.Geometry;
|
|
using System.Collections.Generic;
|
|
|
|
/// <summary>
|
|
/// Carves holes into the triangulation.
|
|
/// </summary>
|
|
class Carver
|
|
{
|
|
Mesh mesh;
|
|
|
|
public Carver(Mesh mesh)
|
|
{
|
|
this.mesh = mesh;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Virally infect all of the triangles of the convex hull that are not
|
|
/// protected by subsegments. Where there are subsegments, set boundary
|
|
/// markers as appropriate.
|
|
/// </summary>
|
|
private void InfectHull()
|
|
{
|
|
Otri hulltri = default(Otri);
|
|
Otri nexttri = default(Otri);
|
|
Otri starttri = default(Otri);
|
|
Osub hullsubseg = default(Osub);
|
|
Vertex horg, hdest;
|
|
|
|
// Find a triangle handle on the hull.
|
|
hulltri.triangle = Mesh.dummytri;
|
|
hulltri.orient = 0;
|
|
hulltri.SymSelf();
|
|
// Remember where we started so we know when to stop.
|
|
hulltri.Copy(ref starttri);
|
|
// Go once counterclockwise around the convex hull.
|
|
do
|
|
{
|
|
// Ignore triangles that are already infected.
|
|
if (!hulltri.IsInfected())
|
|
{
|
|
// Is the triangle protected by a subsegment?
|
|
hulltri.SegPivot(ref hullsubseg);
|
|
if (hullsubseg.seg == Mesh.dummysub)
|
|
{
|
|
// The triangle is not protected; infect it.
|
|
if (!hulltri.IsInfected())
|
|
{
|
|
hulltri.Infect();
|
|
mesh.viri.Add(hulltri.triangle);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// The triangle is protected; set boundary markers if appropriate.
|
|
if (hullsubseg.seg.boundary == 0)
|
|
{
|
|
hullsubseg.seg.boundary = 1;
|
|
horg = hulltri.Org();
|
|
hdest = hulltri.Dest();
|
|
if (horg.mark == 0)
|
|
{
|
|
horg.mark = 1;
|
|
}
|
|
if (hdest.mark == 0)
|
|
{
|
|
hdest.mark = 1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// To find the next hull edge, go clockwise around the next vertex.
|
|
hulltri.LnextSelf();
|
|
hulltri.Oprev(ref nexttri);
|
|
while (nexttri.triangle != Mesh.dummytri)
|
|
{
|
|
nexttri.Copy(ref hulltri);
|
|
hulltri.Oprev(ref nexttri);
|
|
}
|
|
|
|
} while (!hulltri.Equal(starttri));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Spread the virus from all infected triangles to any neighbors not
|
|
/// protected by subsegments. Delete all infected triangles.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// This is the procedure that actually creates holes and concavities.
|
|
///
|
|
/// This procedure operates in two phases. The first phase identifies all
|
|
/// the triangles that will die, and marks them as infected. They are
|
|
/// marked to ensure that each triangle is added to the virus pool only
|
|
/// once, so the procedure will terminate.
|
|
///
|
|
/// The second phase actually eliminates the infected triangles. It also
|
|
/// eliminates orphaned vertices.
|
|
/// </remarks>
|
|
void Plague()
|
|
{
|
|
Otri testtri = default(Otri);
|
|
Otri neighbor = default(Otri);
|
|
Osub neighborsubseg = default(Osub);
|
|
Vertex testvertex;
|
|
Vertex norg, ndest;
|
|
|
|
bool killorg;
|
|
|
|
// Loop through all the infected triangles, spreading the virus to
|
|
// their neighbors, then to their neighbors' neighbors.
|
|
for (int i = 0; i < mesh.viri.Count; i++)
|
|
{
|
|
// WARNING: Don't use foreach, mesh.viri list may get modified.
|
|
|
|
testtri.triangle = mesh.viri[i];
|
|
// A triangle is marked as infected by messing with one of its pointers
|
|
// to subsegments, setting it to an illegal value. Hence, we have to
|
|
// temporarily uninfect this triangle so that we can examine its
|
|
// adjacent subsegments.
|
|
// TODO: Not true in the C# version (so we could skip this).
|
|
testtri.Uninfect();
|
|
|
|
// 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.SegPivot(ref neighborsubseg);
|
|
// Check if the neighbor is nonexistent or already infected.
|
|
if ((neighbor.triangle == Mesh.dummytri) || neighbor.IsInfected())
|
|
{
|
|
if (neighborsubseg.seg != Mesh.dummysub)
|
|
{
|
|
// There is a subsegment separating the triangle from its
|
|
// neighbor, but both triangles are dying, so the subsegment
|
|
// dies too.
|
|
mesh.SubsegDealloc(neighborsubseg.seg);
|
|
if (neighbor.triangle != Mesh.dummytri)
|
|
{
|
|
// Make sure the subsegment doesn't get deallocated again
|
|
// later when the infected neighbor is visited.
|
|
neighbor.Uninfect();
|
|
neighbor.SegDissolve();
|
|
neighbor.Infect();
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{ // The neighbor exists and is not infected.
|
|
if (neighborsubseg.seg == Mesh.dummysub)
|
|
{
|
|
// There is no subsegment protecting the neighbor, so
|
|
// the neighbor becomes infected.
|
|
neighbor.Infect();
|
|
// Ensure that the neighbor's neighbors will be infected.
|
|
mesh.viri.Add(neighbor.triangle);
|
|
}
|
|
else
|
|
{
|
|
// The neighbor is protected by a subsegment.
|
|
// Remove this triangle from the subsegment.
|
|
neighborsubseg.TriDissolve();
|
|
// The subsegment becomes a boundary. Set markers accordingly.
|
|
if (neighborsubseg.seg.boundary == 0)
|
|
{
|
|
neighborsubseg.seg.boundary = 1;
|
|
}
|
|
norg = neighbor.Org();
|
|
ndest = neighbor.Dest();
|
|
if (norg.mark == 0)
|
|
{
|
|
norg.mark = 1;
|
|
}
|
|
if (ndest.mark == 0)
|
|
{
|
|
ndest.mark = 1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// Remark the triangle as infected, so it doesn't get added to the
|
|
// virus pool again.
|
|
testtri.Infect();
|
|
}
|
|
|
|
foreach (var virus in mesh.viri)
|
|
{
|
|
testtri.triangle = virus;
|
|
|
|
// Check each of the three corners of the triangle for elimination.
|
|
// This is done by walking around each vertex, checking if it is
|
|
// still connected to at least one live triangle.
|
|
for (testtri.orient = 0; testtri.orient < 3; testtri.orient++)
|
|
{
|
|
testvertex = testtri.Org();
|
|
// Check if the vertex has already been tested.
|
|
if (testvertex != null)
|
|
{
|
|
killorg = true;
|
|
// Mark the corner of the triangle as having been tested.
|
|
testtri.SetOrg(null);
|
|
// Walk counterclockwise about the vertex.
|
|
testtri.Onext(ref neighbor);
|
|
// Stop upon reaching a boundary or the starting triangle.
|
|
while ((neighbor.triangle != Mesh.dummytri) &&
|
|
(!neighbor.Equal(testtri)))
|
|
{
|
|
if (neighbor.IsInfected())
|
|
{
|
|
// Mark the corner of this triangle as having been tested.
|
|
neighbor.SetOrg(null);
|
|
}
|
|
else
|
|
{
|
|
// A live triangle. The vertex survives.
|
|
killorg = false;
|
|
}
|
|
// Walk counterclockwise about the vertex.
|
|
neighbor.OnextSelf();
|
|
}
|
|
// If we reached a boundary, we must walk clockwise as well.
|
|
if (neighbor.triangle == Mesh.dummytri)
|
|
{
|
|
// Walk clockwise about the vertex.
|
|
testtri.Oprev(ref neighbor);
|
|
// Stop upon reaching a boundary.
|
|
while (neighbor.triangle != Mesh.dummytri)
|
|
{
|
|
if (neighbor.IsInfected())
|
|
{
|
|
// Mark the corner of this triangle as having been tested.
|
|
neighbor.SetOrg(null);
|
|
}
|
|
else
|
|
{
|
|
// A live triangle. The vertex survives.
|
|
killorg = false;
|
|
}
|
|
// Walk clockwise about the vertex.
|
|
neighbor.OprevSelf();
|
|
}
|
|
}
|
|
if (killorg)
|
|
{
|
|
// Deleting vertex
|
|
testvertex.type = VertexType.UndeadVertex;
|
|
mesh.undeads++;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Record changes in the number of boundary edges, and disconnect
|
|
// dead triangles from their neighbors.
|
|
for (testtri.orient = 0; testtri.orient < 3; testtri.orient++)
|
|
{
|
|
testtri.Sym(ref neighbor);
|
|
if (neighbor.triangle == Mesh.dummytri)
|
|
{
|
|
// There is no neighboring triangle on this edge, so this edge
|
|
// is a boundary edge. This triangle is being deleted, so this
|
|
// boundary edge is deleted.
|
|
mesh.hullsize--;
|
|
}
|
|
else
|
|
{
|
|
// Disconnect the triangle from its neighbor.
|
|
neighbor.Dissolve();
|
|
// There is a neighboring triangle on this edge, so this edge
|
|
// becomes a boundary edge when this triangle is deleted.
|
|
mesh.hullsize++;
|
|
}
|
|
}
|
|
// Return the dead triangle to the pool of triangles.
|
|
mesh.TriangleDealloc(testtri.triangle);
|
|
}
|
|
|
|
// Empty the virus pool.
|
|
mesh.viri.Clear();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Spread regional attributes and/or area constraints (from a .poly file)
|
|
/// throughout the mesh.
|
|
/// </summary>
|
|
/// <param name="attribute"></param>
|
|
/// <param name="area"></param>
|
|
/// <remarks>
|
|
/// This procedure operates in two phases. The first phase spreads an
|
|
/// attribute and/or an area constraint through a (segment-bounded) region.
|
|
/// The triangles are marked to ensure that each triangle is added to the
|
|
/// virus pool only once, so the procedure will terminate.
|
|
///
|
|
/// The second phase uninfects all infected triangles, returning them to
|
|
/// normal.
|
|
/// </remarks>
|
|
void RegionPlague(double attribute, double area)
|
|
{
|
|
Otri testtri = default(Otri);
|
|
Otri neighbor = default(Otri);
|
|
Osub neighborsubseg = default(Osub);
|
|
|
|
Behavior behavior = mesh.behavior;
|
|
|
|
// 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 < mesh.viri.Count; i++)
|
|
{
|
|
// WARNING: Don't use foreach, mesh.viri list may get modified.
|
|
|
|
testtri.triangle = mesh.viri[i];
|
|
// A triangle is marked as infected by messing with one of its pointers
|
|
// to subsegments, setting it to an illegal value. Hence, we have to
|
|
// temporarily uninfect this triangle so that we can examine its
|
|
// adjacent subsegments.
|
|
// TODO: Not true in the C# version (so we could skip this).
|
|
testtri.Uninfect();
|
|
if (behavior.RegionAttrib)
|
|
{
|
|
// Set an attribute (Note: the attributes array was resized before).
|
|
testtri.triangle.attributes[mesh.eextras] = attribute;
|
|
}
|
|
if (behavior.VarArea)
|
|
{
|
|
// Set an area constraint.
|
|
testtri.triangle.area = area;
|
|
}
|
|
|
|
// 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.SegPivot(ref neighborsubseg);
|
|
// Make sure the neighbor exists, is not already infected, and
|
|
// isn't protected by a subsegment.
|
|
if ((neighbor.triangle != Mesh.dummytri) && !neighbor.IsInfected()
|
|
&& (neighborsubseg.seg == Mesh.dummysub))
|
|
{
|
|
// Infect the neighbor.
|
|
neighbor.Infect();
|
|
// Ensure that the neighbor's neighbors will be infected.
|
|
mesh.viri.Add(neighbor.triangle);
|
|
}
|
|
}
|
|
// Remark the triangle as infected, so it doesn't get added to the
|
|
// virus pool again.
|
|
testtri.Infect();
|
|
}
|
|
|
|
// Uninfect all triangles.
|
|
foreach (var virus in mesh.viri)
|
|
{
|
|
testtri.triangle = virus;
|
|
testtri.Uninfect();
|
|
}
|
|
|
|
// Empty the virus pool.
|
|
mesh.viri.Clear();
|
|
}
|
|
/// <summary>
|
|
/// Find the holes and infect them. Find the area constraints and infect
|
|
/// them. Infect the convex hull. Spread the infection and kill triangles.
|
|
/// Spread the area constraints.
|
|
/// </summary>
|
|
public void CarveHoles()
|
|
{
|
|
Otri searchtri = default(Otri);
|
|
Otri tri = default(Otri);
|
|
Vertex searchorg, searchdest;
|
|
LocateResult intersect;
|
|
|
|
Otri[] regionTris = null;
|
|
|
|
if (!mesh.behavior.Convex)
|
|
{
|
|
// Mark as infected any unprotected triangles on the boundary.
|
|
// This is one way by which concavities are created.
|
|
InfectHull();
|
|
}
|
|
|
|
if (!mesh.behavior.NoHoles)
|
|
{
|
|
// Infect each triangle in which a hole lies.
|
|
foreach (var hole in mesh.holes)
|
|
{
|
|
// Ignore holes that aren't within the bounds of the mesh.
|
|
if (mesh.bounds.Contains(hole))
|
|
{
|
|
// Start searching from some triangle on the outer boundary.
|
|
searchtri.triangle = Mesh.dummytri;
|
|
searchtri.orient = 0;
|
|
searchtri.SymSelf();
|
|
// Ensure that the hole is to the left of this boundary edge;
|
|
// otherwise, locate() will falsely report that the hole
|
|
// falls within the starting triangle.
|
|
searchorg = searchtri.Org();
|
|
searchdest = searchtri.Dest();
|
|
if (Primitives.CounterClockwise(searchorg, searchdest, hole) > 0.0)
|
|
{
|
|
// Find a triangle that contains the hole.
|
|
intersect = mesh.Locate(hole, ref searchtri);
|
|
if ((intersect != LocateResult.Outside) && (!searchtri.IsInfected()))
|
|
{
|
|
// Infect the triangle. This is done by marking the triangle
|
|
// as infected and including the triangle in the virus pool.
|
|
searchtri.Infect();
|
|
mesh.viri.Add(searchtri.triangle);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Now, we have to find all the regions BEFORE we carve the holes, because locate() won't
|
|
// work when the triangulation is no longer convex. (Incidentally, this is the reason why
|
|
// regional attributes and area constraints can't be used when refining a preexisting mesh,
|
|
// which might not be convex; they can only be used with a freshly triangulated PSLG.)
|
|
if (mesh.regions.Count > 0)
|
|
{
|
|
regionTris = new Otri[mesh.regions.Count];
|
|
|
|
int i = 0;
|
|
|
|
// Find the starting triangle for each region.
|
|
foreach (var region in mesh.regions)
|
|
{
|
|
regionTris[i].triangle = Mesh.dummytri;
|
|
// Ignore region points that aren't within the bounds of the mesh.
|
|
if (mesh.bounds.Contains(region.point))
|
|
{
|
|
// Start searching from some triangle on the outer boundary.
|
|
searchtri.triangle = Mesh.dummytri;
|
|
searchtri.orient = 0;
|
|
searchtri.SymSelf();
|
|
// Ensure that the region point is to the left of this boundary
|
|
// edge; otherwise, locate() will falsely report that the
|
|
// region point falls within the starting triangle.
|
|
searchorg = searchtri.Org();
|
|
searchdest = searchtri.Dest();
|
|
if (Primitives.CounterClockwise(searchorg, searchdest, region.point) > 0.0)
|
|
{
|
|
// Find a triangle that contains the region point.
|
|
intersect = mesh.Locate(region.point, ref searchtri);
|
|
if ((intersect != LocateResult.Outside) && (!searchtri.IsInfected()))
|
|
{
|
|
// Record the triangle for processing after the
|
|
// holes have been carved.
|
|
searchtri.Copy(ref regionTris[i]);
|
|
}
|
|
}
|
|
}
|
|
|
|
i++;
|
|
}
|
|
}
|
|
|
|
if (mesh.viri.Count > 0)
|
|
{
|
|
// Carve the holes and concavities.
|
|
Plague();
|
|
}
|
|
// The virus pool should be empty now.
|
|
|
|
if (regionTris != null)
|
|
{
|
|
if (mesh.behavior.RegionAttrib)
|
|
{
|
|
// Make the triangle's attributes larger.
|
|
double[] attributes = new double[mesh.eextras + 1];
|
|
|
|
// Assign every triangle a regional attribute of zero.
|
|
tri.orient = 0;
|
|
foreach (var t in mesh.triangles.Values)
|
|
{
|
|
Array.Copy(tri.triangle.attributes, attributes, mesh.eextras);
|
|
tri.triangle = t;
|
|
tri.triangle.attributes = attributes;
|
|
}
|
|
}
|
|
|
|
for (int i = 0; i < regionTris.Length; i++)
|
|
{
|
|
if (regionTris[i].triangle != Mesh.dummytri)
|
|
{
|
|
// Make sure the triangle under consideration still exists.
|
|
// It may have been eaten by the virus.
|
|
if (!Otri.IsDead(regionTris[i].triangle))
|
|
{
|
|
// Put one triangle in the virus pool.
|
|
regionTris[i].Infect();
|
|
mesh.viri.Add(regionTris[i].triangle);
|
|
// Apply one region's attribute and/or area constraint.
|
|
RegionPlague(mesh.regions[i].Attribute, mesh.regions[i].Area);
|
|
// The virus pool should be empty now.
|
|
}
|
|
}
|
|
}
|
|
|
|
if (mesh.behavior.RegionAttrib)
|
|
{
|
|
// Note the fact that each triangle has an additional attribute.
|
|
mesh.eextras++;
|
|
}
|
|
}
|
|
|
|
// Free up memory (virus pool should be empty anyway).
|
|
mesh.viri.Clear();
|
|
}
|
|
}
|
|
}
|