-
-
Save wo80/fe9b40925cdfdafedad935457c4768dd to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
namespace Examples | |
{ | |
using System; | |
using System.Collections.Generic; | |
using System.Linq; | |
using TriangleNet; | |
using TriangleNet.Geometry; | |
using TriangleNet.IO; | |
using TriangleNet.Meshing; | |
using TriangleNet.Meshing.Iterators; | |
using TriangleNet.Rendering.GDI; | |
using TriangleNet.Tools; | |
using TriangleNet.Topology; | |
/// <summary> | |
/// A list of points representing a set of connected segments (also known as a "line string"). | |
/// </summary> | |
class Constraint | |
{ | |
public List<Vertex> Points { get; } | |
int marker; | |
public Constraint(List<Vertex> points, int marker) | |
{ | |
this.Points = points; | |
this.marker = marker; | |
} | |
public List<ISegment> GetSegments() | |
{ | |
var segments = new List<ISegment>(); | |
var p = this.Points; | |
int count = p.Count - 1; | |
for (int i = 0; i < count; i++) | |
{ | |
// Add segments to polygon. | |
segments.Add(new Segment(p[i], p[i + 1], marker)); | |
} | |
return segments; | |
} | |
} | |
static class Extensions | |
{ | |
public static void Add(this IPolygon poly, Constraint constraint) | |
{ | |
var segments = constraint.GetSegments(); | |
int count = segments.Count - 1; | |
for (int i = 0; i < count; i++) | |
{ | |
poly.Add(segments[i], 0); | |
} | |
poly.Add(segments[count], true); | |
} | |
} | |
public static class Issue44 | |
{ | |
public static void Run() | |
{ | |
Run(1); | |
Console.WriteLine(); | |
Run(2); | |
} | |
public static void Run(int approach) | |
{ | |
Console.WriteLine($"Approach {approach}"); | |
Console.WriteLine(); | |
var holes = new List<Point>(); | |
// Generate the input geometry. | |
var poly = CreatePolygon(holes, approach); | |
// Generate mesh without any quality option. This will make processing the | |
// holes more efficient. | |
var mesh = (Mesh)poly.Triangulate(); | |
// Two approaches for processing holes: | |
if (approach == 1) | |
{ | |
// Approach 1: region pointers. | |
ProcessHoles1(mesh, holes); | |
} | |
else | |
{ | |
// Approach 2: quadtree. | |
ProcessHoles2(mesh, holes); | |
} | |
// Set minimum angle quality option. | |
var quality = new QualityOptions() | |
{ | |
MinimumAngle = 30.0, | |
}; | |
// Ignore holes when doing mesh refinement. | |
quality.Exclude = t => t.Label < 0 | |
mesh.Refine(quality); | |
ProcessMesh(mesh); | |
new TriangleWriter().WritePoly(poly, $"issue-44-{approach}.poly"); | |
ImageRenderer.Save(mesh, $"issue-44-{approach}.png", 500); | |
} | |
public static IPolygon CreatePolygon(List<Point> holes, int approach) | |
{ | |
// We don't pass the holes directly to Triangle, since automatic decimation of hole | |
// triangles is not reliable when a hole intersects another boundary. We will use | |
// negative valued labels to easily identify holes. | |
// Generate the input geometry. | |
var poly = new Polygon(); | |
// Outer ring. We use label 1 to identify the segments. | |
poly.Add(Circle(5.0, new Point(0d, 0d), 3.0, 1)); | |
// Hole 1 intersecting ring. Label -1 to identify the hole. | |
poly.Add(Circle(2.0, new Point(4d, -4d), 1.0, -1)); | |
holes.Add(new Point(4d, -4d, -1)); | |
// Hole 2 inside ring. Label -2 to identify the hole. | |
poly.Add(Circle(1.5, new Point(-1.5, 2d), 0.5, -2)); | |
holes.Add(new Point(-1.5, 2d, -2)); | |
if (approach == 1) | |
{ | |
// For approach 1, we try to mark the holes with a special region pointer. Make | |
// sure that the holes' boundary marker and the corresponding region pointer have | |
// the same value. | |
// | |
// For the same reason as above, we cannot rely on the whole region being marked | |
// correctly, but there will be at least one trianlge marked correctly for each | |
// hole. | |
foreach (var hole in holes) | |
{ | |
poly.Regions.Add(new RegionPointer(hole.X, hole.Y, hole.Label)); | |
} | |
} | |
// Constraint. | |
var constraint = new Constraint(new List<Vertex>() | |
{ | |
new Vertex(-2.5, -6d, 4), | |
new Vertex(-2.5, -1d, 4), | |
new Vertex(-0.5, -1d, 4), | |
new Vertex(-0.5, 6d, 4) | |
}, 4); | |
poly.Add(constraint); | |
poly.Add(new Vertex(-3.5, 0d, 5)); | |
return poly; | |
} | |
/// <summary> | |
/// This is just a dummy function counting boundary conditions. | |
/// </summary> | |
private static void ProcessMesh(Mesh mesh) | |
{ | |
// Counting triangles touching constraints (boundary conditions). | |
var boundary = new Dictionary<int, int>(); | |
// Sweep mesh excluding holes. | |
foreach (var t in mesh.Triangles.Where(t => t.Label >= 0)) | |
{ | |
// Check if any triangle side is a segment. | |
for (int i = 0; i < 3; i++) | |
{ | |
var s = t.GetSegment(i); | |
if (s != null) | |
{ | |
if (boundary.ContainsKey(s.Label)) | |
{ | |
boundary[s.Label]++; | |
} | |
else | |
{ | |
boundary[s.Label] = 1; | |
} | |
} | |
} | |
} | |
foreach (var p in boundary) | |
{ | |
Console.WriteLine($"Mesh has {p.Value} edges with boundary condition {p.Key}"); | |
} | |
} | |
#region Approach 1: region pointers | |
private static void ProcessHoles1(Mesh mesh, List<Point> holes) | |
{ | |
var iterator = new RegionIterator(mesh); | |
int numHoles = holes.Count; | |
var set = new HashSet<int>(); | |
// Sweep over all triangles to find a seeding triangle for holes. | |
foreach (var seed in mesh.Triangles) | |
{ | |
int label = seed.Label; | |
// Check if hole was already processed. | |
if (!set.Add(label)) continue; | |
// Test if triangle is inside a hole (we explicitly chose to give | |
// holes a negative label value). | |
if (label < 0) | |
{ | |
int count = 0; | |
// Mark all triangles inside hole with the label. This works because | |
// the hole boundary segments share the same label and the iterator | |
// will stop when encountering such a segment (last argument of the | |
// iterator.Process(...) method). | |
iterator.Process(seed, t => { t.Label = label; count++; }, label); | |
Console.WriteLine($"Found {count} triangles in hole with label {label}."); | |
numHoles--; | |
} | |
// Break as soon as there are no holes left. | |
if (numHoles == 0) break; | |
} | |
} | |
#endregion | |
#region Approach 2: quadtree | |
private static void ProcessHoles2(Mesh mesh, List<Point> holes) | |
{ | |
var iterator = new RegionIterator(mesh); | |
// Use quadtree to find seeding trianlge for holes. | |
var index = new TriangleQuadTree(mesh); | |
foreach (var h in holes) | |
{ | |
int label = h.Label; | |
var seed = (Triangle)index.Query(h.X, h.Y); | |
if (seed == null) | |
{ | |
throw new Exception($"Unable to find seeding triangle for hole {label}."); | |
} | |
int count = 0; | |
// Mark all triangles inside hole with the label. This works because | |
// the holde boundary segments share the same label and the iterator | |
// will stop when encountering such a segment (last argument of the | |
// iterator.Process(...) method). | |
iterator.Process(seed, t => { t.Label = label; count++; }, label); | |
Console.WriteLine($"Found {count} triangles in hole with label {label}."); | |
} | |
} | |
#endregion | |
/// <summary> | |
/// Create a circular contour. | |
/// </summary> | |
/// <param name="r">The radius.</param> | |
/// <param name="center">The center point.</param> | |
/// <param name="h">The desired segment length.</param> | |
/// <param name="label">The boundary label.</param> | |
/// <returns>A circular contour.</returns> | |
public static Contour Circle(double r, Point center, double h, int label = 0) | |
{ | |
int n = (int)(2 * Math.PI * r / h); | |
var points = new List<Vertex>(n); | |
double x, y, dphi = 2 * Math.PI / n; | |
for (int i = 0; i < n; i++) | |
{ | |
x = center.X + r * Math.Cos(i * dphi); | |
y = center.Y + r * Math.Sin(i * dphi); | |
points.Add(new Vertex(x, y, label)); | |
} | |
return new Contour(points, label, true); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment