Mesh generation for water surfaces. Defines OutlineVertex for boundary points and SurfaceMesher which subdivides outlines, removes duplicates, and builds a constrained Delaunay triangulation with an interior grid for water surface meshing.
using System;
using System.Collections.Generic;
using Sandbox;
namespace RedSnail.WaterTool2;
/// <summary>
/// A point of an outline a surface is meshed inside. Points that belong to a river's row carry the exact state of the
/// water on that row, and the edge between two points of the same row is never split: the surface meets the river on
/// the river's own vertices, with the river's own data on them, so nothing can open up between the two.
/// </summary>
internal struct OutlineVertex
{
public Vector3 Position;
/// <summary>
/// The shared row this point belongs to, or -1 for a free point (a shore, a bank, a fillet).
/// </summary>
public int Row;
public Vector2 Velocity;
public float Waves;
public float Foam;
/// <summary>
/// A point where the surface meets the sea's own (the edge of a river mouth's water, against the coast zone): it is
/// the sea there exactly, and the edge between two such points is never split either, so it stays on the grid the sea
/// is drawn on.
/// </summary>
public bool SeaEdge;
public readonly bool IsFixed => Row >= 0;
public static OutlineVertex Free(Vector3 _Position)
{
return new OutlineVertex { Position = _Position, Row = -1 };
}
public static OutlineVertex OnSeaEdge(Vector3 _Position)
{
return new OutlineVertex { Position = _Position, Row = -1, SeaEdge = true };
}
public static OutlineVertex OnRow(RiverRow _Row, int _Index, int _RowId)
{
return new OutlineVertex
{
Position = _Row.Points[_Index],
Row = _RowId,
Velocity = _Row.Velocities[_Index],
Waves = _Row.Waves[_Index],
Foam = _Row.Foams[_Index]
};
}
}
/// <summary>
/// Meshes the inside of a closed outline for the water: the outline's own points plus a grid of points inside it, joined
/// into a constrained Delaunay triangulation. Waves need vertices all over a surface, not just round its edge, and the
/// triangulation keeps every edge of the outline as it is, which is what lets a lake or a confluence share its edge
/// with the rows of the rivers running into it.
/// </summary>
internal static class SurfaceMesher
{
/// <summary>
/// Interior points closer than this many spacings to the outline are left out: they would only make slivers.
/// </summary>
private const float EdgeClearance = 0.5f;
/// <summary>
/// The interior grid is offset from the world's axes by these fractions of a spacing, so that outlines drawn on
/// round numbers never put a grid point exactly on an edge.
/// </summary>
private const double GridOffsetX = 0.31;
private const double GridOffsetY = 0.57;
/// <summary>
/// Splits every edge of an outline that is not part of a river's row into pieces no longer than the spacing, so
/// the surface has vertices all along its shore. The new points are free; their heights are interpolated.
/// </summary>
public static List<OutlineVertex> Subdivide(IReadOnlyList<OutlineVertex> _Outline, float _Spacing)
{
var result = new List<OutlineVertex>(_Outline.Count * 2);
float spacing = MathF.Max(1.0f, _Spacing);
for (int i = 0; i < _Outline.Count; i++)
{
var from = _Outline[i];
var to = _Outline[(i + 1) % _Outline.Count];
result.Add(from);
// An edge between two points of the same row is the river's own, one along the sea's edge the sea's: neither
// is ever split.
if ((from.IsFixed && to.IsFixed && from.Row == to.Row) || (from.SeaEdge && to.SeaEdge))
continue;
float length = new Vector2(to.Position.x - from.Position.x, to.Position.y - from.Position.y).Length;
int pieces = (int)MathF.Ceiling(length / spacing);
for (int k = 1; k < pieces; k++)
result.Add(OutlineVertex.Free(Vector3.Lerp(from.Position, to.Position, (float)k / pieces)));
}
return result;
}
/// <summary>
/// Removes points that sit on the one before them, which would only make edges of no length. Points of a river's
/// row (or of the sea's edge) are always kept; the free point next to one goes instead.
/// </summary>
public static List<OutlineVertex> RemoveDuplicates(IReadOnlyList<OutlineVertex> _Outline, float _Distance = 0.5f)
{
var result = new List<OutlineVertex>(_Outline.Count);
foreach (var vertex in _Outline)
{
if (result.Count > 0 && Flat(result[^1].Position).Distance(Flat(vertex.Position)) < _Distance)
{
if (IsKept(result[^1]) || !IsKept(vertex))
continue;
result[^1] = vertex;
continue;
}
result.Add(vertex);
}
// The loop closes back on its first point.
while (result.Count > 1 && Flat(result[^1].Position).Distance(Flat(result[0].Position)) < _Distance)
{
if (IsKept(result[^1]) && !IsKept(result[0]))
result.RemoveAt(0);
else
result.RemoveAt(result.Count - 1);
}
return result;
}
private static bool IsKept(in OutlineVertex _Vertex)
{
return _Vertex.IsFixed || _Vertex.SeaEdge;
}
/// <summary>
/// Triangulates the inside of a closed outline (either winding) together with a grid of interior points every
/// <paramref name="_Spacing"/>. The result lists the outline's points first, in their order, then the interior
/// points; every triangle winds counter-clockwise seen from above. False when the outline is not a simple polygon.
/// </summary>
public static bool Triangulate(IReadOnlyList<Vector2> _Outline, float _Spacing, out List<Vector2> _Points, out List<int> _Triangles)
{
_Points = null;
_Triangles = null;
int count = _Outline.Count;
if (count < 3)
return false;
// Everything is worked out relative to the outline's middle, in doubles: a lake can be tens of thousands of
// units across and its predicates must still agree with each other.
var bounds = new BBox(new Vector3(_Outline[0].x, _Outline[0].y, 0.0f), new Vector3(_Outline[0].x, _Outline[0].y, 0.0f));
foreach (var point in _Outline)
bounds = bounds.AddPoint(new Vector3(point.x, point.y, 0.0f));
double originX = Math.Round(bounds.Center.x);
double originY = Math.Round(bounds.Center.y);
double area = 0.0;
for (int i = 0; i < count; i++)
{
var a = _Outline[i];
var b = _Outline[(i + 1) % count];
area += (double)a.x * b.y - (double)b.x * a.y;
}
if (Math.Abs(area) < 1e-6)
return false;
// The triangulation wants the outline counter-clockwise; a clockwise one is walked backwards and mapped back.
bool reversed = area < 0.0;
var mesh = new Triangulation(Math.Max(bounds.Size.x, bounds.Size.y));
for (int i = 0; i < count; i++)
{
var point = _Outline[reversed ? count - 1 - i : i];
mesh.AddPoint(point.x - originX, point.y - originY);
}
if (!mesh.EarClip(count))
return false;
mesh.BuildAdjacency(count);
mesh.MakeDelaunay();
InsertGrid(mesh, _Outline, reversed, originX, originY, _Spacing);
// Back to the caller's order: outline points first, as given, then the interior points.
_Points = new List<Vector2>(mesh.PointCount);
for (int i = 0; i < count; i++)
_Points.Add(_Outline[i]);
for (int i = count; i < mesh.PointCount; i++)
_Points.Add(new Vector2((float)(mesh.X[i] + originX), (float)(mesh.Y[i] + originY)));
_Triangles = new List<int>(mesh.TriangleCount * 3);
for (int t = 0; t < mesh.TriangleCount; t++)
{
for (int k = 0; k < 3; k++)
{
int index = mesh.V[t * 3 + k];
if (reversed && index < count)
index = count - 1 - index;
_Triangles.Add(index);
}
}
return true;
}
/// <summary>
/// Adds the interior grid: every point of it inside the outline and clear of its edges, row by row and back and
/// forth along the rows, so each one is found a short walk from the last.
/// </summary>
private static void InsertGrid(Triangulation _Mesh, IReadOnlyList<Vector2> _Outline, bool _Reversed, double _OriginX, double _OriginY, float _Spacing)
{
double spacing = Math.Max(1.0, _Spacing);
int count = _Outline.Count;
// The outline relative to the origin, in the triangulation's own (counter-clockwise) order.
var xs = new double[count];
var ys = new double[count];
double minX = double.MaxValue, minY = double.MaxValue, maxX = double.MinValue, maxY = double.MinValue;
for (int i = 0; i < count; i++)
{
xs[i] = _Mesh.X[i];
ys[i] = _Mesh.Y[i];
minX = Math.Min(minX, xs[i]);
minY = Math.Min(minY, ys[i]);
maxX = Math.Max(maxX, xs[i]);
maxY = Math.Max(maxY, ys[i]);
}
// The grid is anchored in the world, not in the outline, so it does not swim as the outline is edited.
double offsetX = Mod(GridOffsetX * spacing - _OriginX, spacing);
double offsetY = Mod(GridOffsetY * spacing - _OriginY, spacing);
double clearance = EdgeClearance * spacing;
var edges = new EdgeGrid(xs, ys, spacing, clearance);
var crossings = new List<double>();
var row = new List<double>();
var hints = new Dictionary<long, int>();
int hint = 0;
int firstRow = (int)Math.Ceiling((minY - offsetY) / spacing);
int lastRow = (int)Math.Floor((maxY - offsetY) / spacing);
for (int j = firstRow; j <= lastRow; j++)
{
double y = j * spacing + offsetY;
// Where the row crosses the outline: inside between each pair of crossings.
crossings.Clear();
for (int i = 0; i < count; i++)
{
int next = (i + 1) % count;
double y0 = ys[i];
double y1 = ys[next];
if ((y0 > y) == (y1 > y))
continue;
crossings.Add(xs[i] + (y - y0) * (xs[next] - xs[i]) / (y1 - y0));
}
crossings.Sort();
row.Clear();
for (int c = 0; c + 1 < crossings.Count; c += 2)
{
int from = (int)Math.Ceiling((crossings[c] - offsetX) / spacing);
int to = (int)Math.Floor((crossings[c + 1] - offsetX) / spacing);
for (int i = from; i <= to; i++)
{
double x = i * spacing + offsetX;
if (!edges.IsNear(x, y))
row.Add(x);
}
}
// Back and forth, so the walk to the first point of a row starts right next to it.
if (((j - firstRow) & 1) == 1)
row.Reverse();
foreach (double x in row)
{
long bucket = BucketKey(x, y, spacing * 2.0);
int triangle = _Mesh.Locate(x, y, hint);
if (triangle < 0 && hints.TryGetValue(bucket, out int bucketHint))
triangle = _Mesh.Locate(x, y, bucketHint);
if (triangle < 0)
triangle = FindNearHint(_Mesh, hints, x, y, spacing * 2.0);
if (triangle < 0)
triangle = _Mesh.Scan(x, y);
if (triangle < 0)
continue;
int inserted = _Mesh.InsertAt(triangle, x, y, spacing);
if (inserted < 0)
continue;
hint = inserted;
hints[bucket] = inserted;
}
}
}
/// <summary>
/// A walk starting from the hint of any bucket around the point.
/// </summary>
private static int FindNearHint(Triangulation _Mesh, Dictionary<long, int> _Hints, double _X, double _Y, double _BucketSize)
{
long bx = (long)Math.Floor(_X / _BucketSize);
long by = (long)Math.Floor(_Y / _BucketSize);
for (int ring = 1; ring <= 2; ring++)
{
for (long dy = -ring; dy <= ring; dy++)
{
for (long dx = -ring; dx <= ring; dx++)
{
if (Math.Max(Math.Abs(dx), Math.Abs(dy)) != ring)
continue;
if (!_Hints.TryGetValue(Key(bx + dx, by + dy), out int start))
continue;
int triangle = _Mesh.Locate(_X, _Y, start);
if (triangle >= 0)
return triangle;
}
}
}
return -1;
}
private static long BucketKey(double _X, double _Y, double _BucketSize)
{
return Key((long)Math.Floor(_X / _BucketSize), (long)Math.Floor(_Y / _BucketSize));
}
private static long Key(long _X, long _Y)
{
return (_X << 32) ^ (_Y & 0xFFFFFFFFL);
}
private static double Mod(double _Value, double _Modulo)
{
double result = _Value % _Modulo;
return result < 0.0 ? result + _Modulo : result;
}
private static Vector2 Flat(Vector3 _Vector)
{
return new Vector2(_Vector.x, _Vector.y);
}
/// <summary>
/// The outline's edges bucketed on a grid, for the "too close to the outline" test of the interior points.
/// </summary>
private sealed class EdgeGrid
{
private readonly double[] m_X;
private readonly double[] m_Y;
private readonly double m_Cell;
private readonly double m_Clearance;
private readonly Dictionary<long, List<int>> m_Buckets = new();
public EdgeGrid(double[] _X, double[] _Y, double _Cell, double _Clearance)
{
m_X = _X;
m_Y = _Y;
m_Cell = _Cell;
m_Clearance = _Clearance;
int count = _X.Length;
for (int i = 0; i < count; i++)
{
int next = (i + 1) % count;
long fromX = (long)Math.Floor((Math.Min(_X[i], _X[next]) - _Clearance) / _Cell);
long toX = (long)Math.Floor((Math.Max(_X[i], _X[next]) + _Clearance) / _Cell);
long fromY = (long)Math.Floor((Math.Min(_Y[i], _Y[next]) - _Clearance) / _Cell);
long toY = (long)Math.Floor((Math.Max(_Y[i], _Y[next]) + _Clearance) / _Cell);
for (long y = fromY; y <= toY; y++)
{
for (long x = fromX; x <= toX; x++)
{
long key = Key(x, y);
if (!m_Buckets.TryGetValue(key, out var list))
{
list = new List<int>();
m_Buckets[key] = list;
}
list.Add(i);
}
}
}
}
public bool IsNear(double _X, double _Y)
{
long key = Key((long)Math.Floor(_X / m_Cell), (long)Math.Floor(_Y / m_Cell));
if (!m_Buckets.TryGetValue(key, out var list))
return false;
double limit = m_Clearance * m_Clearance;
int count = m_X.Length;
foreach (int i in list)
{
int next = (i + 1) % count;
double ex = m_X[next] - m_X[i];
double ey = m_Y[next] - m_Y[i];
double lengthSquared = ex * ex + ey * ey;
double t = lengthSquared > 0.0 ? Math.Clamp(((_X - m_X[i]) * ex + (_Y - m_Y[i]) * ey) / lengthSquared, 0.0, 1.0) : 0.0;
double dx = m_X[i] + ex * t - _X;
double dy = m_Y[i] + ey * t - _Y;
if (dx * dx + dy * dy < limit)
return true;
}
return false;
}
}
/// <summary>
/// A constrained triangulation: triangles winding counter-clockwise, each knowing its three neighbours (across the
/// edge from its k-th vertex to the next) and which of its edges belong to the outline and must never be flipped.
/// </summary>
private sealed class Triangulation
{
public readonly List<double> X = new();
public readonly List<double> Y = new();
public readonly List<int> V = new();
private readonly List<int> m_N = new();
private readonly List<bool> m_C = new();
private readonly Stack<int> m_Stack = new();
private readonly double m_AreaEpsilon;
public int PointCount => X.Count;
public int TriangleCount => V.Count / 3;
public Triangulation(double _Extent)
{
// Twice a triangle's area, below which it counts as flat.
m_AreaEpsilon = Math.Max(1e-9, _Extent * _Extent * 1e-12);
}
public int AddPoint(double _X, double _Y)
{
X.Add(_X);
Y.Add(_Y);
return X.Count - 1;
}
private double Orient(int _A, int _B, int _C)
{
return (X[_B] - X[_A]) * (Y[_C] - Y[_A]) - (Y[_B] - Y[_A]) * (X[_C] - X[_A]);
}
private double Orient(int _A, int _B, double _X, double _Y)
{
return (X[_B] - X[_A]) * (_Y - Y[_A]) - (Y[_B] - Y[_A]) * (_X - X[_A]);
}
private int AddTriangle(int _A, int _B, int _C)
{
V.Add(_A);
V.Add(_B);
V.Add(_C);
for (int k = 0; k < 3; k++)
{
m_N.Add(-1);
m_C.Add(false);
}
return TriangleCount - 1;
}
private void SetTriangle(int _T, int _A, int _B, int _C, int _NAB, int _NBC, int _NCA, bool _CAB, bool _CBC, bool _CCA)
{
int i = _T * 3;
V[i] = _A;
V[i + 1] = _B;
V[i + 2] = _C;
m_N[i] = _NAB;
m_N[i + 1] = _NBC;
m_N[i + 2] = _NCA;
m_C[i] = _CAB;
m_C[i + 1] = _CBC;
m_C[i + 2] = _CCA;
}
private void ReplaceNeighbour(int _Triangle, int _Old, int _New)
{
if (_Triangle < 0)
return;
for (int k = 0; k < 3; k++)
{
if (m_N[_Triangle * 3 + k] != _Old)
continue;
m_N[_Triangle * 3 + k] = _New;
return;
}
}
/// <summary>
/// Ear clipping of the outline (points 0 to count - 1, counter-clockwise). An ear is a convex corner with no
/// other point inside or on its triangle; testing "on" as well is what keeps a corner from being cut along a
/// diagonal that runs through a point of a straight stretch, like the points across a river's row.
/// </summary>
public bool EarClip(int _Count)
{
int n = _Count;
var prev = new int[n];
var next = new int[n];
var removed = new bool[n];
var reflex = new bool[n];
var reflexList = new List<int>();
for (int i = 0; i < n; i++)
{
prev[i] = (i + n - 1) % n;
next[i] = (i + 1) % n;
}
for (int i = 0; i < n; i++)
{
reflex[i] = Orient(prev[i], i, next[i]) <= m_AreaEpsilon;
if (reflex[i])
reflexList.Add(i);
}
int remaining = n;
int current = 0;
int misses = 0;
while (remaining > 3)
{
if (IsEar(current, prev, next, reflex, removed, reflexList))
{
int a = prev[current];
int c = next[current];
AddTriangle(a, current, c);
removed[current] = true;
next[a] = c;
prev[c] = a;
remaining--;
// Cutting an ear can only make its neighbours more convex.
if (reflex[a] && Orient(prev[a], a, next[a]) > m_AreaEpsilon)
reflex[a] = false;
if (reflex[c] && Orient(prev[c], c, next[c]) > m_AreaEpsilon)
reflex[c] = false;
current = c;
misses = 0;
continue;
}
current = next[current];
// A full lap without an ear: the outline crosses itself.
if (++misses > remaining)
return false;
}
int last = current;
if (Orient(prev[last], last, next[last]) <= m_AreaEpsilon)
return false;
AddTriangle(prev[last], last, next[last]);
return true;
}
private bool IsEar(int _I, int[] _Prev, int[] _Next, bool[] _Reflex, bool[] _Removed, List<int> _ReflexList)
{
int a = _Prev[_I];
int c = _Next[_I];
if (_Reflex[_I] || Orient(a, _I, c) <= m_AreaEpsilon)
return false;
foreach (int r in _ReflexList)
{
if (_Removed[r] || !_Reflex[r] || r == a || r == _I || r == c)
continue;
if (Orient(a, _I, r) >= -m_AreaEpsilon && Orient(_I, c, r) >= -m_AreaEpsilon && Orient(c, a, r) >= -m_AreaEpsilon)
return false;
}
return true;
}
/// <summary>
/// Links every triangle to its neighbours, and marks the outline's edges as constraints.
/// </summary>
public void BuildAdjacency(int _OutlineCount)
{
var edges = new Dictionary<long, int>(V.Count);
for (int t = 0; t < TriangleCount; t++)
{
for (int k = 0; k < 3; k++)
edges[EdgeKey(V[t * 3 + k], V[t * 3 + (k + 1) % 3])] = t;
}
for (int t = 0; t < TriangleCount; t++)
{
for (int k = 0; k < 3; k++)
{
int a = V[t * 3 + k];
int b = V[t * 3 + (k + 1) % 3];
m_N[t * 3 + k] = edges.TryGetValue(EdgeKey(b, a), out int other) ? other : -1;
m_C[t * 3 + k] = a < _OutlineCount && b < _OutlineCount && (b == (a + 1) % _OutlineCount || a == (b + 1) % _OutlineCount);
}
}
}
private static long EdgeKey(int _A, int _B)
{
return ((long)_A << 32) | (uint)_B;
}
/// <summary>
/// Lawson's flips over the whole triangulation: every edge that is not the outline's and fails the empty circle
/// test is flipped, until none is left. The result is the constrained Delaunay triangulation of the outline.
/// </summary>
public void MakeDelaunay()
{
m_Stack.Clear();
for (int t = 0; t < TriangleCount; t++)
{
for (int k = 0; k < 3; k++)
{
if (m_N[t * 3 + k] >= 0 && !m_C[t * 3 + k])
m_Stack.Push(t * 3 + k);
}
}
Legalize();
}
private void Legalize()
{
int guard = 0;
int limit = V.Count * 40 + 1000;
while (m_Stack.Count > 0 && guard++ < limit)
{
int edge = m_Stack.Pop();
Flip(edge / 3, edge % 3);
}
m_Stack.Clear();
}
/// <summary>
/// Flips the edge from the k-th vertex of a triangle to the next when it is not locally Delaunay: the triangles
/// (a, b, c) and (b, a, d) become (a, d, c) and (d, b, c). The four edges round the pair are queued again.
/// </summary>
private bool Flip(int _T, int _K)
{
int edge = _T * 3 + _K;
int u = m_N[edge];
if (u < 0 || m_C[edge])
return false;
int a = V[_T * 3 + _K];
int b = V[_T * 3 + (_K + 1) % 3];
int c = V[_T * 3 + (_K + 2) % 3];
int j = -1;
for (int m = 0; m < 3; m++)
{
if (V[u * 3 + m] == b && V[u * 3 + (m + 1) % 3] == a)
{
j = m;
break;
}
}
if (j < 0)
return false;
int d = V[u * 3 + (j + 2) % 3];
if (!InCircle(a, b, c, d))
return false;
// Never flip into a folded pair (only possible right at the precision limit).
if (Orient(a, d, c) <= m_AreaEpsilon || Orient(d, b, c) <= m_AreaEpsilon)
return false;
int nbc = m_N[_T * 3 + (_K + 1) % 3];
bool cbc = m_C[_T * 3 + (_K + 1) % 3];
int nca = m_N[_T * 3 + (_K + 2) % 3];
bool cca = m_C[_T * 3 + (_K + 2) % 3];
int nad = m_N[u * 3 + (j + 1) % 3];
bool cad = m_C[u * 3 + (j + 1) % 3];
int ndb = m_N[u * 3 + (j + 2) % 3];
bool cdb = m_C[u * 3 + (j + 2) % 3];
SetTriangle(_T, a, d, c, nad, u, nca, cad, false, cca);
SetTriangle(u, d, b, c, ndb, nbc, _T, cdb, cbc, false);
ReplaceNeighbour(nad, u, _T);
ReplaceNeighbour(nbc, _T, u);
m_Stack.Push(_T * 3);
m_Stack.Push(_T * 3 + 2);
m_Stack.Push(u * 3);
m_Stack.Push(u * 3 + 1);
return true;
}
/// <summary>
/// Whether d lies inside the circle through the counter-clockwise triangle (a, b, c), by more than the
/// arithmetic can be trusted to (cocircular points, like a square of grid points, are left alone).
/// </summary>
private bool InCircle(int _A, int _B, int _C, int _D)
{
double adx = X[_A] - X[_D];
double ady = Y[_A] - Y[_D];
double bdx = X[_B] - X[_D];
double bdy = Y[_B] - Y[_D];
double cdx = X[_C] - X[_D];
double cdy = Y[_C] - Y[_D];
double ad = adx * adx + ady * ady;
double bd = bdx * bdx + bdy * bdy;
double cd = cdx * cdx + cdy * cdy;
double t1 = ad * (bdx * cdy - cdx * bdy);
double t2 = bd * (cdx * ady - adx * cdy);
double t3 = cd * (adx * bdy - bdx * ady);
double scale = Math.Abs(t1) + Math.Abs(t2) + Math.Abs(t3);
return t1 + t2 + t3 > scale * 1e-10;
}
/// <summary>
/// Walks from a triangle towards a point, across whichever edge has the point on its far side, until the point
/// is inside. -1 when the walk runs off the outline (the straight line to the point leaves the surface).
/// </summary>
public int Locate(double _X, double _Y, int _Start)
{
if (TriangleCount == 0)
return -1;
int t = _Start >= 0 && _Start < TriangleCount ? _Start : 0;
int limit = TriangleCount + 16;
int rotate = 0;
for (int step = 0; step < limit; step++)
{
int crossed = -1;
for (int e = 0; e < 3; e++)
{
int k = (e + rotate) % 3;
if (Orient(V[t * 3 + k], V[t * 3 + (k + 1) % 3], _X, _Y) < 0.0)
{
crossed = k;
break;
}
}
if (crossed < 0)
return t;
int neighbour = m_N[t * 3 + crossed];
if (neighbour < 0)
return -1;
t = neighbour;
rotate = (rotate + 1) % 3;
}
return -1;
}
/// <summary>
/// The triangle a point is in, by testing them all. Only for when every walk failed.
/// </summary>
public int Scan(double _X, double _Y)
{
for (int t = 0; t < TriangleCount; t++)
{
if (Orient(V[t * 3], V[t * 3 + 1], _X, _Y) >= 0.0 &&
Orient(V[t * 3 + 1], V[t * 3 + 2], _X, _Y) >= 0.0 &&
Orient(V[t * 3 + 2], V[t * 3], _X, _Y) >= 0.0)
{
return t;
}
}
return -1;
}
/// <summary>
/// Splits a triangle at a point inside it and restores the Delaunay property round it. A point sitting on (or
/// a hair off) an edge is nudged into the triangle first, so the split never makes a flat triangle. Returns
/// the triangle the point ended up in, or -1 when it could not be placed.
/// </summary>
public int InsertAt(int _Triangle, double _X, double _Y, double _Spacing)
{
int t = _Triangle;
int a = V[t * 3];
int b = V[t * 3 + 1];
int c = V[t * 3 + 2];
double x = _X;
double y = _Y;
double clearance = Math.Max(1e-4, _Spacing * 1e-3);
for (int attempt = 0; attempt < 4; attempt++)
{
if (EdgeDistance(a, b, x, y) > clearance && EdgeDistance(b, c, x, y) > clearance && EdgeDistance(c, a, x, y) > clearance)
break;
if (attempt == 3)
return -1;
// Towards the middle of the triangle, a little further each time.
double cx = (X[a] + X[b] + X[c]) / 3.0;
double cy = (Y[a] + Y[b] + Y[c]) / 3.0;
double fraction = 0.01 * (attempt + 1);
x += (cx - x) * fraction;
y += (cy - y) * fraction;
}
int p = AddPoint(x, y);
int nab = m_N[t * 3];
int nbc = m_N[t * 3 + 1];
int nca = m_N[t * 3 + 2];
bool cab = m_C[t * 3];
bool cbc = m_C[t * 3 + 1];
bool cca = m_C[t * 3 + 2];
int t1 = AddTriangle(b, c, p);
int t2 = AddTriangle(c, a, p);
SetTriangle(t, a, b, p, nab, t1, t2, cab, false, false);
SetTriangle(t1, b, c, p, nbc, t2, t, cbc, false, false);
SetTriangle(t2, c, a, p, nca, t, t1, cca, false, false);
ReplaceNeighbour(nbc, t, t1);
ReplaceNeighbour(nca, t, t2);
m_Stack.Clear();
m_Stack.Push(t * 3);
m_Stack.Push(t1 * 3);
m_Stack.Push(t2 * 3);
Legalize();
return t;
}
/// <summary>
/// How far a point is inside a triangle's edge (negative outside it).
/// </summary>
private double EdgeDistance(int _A, int _B, double _X, double _Y)
{
double length = Math.Sqrt((X[_B] - X[_A]) * (X[_B] - X[_A]) + (Y[_B] - Y[_A]) * (Y[_B] - Y[_A]));
return length > 0.0 ? Orient(_A, _B, _X, _Y) / length : 0.0;
}
}
}