Skip to content

Instantly share code, notes, and snippets.

@wo80
Last active October 5, 2022 16:11
Show Gist options
  • Save wo80/fe9b40925cdfdafedad935457c4768dd to your computer and use it in GitHub Desktop.
Save wo80/fe9b40925cdfdafedad935457c4768dd to your computer and use it in GitHub Desktop.
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