mirror of
https://gitgud.io/AbstractConcept/rimworld-animation-studio.git
synced 2024-08-15 00:43:27 +00:00
Initial commit
This commit is contained in:
commit
3c7cc0c973
8391 changed files with 704313 additions and 0 deletions
|
@ -0,0 +1,248 @@
|
|||
// -----------------------------------------------------------------------
|
||||
// <copyright file="Behavior.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
|
||||
{
|
||||
using System;
|
||||
using Animation.TriangleNet.Geometry;
|
||||
|
||||
/// <summary>
|
||||
/// Controls the behavior of the meshing software.
|
||||
/// </summary>
|
||||
class Behavior
|
||||
{
|
||||
bool poly = false;
|
||||
bool quality = false;
|
||||
bool varArea = false;
|
||||
bool convex = false;
|
||||
bool jettison = false;
|
||||
bool boundaryMarkers = true;
|
||||
bool noHoles = false;
|
||||
bool conformDel = false;
|
||||
|
||||
Func<ITriangle, double, bool> usertest;
|
||||
|
||||
int noBisect = 0;
|
||||
|
||||
double minAngle = 0.0;
|
||||
double maxAngle = 0.0;
|
||||
double maxArea = -1.0;
|
||||
|
||||
internal bool fixedArea = false;
|
||||
internal bool useSegments = true;
|
||||
internal bool useRegions = false;
|
||||
internal double goodAngle = 0.0;
|
||||
internal double maxGoodAngle = 0.0;
|
||||
internal double offconstant = 0.0;
|
||||
|
||||
/// <summary>
|
||||
/// Creates an instance of the Behavior class.
|
||||
/// </summary>
|
||||
public Behavior(bool quality = false, double minAngle = 20.0)
|
||||
{
|
||||
if (quality)
|
||||
{
|
||||
this.quality = true;
|
||||
this.minAngle = minAngle;
|
||||
|
||||
Update();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update quality options dependencies.
|
||||
/// </summary>
|
||||
private void Update()
|
||||
{
|
||||
this.quality = true;
|
||||
|
||||
if (this.minAngle < 0 || this.minAngle > 60)
|
||||
{
|
||||
this.minAngle = 0;
|
||||
this.quality = false;
|
||||
|
||||
Log.Instance.Warning("Invalid quality option (minimum angle).", "Mesh.Behavior");
|
||||
}
|
||||
|
||||
if ((this.maxAngle != 0.0) && (this.maxAngle < 60 || this.maxAngle > 180))
|
||||
{
|
||||
this.maxAngle = 0;
|
||||
this.quality = false;
|
||||
|
||||
Log.Instance.Warning("Invalid quality option (maximum angle).", "Mesh.Behavior");
|
||||
}
|
||||
|
||||
this.useSegments = this.Poly || this.Quality || this.Convex;
|
||||
this.goodAngle = Math.Cos(this.MinAngle * Math.PI / 180.0);
|
||||
this.maxGoodAngle = Math.Cos(this.MaxAngle * Math.PI / 180.0);
|
||||
|
||||
if (this.goodAngle == 1.0)
|
||||
{
|
||||
this.offconstant = 0.0;
|
||||
}
|
||||
else
|
||||
{
|
||||
this.offconstant = 0.475 * Math.Sqrt((1.0 + this.goodAngle) / (1.0 - this.goodAngle));
|
||||
}
|
||||
|
||||
this.goodAngle *= this.goodAngle;
|
||||
}
|
||||
|
||||
#region Static properties
|
||||
|
||||
/// <summary>
|
||||
/// No exact arithmetic.
|
||||
/// </summary>
|
||||
internal static bool NoExact { get; set; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public properties
|
||||
|
||||
/// <summary>
|
||||
/// Quality mesh generation.
|
||||
/// </summary>
|
||||
public bool Quality
|
||||
{
|
||||
get { return quality; }
|
||||
set
|
||||
{
|
||||
quality = value;
|
||||
if (quality)
|
||||
{
|
||||
Update();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Minimum angle constraint.
|
||||
/// </summary>
|
||||
public double MinAngle
|
||||
{
|
||||
get { return minAngle; }
|
||||
set { minAngle = value; Update(); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Maximum angle constraint.
|
||||
/// </summary>
|
||||
public double MaxAngle
|
||||
{
|
||||
get { return maxAngle; }
|
||||
set { maxAngle = value; Update(); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Maximum area constraint.
|
||||
/// </summary>
|
||||
public double MaxArea
|
||||
{
|
||||
get { return maxArea; }
|
||||
set
|
||||
{
|
||||
maxArea = value;
|
||||
fixedArea = value > 0.0;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Apply a maximum triangle area constraint.
|
||||
/// </summary>
|
||||
public bool VarArea
|
||||
{
|
||||
get { return varArea; }
|
||||
set { varArea = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Input is a Planar Straight Line Graph.
|
||||
/// </summary>
|
||||
public bool Poly
|
||||
{
|
||||
get { return poly; }
|
||||
set { poly = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Apply a user-defined triangle constraint.
|
||||
/// </summary>
|
||||
public Func<ITriangle, double, bool> UserTest
|
||||
{
|
||||
get { return usertest; }
|
||||
set { usertest = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enclose the convex hull with segments.
|
||||
/// </summary>
|
||||
public bool Convex
|
||||
{
|
||||
get { return convex; }
|
||||
set { convex = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Conforming Delaunay (all triangles are truly Delaunay).
|
||||
/// </summary>
|
||||
public bool ConformingDelaunay
|
||||
{
|
||||
get { return conformDel; }
|
||||
set { conformDel = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Suppresses boundary segment splitting.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 0 = split segments
|
||||
/// 1 = no new vertices on the boundary
|
||||
/// 2 = prevent all segment splitting, including internal boundaries
|
||||
/// </remarks>
|
||||
public int NoBisect
|
||||
{
|
||||
get { return noBisect; }
|
||||
set
|
||||
{
|
||||
noBisect = value;
|
||||
if (noBisect < 0 || noBisect > 2)
|
||||
{
|
||||
noBisect = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compute boundary information.
|
||||
/// </summary>
|
||||
public bool UseBoundaryMarkers
|
||||
{
|
||||
get { return boundaryMarkers; }
|
||||
set { boundaryMarkers = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ignores holes in polygons.
|
||||
/// </summary>
|
||||
public bool NoHoles
|
||||
{
|
||||
get { return noHoles; }
|
||||
set { noHoles = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Jettison unused vertices from output.
|
||||
/// </summary>
|
||||
public bool Jettison
|
||||
{
|
||||
get { return jettison; }
|
||||
set { jettison = value; }
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: ed18cd282abce4422b584d99115a6c87
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,44 @@
|
|||
// -----------------------------------------------------------------------
|
||||
// <copyright file="Configuration.cs" company="">
|
||||
// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/
|
||||
// </copyright>
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
namespace UnityEngine.U2D.Animation.TriangleNet
|
||||
{
|
||||
using System;
|
||||
using Animation.TriangleNet.Meshing;
|
||||
using Animation.TriangleNet.Meshing.Algorithm;
|
||||
|
||||
/// <summary>
|
||||
/// Configure advanced aspects of the library.
|
||||
/// </summary>
|
||||
internal class Configuration
|
||||
{
|
||||
public Configuration()
|
||||
: this(() => RobustPredicates.Default, () => new TrianglePool())
|
||||
{
|
||||
}
|
||||
|
||||
public Configuration(Func<IPredicates> predicates)
|
||||
: this(predicates, () => new TrianglePool())
|
||||
{
|
||||
}
|
||||
|
||||
public Configuration(Func<IPredicates> predicates, Func<TrianglePool> trianglePool)
|
||||
{
|
||||
Predicates = predicates;
|
||||
TrianglePool = trianglePool;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the factory method for the <see cref="IPredicates"/> implementation.
|
||||
/// </summary>
|
||||
public Func<IPredicates> Predicates { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the factory method for the <see cref="TrianglePool"/>.
|
||||
/// </summary>
|
||||
public Func<TrianglePool> TrianglePool { get; set; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 3668477112fb74f47ae17f9a227d562f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,46 @@
|
|||
// -----------------------------------------------------------------------
|
||||
// <copyright file="Enums.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
|
||||
{
|
||||
/// <summary>
|
||||
/// The type of the mesh vertex.
|
||||
/// </summary>
|
||||
internal enum VertexType { InputVertex, SegmentVertex, FreeVertex, DeadVertex, UndeadVertex };
|
||||
|
||||
/// <summary>
|
||||
/// Node renumbering algorithms.
|
||||
/// </summary>
|
||||
internal enum NodeNumbering { None, Linear, CuthillMcKee };
|
||||
|
||||
/// <summary>
|
||||
/// Labels that signify the result of point location.
|
||||
/// </summary>
|
||||
/// <remarks>The result of a search indicates that the point falls in the
|
||||
/// interior of a triangle, on an edge, on a vertex, or outside the mesh.
|
||||
/// </remarks>
|
||||
internal enum LocateResult { InTriangle, OnEdge, OnVertex, Outside };
|
||||
|
||||
/// <summary>
|
||||
/// Labels that signify the result of vertex insertion.
|
||||
/// </summary>
|
||||
/// <remarks>The result indicates that the vertex was inserted with complete
|
||||
/// success, was inserted but encroaches upon a subsegment, was not inserted
|
||||
/// because it lies on a segment, or was not inserted because another vertex
|
||||
/// occupies the same location.
|
||||
/// </remarks>
|
||||
enum InsertVertexResult { Successful, Encroaching, Violating, Duplicate };
|
||||
|
||||
/// <summary>
|
||||
/// Labels that signify the result of direction finding.
|
||||
/// </summary>
|
||||
/// <remarks>The result indicates that a segment connecting the two query
|
||||
/// points falls within the direction triangle, along the left edge of the
|
||||
/// direction triangle, or along the right edge of the direction triangle.
|
||||
/// </remarks>
|
||||
enum FindDirectionResult { Within, Leftcollinear, Rightcollinear };
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: bffd3c18e4fd644ef820ed46f2f9133c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: a2002c22d90564ebb9e66b44b6f50886
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,248 @@
|
|||
// -----------------------------------------------------------------------
|
||||
// <copyright file="Contour.cs" company="">
|
||||
// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/
|
||||
// </copyright>
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
namespace UnityEngine.U2D.Animation.TriangleNet
|
||||
.Geometry
|
||||
{
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
|
||||
internal class Contour
|
||||
{
|
||||
int marker;
|
||||
|
||||
bool convex;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the list of points making up the contour.
|
||||
/// </summary>
|
||||
public List<Vertex> Points { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Contour" /> class.
|
||||
/// </summary>
|
||||
/// <param name="points">The points that make up the contour.</param>
|
||||
public Contour(IEnumerable<Vertex> points)
|
||||
: this(points, 0, false)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Contour" /> class.
|
||||
/// </summary>
|
||||
/// <param name="points">The points that make up the contour.</param>
|
||||
/// <param name="marker">Contour marker.</param>
|
||||
public Contour(IEnumerable<Vertex> points, int marker)
|
||||
: this(points, marker, false)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Contour" /> class.
|
||||
/// </summary>
|
||||
/// <param name="points">The points that make up the contour.</param>
|
||||
/// <param name="marker">Contour marker.</param>
|
||||
/// <param name="convex">The hole is convex.</param>
|
||||
public Contour(IEnumerable<Vertex> points, int marker, bool convex)
|
||||
{
|
||||
AddPoints(points);
|
||||
|
||||
this.marker = marker;
|
||||
this.convex = convex;
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
// Close the contour.
|
||||
segments.Add(new Segment(p[count], p[0], marker));
|
||||
|
||||
return segments;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Try to find a point inside the contour.
|
||||
/// </summary>
|
||||
/// <param name="limit">The number of iterations on each segment (default = 5).</param>
|
||||
/// <param name="eps">Threshold for co-linear points (default = 2e-5).</param>
|
||||
/// <returns>Point inside the contour</returns>
|
||||
/// <exception cref="Exception">Throws if no point could be found.</exception>
|
||||
/// <remarks>
|
||||
/// For each corner (index i) of the contour, the 3 points with indices i-1, i and i+1
|
||||
/// are considered and a search on the line through the corner vertex is started (either
|
||||
/// on the bisecting line, or, if <see cref="IPredicates.CounterClockwise"/> is less than
|
||||
/// eps, on the perpendicular line.
|
||||
/// A given number of points will be tested (limit), while the distance to the contour
|
||||
/// boundary will be reduced in each iteration (with a factor 1 / 2^i, i = 1 ... limit).
|
||||
/// </remarks>
|
||||
public Point FindInteriorPoint(int limit = 5, double eps = 2e-5)
|
||||
{
|
||||
if (convex)
|
||||
{
|
||||
int count = this.Points.Count;
|
||||
|
||||
var point = new Point(0.0, 0.0);
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
point.x += this.Points[i].x;
|
||||
point.y += this.Points[i].y;
|
||||
}
|
||||
|
||||
// If the contour is convex, use its centroid.
|
||||
point.x /= count;
|
||||
point.y /= count;
|
||||
|
||||
return point;
|
||||
}
|
||||
|
||||
return FindPointInPolygon(this.Points, limit, eps);
|
||||
}
|
||||
|
||||
private void AddPoints(IEnumerable<Vertex> points)
|
||||
{
|
||||
this.Points = new List<Vertex>(points);
|
||||
|
||||
int count = Points.Count - 1;
|
||||
|
||||
// Check if first vertex equals last vertex.
|
||||
if (Points[0] == Points[count])
|
||||
{
|
||||
Points.RemoveAt(count);
|
||||
}
|
||||
}
|
||||
|
||||
#region Helper methods
|
||||
|
||||
private static Point FindPointInPolygon(List<Vertex> contour, int limit, double eps)
|
||||
{
|
||||
var bounds = new Rectangle();
|
||||
bounds.Expand(contour.Cast<Point>());
|
||||
|
||||
int length = contour.Count;
|
||||
|
||||
var test = new Point();
|
||||
|
||||
Point a, b, c; // Current corner points.
|
||||
|
||||
double bx, by;
|
||||
double dx, dy;
|
||||
double h;
|
||||
|
||||
var predicates = new RobustPredicates();
|
||||
|
||||
a = contour[0];
|
||||
b = contour[1];
|
||||
|
||||
for (int i = 0; i < length; i++)
|
||||
{
|
||||
c = contour[(i + 2) % length];
|
||||
|
||||
// Corner point.
|
||||
bx = b.x;
|
||||
by = b.y;
|
||||
|
||||
// NOTE: if we knew the contour points were in counterclockwise order, we
|
||||
// could skip concave corners and search only in one direction.
|
||||
|
||||
h = predicates.CounterClockwise(a, b, c);
|
||||
|
||||
if (Math.Abs(h) < eps)
|
||||
{
|
||||
// Points are nearly co-linear. Use perpendicular direction.
|
||||
dx = (c.y - a.y) / 2;
|
||||
dy = (a.x - c.x) / 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Direction [midpoint(a-c) -> corner point]
|
||||
dx = (a.x + c.x) / 2 - bx;
|
||||
dy = (a.y + c.y) / 2 - by;
|
||||
}
|
||||
|
||||
// Move around the contour.
|
||||
a = b;
|
||||
b = c;
|
||||
|
||||
h = 1.0;
|
||||
|
||||
for (int j = 0; j < limit; j++)
|
||||
{
|
||||
// Search in direction.
|
||||
test.x = bx + dx * h;
|
||||
test.y = by + dy * h;
|
||||
|
||||
if (bounds.Contains(test) && IsPointInPolygon(test, contour))
|
||||
{
|
||||
return test;
|
||||
}
|
||||
|
||||
// Search in opposite direction (see NOTE above).
|
||||
test.x = bx - dx * h;
|
||||
test.y = by - dy * h;
|
||||
|
||||
if (bounds.Contains(test) && IsPointInPolygon(test, contour))
|
||||
{
|
||||
return test;
|
||||
}
|
||||
|
||||
h = h / 2;
|
||||
}
|
||||
}
|
||||
|
||||
throw new Exception();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return true if the given point is inside the polygon, or false if it is not.
|
||||
/// </summary>
|
||||
/// <param name="point">The point to check.</param>
|
||||
/// <param name="poly">The polygon (list of contour points).</param>
|
||||
/// <returns></returns>
|
||||
/// <remarks>
|
||||
/// WARNING: If the point is exactly on the edge of the polygon, then the function
|
||||
/// may return true or false.
|
||||
///
|
||||
/// See http://alienryderflex.com/polygon/
|
||||
/// </remarks>
|
||||
private static bool IsPointInPolygon(Point point, List<Vertex> poly)
|
||||
{
|
||||
bool inside = false;
|
||||
|
||||
double x = point.x;
|
||||
double y = point.y;
|
||||
|
||||
int count = poly.Count;
|
||||
|
||||
for (int i = 0, j = count - 1; i < count; i++)
|
||||
{
|
||||
if (((poly[i].y < y && poly[j].y >= y) || (poly[j].y < y && poly[i].y >= y))
|
||||
&& (poly[i].x <= x || poly[j].x <= x))
|
||||
{
|
||||
inside ^= (poly[i].x + (y - poly[i].y) / (poly[j].y - poly[i].y) * (poly[j].x - poly[i].x) < x);
|
||||
}
|
||||
|
||||
j = i;
|
||||
}
|
||||
|
||||
return inside;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 7115b8240624044b9a02b74ca778a32e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,59 @@
|
|||
// -----------------------------------------------------------------------
|
||||
// <copyright file="Edge.cs" company="">
|
||||
// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/
|
||||
// </copyright>
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
namespace UnityEngine.U2D.Animation.TriangleNet
|
||||
.Geometry
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a straight line segment in 2D space.
|
||||
/// </summary>
|
||||
internal class Edge : IEdge
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the first endpoints index.
|
||||
/// </summary>
|
||||
public int P0
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the second endpoints index.
|
||||
/// </summary>
|
||||
public int P1
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the segments boundary mark.
|
||||
/// </summary>
|
||||
public int Label
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Edge" /> class.
|
||||
/// </summary>
|
||||
public Edge(int p0, int p1)
|
||||
: this(p0, p1, 0)
|
||||
{}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Edge" /> class.
|
||||
/// </summary>
|
||||
public Edge(int p0, int p1, int label)
|
||||
{
|
||||
this.P0 = p0;
|
||||
this.P1 = p1;
|
||||
this.Label = label;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 80d43aefd9bfa4f669b8abc336353628
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,143 @@
|
|||
namespace UnityEngine.U2D.Animation.TriangleNet
|
||||
.Geometry
|
||||
{
|
||||
using System;
|
||||
using Animation.TriangleNet.Meshing;
|
||||
|
||||
internal static class ExtensionMethods
|
||||
{
|
||||
#region IPolygon extensions
|
||||
|
||||
/// <summary>
|
||||
/// Triangulates a polygon.
|
||||
/// </summary>
|
||||
internal static IMesh Triangulate(this IPolygon polygon)
|
||||
{
|
||||
return (new GenericMesher()).Triangulate(polygon, null, null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Triangulates a polygon, applying constraint options.
|
||||
/// </summary>
|
||||
/// <param name="options">Constraint options.</param>
|
||||
internal static IMesh Triangulate(this IPolygon polygon, ConstraintOptions options)
|
||||
{
|
||||
return (new GenericMesher()).Triangulate(polygon, options, null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Triangulates a polygon, applying quality options.
|
||||
/// </summary>
|
||||
/// <param name="quality">Quality options.</param>
|
||||
internal static IMesh Triangulate(this IPolygon polygon, QualityOptions quality)
|
||||
{
|
||||
return (new GenericMesher()).Triangulate(polygon, null, quality);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Triangulates a polygon, applying quality and constraint options.
|
||||
/// </summary>
|
||||
/// <param name="options">Constraint options.</param>
|
||||
/// <param name="quality">Quality options.</param>
|
||||
internal static IMesh Triangulate(this IPolygon polygon, ConstraintOptions options, QualityOptions quality)
|
||||
{
|
||||
return (new GenericMesher()).Triangulate(polygon, options, quality);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Triangulates a polygon, applying quality and constraint options.
|
||||
/// </summary>
|
||||
/// <param name="options">Constraint options.</param>
|
||||
/// <param name="quality">Quality options.</param>
|
||||
/// <param name="triangulator">The triangulation algorithm.</param>
|
||||
internal static IMesh Triangulate(this IPolygon polygon, ConstraintOptions options, QualityOptions quality,
|
||||
ITriangulator triangulator)
|
||||
{
|
||||
return (new GenericMesher(triangulator)).Triangulate(polygon, options, quality);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Rectangle extensions
|
||||
|
||||
#endregion
|
||||
|
||||
#region ITriangle extensions
|
||||
|
||||
/// <summary>
|
||||
/// Test whether a given point lies inside a triangle or not.
|
||||
/// </summary>
|
||||
/// <param name="p">Point to locate.</param>
|
||||
/// <returns>True, if point is inside or on the edge of this triangle.</returns>
|
||||
internal static bool Contains(this ITriangle triangle, Point p)
|
||||
{
|
||||
return Contains(triangle, p.X, p.Y);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test whether a given point lies inside a triangle or not.
|
||||
/// </summary>
|
||||
/// <param name="x">Point to locate.</param>
|
||||
/// <param name="y">Point to locate.</param>
|
||||
/// <returns>True, if point is inside or on the edge of this triangle.</returns>
|
||||
internal static bool Contains(this ITriangle triangle, double x, double y)
|
||||
{
|
||||
var t0 = triangle.GetVertex(0);
|
||||
var t1 = triangle.GetVertex(1);
|
||||
var t2 = triangle.GetVertex(2);
|
||||
|
||||
// TODO: no need to create new Point instances here
|
||||
Point d0 = new Point(t1.X - t0.X, t1.Y - t0.Y);
|
||||
Point d1 = new Point(t2.X - t0.X, t2.Y - t0.Y);
|
||||
Point d2 = new Point(x - t0.X, y - t0.Y);
|
||||
|
||||
// crossproduct of (0, 0, 1) and d0
|
||||
Point c0 = new Point(-d0.Y, d0.X);
|
||||
|
||||
// crossproduct of (0, 0, 1) and d1
|
||||
Point c1 = new Point(-d1.Y, d1.X);
|
||||
|
||||
// Linear combination d2 = s * d0 + v * d1.
|
||||
//
|
||||
// Multiply both sides of the equation with c0 and c1
|
||||
// and solve for s and v respectively
|
||||
//
|
||||
// s = d2 * c1 / d0 * c1
|
||||
// v = d2 * c0 / d1 * c0
|
||||
|
||||
double s = DotProduct(d2, c1) / DotProduct(d0, c1);
|
||||
double v = DotProduct(d2, c0) / DotProduct(d1, c0);
|
||||
|
||||
if (s >= 0 && v >= 0 && ((s + v) <= 1))
|
||||
{
|
||||
// Point is inside or on the edge of this triangle.
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
internal static Rectangle Bounds(this ITriangle triangle)
|
||||
{
|
||||
var bounds = new Rectangle();
|
||||
|
||||
for (int i = 0; i < 3; i++)
|
||||
{
|
||||
bounds.Expand(triangle.GetVertex(i));
|
||||
}
|
||||
|
||||
return bounds;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Helper methods
|
||||
|
||||
internal static double DotProduct(Point p, Point q)
|
||||
{
|
||||
return p.X * q.X + p.Y * q.Y;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 5756a98707df6417ba9135ea9d4f857e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,30 @@
|
|||
// -----------------------------------------------------------------------
|
||||
// <copyright file="IEdge.cs" company="">
|
||||
// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/
|
||||
// </copyright>
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
namespace UnityEngine.U2D.Animation.TriangleNet
|
||||
.Geometry
|
||||
{
|
||||
internal interface IEdge
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the first endpoints index.
|
||||
/// </summary>
|
||||
int P0 { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the second endpoints index.
|
||||
/// </summary>
|
||||
int P1 { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a general-purpose label.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is used for the segments boundary mark.
|
||||
/// </remarks>
|
||||
int Label { get; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: ac8497ccdcaa24180924e889b9d7b7d5
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,94 @@
|
|||
// -----------------------------------------------------------------------
|
||||
// <copyright file="IPolygon.cs" company="">
|
||||
// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/
|
||||
// </copyright>
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
namespace UnityEngine.U2D.Animation.TriangleNet
|
||||
.Geometry
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
/// <summary>
|
||||
/// Polygon interface.
|
||||
/// </summary>
|
||||
internal interface IPolygon
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the vertices of the polygon.
|
||||
/// </summary>
|
||||
List<Vertex> Points { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the segments of the polygon.
|
||||
/// </summary>
|
||||
List<ISegment> Segments { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a list of points defining the holes of the polygon.
|
||||
/// </summary>
|
||||
List<Point> Holes { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a list of pointers defining the regions of the polygon.
|
||||
/// </summary>
|
||||
List<RegionPointer> Regions { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the vertices have marks or not.
|
||||
/// </summary>
|
||||
bool HasPointMarkers { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the segments have marks or not.
|
||||
/// </summary>
|
||||
bool HasSegmentMarkers { get; set; }
|
||||
|
||||
[Obsolete("Use polygon.Add(contour) method instead.")]
|
||||
void AddContour(IEnumerable<Vertex> points, int marker, bool hole, bool convex);
|
||||
|
||||
[Obsolete("Use polygon.Add(contour) method instead.")]
|
||||
void AddContour(IEnumerable<Vertex> points, int marker, Point hole);
|
||||
|
||||
/// <summary>
|
||||
/// Compute the bounds of the polygon.
|
||||
/// </summary>
|
||||
/// <returns>Rectangle defining an axis-aligned bounding box.</returns>
|
||||
Rectangle Bounds();
|
||||
|
||||
/// <summary>
|
||||
/// Add a vertex to the polygon.
|
||||
/// </summary>
|
||||
/// <param name="vertex">The vertex to insert.</param>
|
||||
void Add(Vertex vertex);
|
||||
|
||||
/// <summary>
|
||||
/// Add a segment to the polygon.
|
||||
/// </summary>
|
||||
/// <param name="segment">The segment to insert.</param>
|
||||
/// <param name="insert">If true, both endpoints will be added to the points list.</param>
|
||||
void Add(ISegment segment, bool insert = false);
|
||||
|
||||
/// <summary>
|
||||
/// Add a segment to the polygon.
|
||||
/// </summary>
|
||||
/// <param name="segment">The segment to insert.</param>
|
||||
/// <param name="index">The index of the segment endpoint to add to the points list (must be 0 or 1).</param>
|
||||
void Add(ISegment segment, int index);
|
||||
|
||||
/// <summary>
|
||||
/// Add a contour to the polygon.
|
||||
/// </summary>
|
||||
/// <param name="contour">The contour to insert.</param>
|
||||
/// <param name="hole">Treat contour as a hole.</param>
|
||||
void Add(Contour contour, bool hole = false);
|
||||
|
||||
/// <summary>
|
||||
/// Add a contour to the polygon.
|
||||
/// </summary>
|
||||
/// <param name="contour">The contour to insert.</param>
|
||||
/// <param name="hole">Point inside the contour, making it a hole.</param>
|
||||
void Add(Contour contour, Point hole);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 68c59424e0fae433b9a2957e751fb25f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,27 @@
|
|||
// -----------------------------------------------------------------------
|
||||
// <copyright file="ISegment.cs" company="">
|
||||
// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/
|
||||
// </copyright>
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
namespace UnityEngine.U2D.Animation.TriangleNet
|
||||
.Geometry
|
||||
{
|
||||
/// <summary>
|
||||
/// Interface for segment geometry.
|
||||
/// </summary>
|
||||
internal interface ISegment : IEdge
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the vertex at given index.
|
||||
/// </summary>
|
||||
/// <param name="index">The local index (0 or 1).</param>
|
||||
Vertex GetVertex(int index);
|
||||
|
||||
/// <summary>
|
||||
/// Gets an adjoining triangle.
|
||||
/// </summary>
|
||||
/// <param name="index">The triangle index (0 or 1).</param>
|
||||
ITriangle GetTriangle(int index);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 9132cd52e337145a7a84643de287a066
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,70 @@
|
|||
// -----------------------------------------------------------------------
|
||||
// <copyright file="ITriangle.cs" company="">
|
||||
// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/
|
||||
// </copyright>
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
namespace UnityEngine.U2D.Animation.TriangleNet
|
||||
.Geometry
|
||||
{
|
||||
using Animation.TriangleNet.Topology;
|
||||
|
||||
/// <summary>
|
||||
/// Triangle interface.
|
||||
/// </summary>
|
||||
internal interface ITriangle
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the triangle ID.
|
||||
/// </summary>
|
||||
int ID { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a general-purpose label.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is used for region information.
|
||||
/// </remarks>
|
||||
int Label { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the triangle area constraint.
|
||||
/// </summary>
|
||||
double Area { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the vertex at given index.
|
||||
/// </summary>
|
||||
/// <param name="index">The local index (0, 1 or 2).</param>
|
||||
/// <returns>The vertex.</returns>
|
||||
Vertex GetVertex(int index);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the ID of the vertex at given index.
|
||||
/// </summary>
|
||||
/// <param name="index">The local index (0, 1 or 2).</param>
|
||||
/// <returns>The vertex ID.</returns>
|
||||
int GetVertexID(int index);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the neighbor triangle at given index.
|
||||
/// </summary>
|
||||
/// <param name="index">The local index (0, 1 or 2).</param>
|
||||
/// <returns>The neighbor triangle.</returns>
|
||||
ITriangle GetNeighbor(int index);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the ID of the neighbor triangle at given index.
|
||||
/// </summary>
|
||||
/// <param name="index">The local index (0, 1 or 2).</param>
|
||||
/// <returns>The neighbor triangle ID.</returns>
|
||||
int GetNeighborID(int index);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the segment at given index.
|
||||
/// </summary>
|
||||
/// <param name="index">The local index (0, 1 or 2).</param>
|
||||
/// <returns>The segment.</returns>
|
||||
ISegment GetSegment(int index);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 6c2817c735e6d4f80867fba95274f386
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,181 @@
|
|||
// -----------------------------------------------------------------------
|
||||
// <copyright file="Point.cs" company="">
|
||||
// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/
|
||||
// </copyright>
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
namespace UnityEngine.U2D.Animation.TriangleNet
|
||||
.Geometry
|
||||
{
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a 2D point.
|
||||
/// </summary>
|
||||
#if USE_Z
|
||||
[DebuggerDisplay("ID {ID} [{X}, {Y}, {Z}]")]
|
||||
#else
|
||||
[DebuggerDisplay("ID {ID} [{X}, {Y}]")]
|
||||
#endif
|
||||
internal class Point : IComparable<Point>, IEquatable<Point>
|
||||
{
|
||||
internal int id;
|
||||
internal int label;
|
||||
|
||||
internal double x;
|
||||
internal double y;
|
||||
#if USE_Z
|
||||
internal double z;
|
||||
#endif
|
||||
|
||||
public Point()
|
||||
: this(0.0, 0.0, 0)
|
||||
{
|
||||
}
|
||||
|
||||
public Point(double x, double y)
|
||||
: this(x, y, 0)
|
||||
{
|
||||
}
|
||||
|
||||
public Point(double x, double y, int label)
|
||||
{
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.label = label;
|
||||
}
|
||||
|
||||
#region Public properties
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the vertex id.
|
||||
/// </summary>
|
||||
public int ID
|
||||
{
|
||||
get { return this.id; }
|
||||
set { this.id = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the vertex x coordinate.
|
||||
/// </summary>
|
||||
public double X
|
||||
{
|
||||
get { return this.x; }
|
||||
set { this.x = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the vertex y coordinate.
|
||||
/// </summary>
|
||||
public double Y
|
||||
{
|
||||
get { return this.y; }
|
||||
set { this.y = value; }
|
||||
}
|
||||
|
||||
#if USE_Z
|
||||
/// <summary>
|
||||
/// Gets or sets the vertex z coordinate.
|
||||
/// </summary>
|
||||
public double Z
|
||||
{
|
||||
get { return this.z; }
|
||||
set { this.z = value; }
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a general-purpose label.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is used for the vertex boundary mark.
|
||||
/// </remarks>
|
||||
public int Label
|
||||
{
|
||||
get { return this.label; }
|
||||
set { this.label = value; }
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Operator overloading / overriding Equals
|
||||
|
||||
// Compare "Guidelines for Overriding Equals() and Operator =="
|
||||
// http://msdn.microsoft.com/en-us/library/ms173147.aspx
|
||||
|
||||
public static bool operator==(Point a, Point b)
|
||||
{
|
||||
// If both are null, or both are same instance, return true.
|
||||
if (Object.ReferenceEquals(a, b))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// If one is null, but not both, return false.
|
||||
if (((object)a == null) || ((object)b == null))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return a.Equals(b);
|
||||
}
|
||||
|
||||
public static bool operator!=(Point a, Point b)
|
||||
{
|
||||
return !(a == b);
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
// If parameter is null return false.
|
||||
if (obj == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Point p = obj as Point;
|
||||
|
||||
if ((object)p == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return (x == p.x) && (y == p.y);
|
||||
}
|
||||
|
||||
public bool Equals(Point p)
|
||||
{
|
||||
// If vertex is null return false.
|
||||
if ((object)p == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Return true if the fields match:
|
||||
return (x == p.x) && (y == p.y);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public int CompareTo(Point other)
|
||||
{
|
||||
if (x == other.x && y == other.y)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
return (x < other.x || (x == other.x && y < other.y)) ? -1 : 1;
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
int hash = 19;
|
||||
hash = hash * 31 + x.GetHashCode();
|
||||
hash = hash * 31 + y.GetHashCode();
|
||||
|
||||
return hash;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 07d9e02d256864a01916e9533af15bca
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,185 @@
|
|||
// -----------------------------------------------------------------------
|
||||
// <copyright file="Polygon.cs" company="">
|
||||
// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/
|
||||
// </copyright>
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
namespace UnityEngine.U2D.Animation.TriangleNet
|
||||
.Geometry
|
||||
{
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
|
||||
/// <summary>
|
||||
/// A polygon represented as a planar straight line graph.
|
||||
/// </summary>
|
||||
internal class Polygon : IPolygon
|
||||
{
|
||||
List<Vertex> points;
|
||||
List<Point> holes;
|
||||
List<RegionPointer> regions;
|
||||
|
||||
List<ISegment> segments;
|
||||
|
||||
/// <inheritdoc />
|
||||
public List<Vertex> Points
|
||||
{
|
||||
get { return points; }
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public List<Point> Holes
|
||||
{
|
||||
get { return holes; }
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public List<RegionPointer> Regions
|
||||
{
|
||||
get { return regions; }
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public List<ISegment> Segments
|
||||
{
|
||||
get { return segments; }
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool HasPointMarkers { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool HasSegmentMarkers { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public int Count
|
||||
{
|
||||
get { return points.Count; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Polygon" /> class.
|
||||
/// </summary>
|
||||
public Polygon()
|
||||
: this(3, false)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Polygon" /> class.
|
||||
/// </summary>
|
||||
/// <param name="capacity">The default capacity for the points list.</param>
|
||||
public Polygon(int capacity)
|
||||
: this(3, false)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Polygon" /> class.
|
||||
/// </summary>
|
||||
/// <param name="capacity">The default capacity for the points list.</param>
|
||||
/// <param name="markers">Use point and segment markers.</param>
|
||||
public Polygon(int capacity, bool markers)
|
||||
{
|
||||
points = new List<Vertex>(capacity);
|
||||
holes = new List<Point>();
|
||||
regions = new List<RegionPointer>();
|
||||
|
||||
segments = new List<ISegment>();
|
||||
|
||||
HasPointMarkers = markers;
|
||||
HasSegmentMarkers = markers;
|
||||
}
|
||||
|
||||
[Obsolete("Use polygon.Add(contour) method instead.")]
|
||||
public void AddContour(IEnumerable<Vertex> points, int marker = 0,
|
||||
bool hole = false, bool convex = false)
|
||||
{
|
||||
this.Add(new Contour(points, marker, convex), hole);
|
||||
}
|
||||
|
||||
[Obsolete("Use polygon.Add(contour) method instead.")]
|
||||
public void AddContour(IEnumerable<Vertex> points, int marker, Point hole)
|
||||
{
|
||||
this.Add(new Contour(points, marker), hole);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Rectangle Bounds()
|
||||
{
|
||||
var bounds = new Rectangle();
|
||||
bounds.Expand(this.points.Cast<Point>());
|
||||
|
||||
return bounds;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add a vertex to the polygon.
|
||||
/// </summary>
|
||||
/// <param name="vertex">The vertex to insert.</param>
|
||||
public void Add(Vertex vertex)
|
||||
{
|
||||
this.points.Add(vertex);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add a segment to the polygon.
|
||||
/// </summary>
|
||||
/// <param name="segment">The segment to insert.</param>
|
||||
/// <param name="insert">If true, both endpoints will be added to the points list.</param>
|
||||
public void Add(ISegment segment, bool insert = false)
|
||||
{
|
||||
this.segments.Add(segment);
|
||||
|
||||
if (insert)
|
||||
{
|
||||
this.points.Add(segment.GetVertex(0));
|
||||
this.points.Add(segment.GetVertex(1));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add a segment to the polygon.
|
||||
/// </summary>
|
||||
/// <param name="segment">The segment to insert.</param>
|
||||
/// <param name="index">The index of the segment endpoint to add to the points list (must be 0 or 1).</param>
|
||||
public void Add(ISegment segment, int index)
|
||||
{
|
||||
this.segments.Add(segment);
|
||||
|
||||
this.points.Add(segment.GetVertex(index));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add a contour to the polygon.
|
||||
/// </summary>
|
||||
/// <param name="contour">The contour to insert.</param>
|
||||
/// <param name="hole">Treat contour as a hole.</param>
|
||||
public void Add(Contour contour, bool hole = false)
|
||||
{
|
||||
if (hole)
|
||||
{
|
||||
this.Add(contour, contour.FindInteriorPoint());
|
||||
}
|
||||
else
|
||||
{
|
||||
this.points.AddRange(contour.Points);
|
||||
this.segments.AddRange(contour.GetSegments());
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add a contour to the polygon.
|
||||
/// </summary>
|
||||
/// <param name="contour">The contour to insert.</param>
|
||||
/// <param name="hole">Point inside the contour, making it a hole.</param>
|
||||
public void Add(Contour contour, Point hole)
|
||||
{
|
||||
this.points.AddRange(contour.Points);
|
||||
this.segments.AddRange(contour.GetSegments());
|
||||
|
||||
this.holes.Add(hole);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 7eb2e763592fc45778f3a2f7718c12fd
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,190 @@
|
|||
// -----------------------------------------------------------------------
|
||||
// <copyright file="Rectangle.cs" company="">
|
||||
// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/
|
||||
// </copyright>
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
namespace UnityEngine.U2D.Animation.TriangleNet
|
||||
.Geometry
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
/// <summary>
|
||||
/// A simple rectangle class.
|
||||
/// </summary>
|
||||
internal class Rectangle
|
||||
{
|
||||
double xmin, ymin, xmax, ymax;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Rectangle" /> class.
|
||||
/// </summary>
|
||||
public Rectangle()
|
||||
{
|
||||
this.xmin = this.ymin = double.MaxValue;
|
||||
this.xmax = this.ymax = -double.MaxValue;
|
||||
}
|
||||
|
||||
public Rectangle(Rectangle other)
|
||||
: this(other.Left, other.Bottom, other.Right, other.Top)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Rectangle" /> class
|
||||
/// with predefined bounds.
|
||||
/// </summary>
|
||||
/// <param name="x">Minimum x value (left).</param>
|
||||
/// <param name="y">Minimum y value (bottom).</param>
|
||||
/// <param name="width">Width of the rectangle.</param>
|
||||
/// <param name="height">Height of the rectangle.</param>
|
||||
public Rectangle(double x, double y, double width, double height)
|
||||
{
|
||||
this.xmin = x;
|
||||
this.ymin = y;
|
||||
this.xmax = x + width;
|
||||
this.ymax = y + height;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the minimum x value (left boundary).
|
||||
/// </summary>
|
||||
public double Left
|
||||
{
|
||||
get { return xmin; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the maximum x value (right boundary).
|
||||
/// </summary>
|
||||
public double Right
|
||||
{
|
||||
get { return xmax; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the minimum y value (bottom boundary).
|
||||
/// </summary>
|
||||
public double Bottom
|
||||
{
|
||||
get { return ymin; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the maximum y value (top boundary).
|
||||
/// </summary>
|
||||
public double Top
|
||||
{
|
||||
get { return ymax; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the width of the rectangle.
|
||||
/// </summary>
|
||||
public double Width
|
||||
{
|
||||
get { return xmax - xmin; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the height of the rectangle.
|
||||
/// </summary>
|
||||
public double Height
|
||||
{
|
||||
get { return ymax - ymin; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update bounds.
|
||||
/// </summary>
|
||||
/// <param name="dx">Add dx to left and right bounds.</param>
|
||||
/// <param name="dy">Add dy to top and bottom bounds.</param>
|
||||
public void Resize(double dx, double dy)
|
||||
{
|
||||
xmin -= dx;
|
||||
xmax += dx;
|
||||
ymin -= dy;
|
||||
ymax += dy;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Expand rectangle to include given point.
|
||||
/// </summary>
|
||||
/// <param name="p">Point.</param>
|
||||
public void Expand(Point p)
|
||||
{
|
||||
xmin = Math.Min(xmin, p.x);
|
||||
ymin = Math.Min(ymin, p.y);
|
||||
xmax = Math.Max(xmax, p.x);
|
||||
ymax = Math.Max(ymax, p.y);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Expand rectangle to include a list of points.
|
||||
/// </summary>
|
||||
public void Expand(IEnumerable<Point> points)
|
||||
{
|
||||
foreach (var p in points)
|
||||
{
|
||||
Expand(p);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Expand rectangle to include given rectangle.
|
||||
/// </summary>
|
||||
/// <param name="x">X coordinate.</param>
|
||||
/// <param name="y">Y coordinate.</param>
|
||||
public void Expand(Rectangle other)
|
||||
{
|
||||
xmin = Math.Min(xmin, other.xmin);
|
||||
ymin = Math.Min(ymin, other.ymin);
|
||||
xmax = Math.Max(xmax, other.xmax);
|
||||
ymax = Math.Max(ymax, other.ymax);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if given point is inside rectangle.
|
||||
/// </summary>
|
||||
/// <param name="x">Point to check.</param>
|
||||
/// <param name="y">Point to check.</param>
|
||||
/// <returns>Return true, if rectangle contains given point.</returns>
|
||||
public bool Contains(double x, double y)
|
||||
{
|
||||
return ((x >= xmin) && (x <= xmax) && (y >= ymin) && (y <= ymax));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if given point is inside rectangle.
|
||||
/// </summary>
|
||||
/// <param name="pt">Point to check.</param>
|
||||
/// <returns>Return true, if rectangle contains given point.</returns>
|
||||
public bool Contains(Point pt)
|
||||
{
|
||||
return Contains(pt.x, pt.y);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if this rectangle contains other rectangle.
|
||||
/// </summary>
|
||||
/// <param name="other">Rectangle to check.</param>
|
||||
/// <returns>Return true, if this rectangle contains given rectangle.</returns>
|
||||
public bool Contains(Rectangle other)
|
||||
{
|
||||
return (xmin <= other.Left && other.Right <= xmax
|
||||
&& ymin <= other.Bottom && other.Top <= ymax);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if this rectangle intersects other rectangle.
|
||||
/// </summary>
|
||||
/// <param name="other">Rectangle to check.</param>
|
||||
/// <returns>Return true, if given rectangle intersects this rectangle.</returns>
|
||||
public bool Intersects(Rectangle other)
|
||||
{
|
||||
return (other.Left < xmax && xmin < other.Right
|
||||
&& other.Bottom < ymax && ymin < other.Top);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: e6dab3a6b0e864d71a92072b3d3224c3
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,64 @@
|
|||
// -----------------------------------------------------------------------
|
||||
// <copyright file="RegionPointer.cs" company="">
|
||||
// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/
|
||||
// </copyright>
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
namespace UnityEngine.U2D.Animation.TriangleNet
|
||||
.Geometry
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
/// <summary>
|
||||
/// Pointer to a region in the mesh geometry. A region is a well-defined
|
||||
/// subset of the geomerty (enclosed by subsegments).
|
||||
/// </summary>
|
||||
internal class RegionPointer
|
||||
{
|
||||
internal Point point;
|
||||
internal int id;
|
||||
internal double area;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a region area constraint.
|
||||
/// </summary>
|
||||
public double Area
|
||||
{
|
||||
get { return area; }
|
||||
set
|
||||
{
|
||||
if (value < 0.0)
|
||||
{
|
||||
throw new ArgumentException("Area constraints must not be negative.");
|
||||
}
|
||||
area = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RegionPointer" /> class.
|
||||
/// </summary>
|
||||
/// <param name="x">X coordinate of the region.</param>
|
||||
/// <param name="y">Y coordinate of the region.</param>
|
||||
/// <param name="id">Region id.</param>
|
||||
public RegionPointer(double x, double y, int id)
|
||||
: this(x, y, id, 0.0)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RegionPointer" /> class.
|
||||
/// </summary>
|
||||
/// <param name="x">X coordinate of the region.</param>
|
||||
/// <param name="y">Y coordinate of the region.</param>
|
||||
/// <param name="id">Region id.</param>
|
||||
/// <param name="area">Area constraint.</param>
|
||||
public RegionPointer(double x, double y, int id, double area)
|
||||
{
|
||||
this.point = new Point(x, y);
|
||||
this.id = id;
|
||||
this.area = area;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 5c8f0437817e84603a4785292a7e5242
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,93 @@
|
|||
// -----------------------------------------------------------------------
|
||||
// <copyright file="Segment.cs" company="">
|
||||
// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/
|
||||
// </copyright>
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
namespace UnityEngine.U2D.Animation.TriangleNet
|
||||
.Geometry
|
||||
{
|
||||
using System;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a straight line segment in 2D space.
|
||||
/// </summary>
|
||||
internal class Segment : ISegment
|
||||
{
|
||||
Vertex v0;
|
||||
Vertex v1;
|
||||
|
||||
int label;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the segments boundary mark.
|
||||
/// </summary>
|
||||
public int Label
|
||||
{
|
||||
get { return label; }
|
||||
set { label = value; }
|
||||
}
|
||||
/// <summary>
|
||||
/// Gets the first endpoints index.
|
||||
/// </summary>
|
||||
public int P0
|
||||
{
|
||||
get { return v0.id; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the second endpoints index.
|
||||
/// </summary>
|
||||
public int P1
|
||||
{
|
||||
get { return v1.id; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Segment" /> class.
|
||||
/// </summary>
|
||||
public Segment(Vertex v0, Vertex v1)
|
||||
: this(v0, v1, 0)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Segment" /> class.
|
||||
/// </summary>
|
||||
public Segment(Vertex v0, Vertex v1, int label)
|
||||
{
|
||||
this.v0 = v0;
|
||||
this.v1 = v1;
|
||||
|
||||
this.label = label;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the specified segment endpoint.
|
||||
/// </summary>
|
||||
/// <param name="index">The endpoint index (0 or 1).</param>
|
||||
/// <returns></returns>
|
||||
public Vertex GetVertex(int index)
|
||||
{
|
||||
if (index == 0)
|
||||
{
|
||||
return v0;
|
||||
}
|
||||
|
||||
if (index == 1)
|
||||
{
|
||||
return v1;
|
||||
}
|
||||
|
||||
throw new IndexOutOfRangeException();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// WARNING: not implemented.
|
||||
/// </summary>
|
||||
public ITriangle GetTriangle(int index)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 64b0cc51c72d04973b2516dd2c8137b6
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,127 @@
|
|||
// -----------------------------------------------------------------------
|
||||
// <copyright file="Vertex.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
|
||||
.Geometry
|
||||
{
|
||||
using System;
|
||||
using Animation.TriangleNet.Topology;
|
||||
|
||||
/// <summary>
|
||||
/// The vertex data structure.
|
||||
/// </summary>
|
||||
internal class Vertex : Point
|
||||
{
|
||||
// Hash for dictionary. Will be set by mesh instance.
|
||||
internal int hash;
|
||||
|
||||
#if USE_ATTRIBS
|
||||
internal double[] attributes;
|
||||
#endif
|
||||
internal VertexType type;
|
||||
internal Otri tri;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Vertex" /> class.
|
||||
/// </summary>
|
||||
public Vertex()
|
||||
: this(0, 0, 0)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Vertex" /> class.
|
||||
/// </summary>
|
||||
/// <param name="x">The x coordinate of the vertex.</param>
|
||||
/// <param name="y">The y coordinate of the vertex.</param>
|
||||
public Vertex(double x, double y)
|
||||
: this(x, y, 0)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Vertex" /> class.
|
||||
/// </summary>
|
||||
/// <param name="x">The x coordinate of the vertex.</param>
|
||||
/// <param name="y">The y coordinate of the vertex.</param>
|
||||
/// <param name="mark">The boundary mark.</param>
|
||||
public Vertex(double x, double y, int mark)
|
||||
: base(x, y, mark)
|
||||
{
|
||||
this.type = VertexType.InputVertex;
|
||||
}
|
||||
|
||||
#if USE_ATTRIBS
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Vertex" /> class.
|
||||
/// </summary>
|
||||
/// <param name="x">The x coordinate of the vertex.</param>
|
||||
/// <param name="y">The y coordinate of the vertex.</param>
|
||||
/// <param name="mark">The boundary mark.</param>
|
||||
/// <param name="attribs">The number of point attributes.</param>
|
||||
public Vertex(double x, double y, int mark, int attribs)
|
||||
: this(x, y, mark)
|
||||
{
|
||||
if (attribs > 0)
|
||||
{
|
||||
this.attributes = new double[attribs];
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#region Public properties
|
||||
|
||||
#if USE_ATTRIBS
|
||||
/// <summary>
|
||||
/// Gets the vertex attributes (may be null).
|
||||
/// </summary>
|
||||
public double[] Attributes
|
||||
{
|
||||
get { return this.attributes; }
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Gets the vertex type.
|
||||
/// </summary>
|
||||
public VertexType Type
|
||||
{
|
||||
get { return this.type; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the specified coordinate of the vertex.
|
||||
/// </summary>
|
||||
/// <param name="i">Coordinate index.</param>
|
||||
/// <returns>X coordinate, if index is 0, Y coordinate, if index is 1.</returns>
|
||||
public double this[int i]
|
||||
{
|
||||
get
|
||||
{
|
||||
if (i == 0)
|
||||
{
|
||||
return x;
|
||||
}
|
||||
|
||||
if (i == 1)
|
||||
{
|
||||
return y;
|
||||
}
|
||||
|
||||
throw new ArgumentOutOfRangeException("Index must be 0 or 1.");
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return this.hash;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 32c23f1f2292142599f01204e68664c9
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: d1b897e6d67a84472b21b63acc3baac7
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,264 @@
|
|||
// -----------------------------------------------------------------------
|
||||
// <copyright file="DebugWriter.cs" company="">
|
||||
// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/
|
||||
// </copyright>
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
namespace UnityEngine.U2D.Animation.TriangleNet
|
||||
.IO
|
||||
{
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Text;
|
||||
using Animation.TriangleNet.Topology;
|
||||
using Animation.TriangleNet.Geometry;
|
||||
|
||||
/// <summary>
|
||||
/// Writes a the current mesh into a text file.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// File format:
|
||||
///
|
||||
/// num_nodes
|
||||
/// id_1 nx ny mark
|
||||
/// ...
|
||||
/// id_n nx ny mark
|
||||
///
|
||||
/// num_segs
|
||||
/// id_1 p1 p2 mark
|
||||
/// ...
|
||||
/// id_n p1 p2 mark
|
||||
///
|
||||
/// num_tris
|
||||
/// id_1 p1 p2 p3 n1 n2 n3
|
||||
/// ...
|
||||
/// id_n p1 p2 p3 n1 n2 n3
|
||||
/// </remarks>
|
||||
class DebugWriter
|
||||
{
|
||||
static NumberFormatInfo nfi = CultureInfo.InvariantCulture.NumberFormat;
|
||||
|
||||
int iteration;
|
||||
string session;
|
||||
StreamWriter stream;
|
||||
string tmpFile;
|
||||
int[] vertices;
|
||||
int triangles;
|
||||
|
||||
#region Singleton pattern
|
||||
|
||||
private static readonly DebugWriter instance = new DebugWriter();
|
||||
|
||||
// Explicit static constructor to tell C# compiler
|
||||
// not to mark type as beforefieldinit
|
||||
static DebugWriter() {}
|
||||
|
||||
private DebugWriter() {}
|
||||
|
||||
internal static DebugWriter Session
|
||||
{
|
||||
get
|
||||
{
|
||||
return instance;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Start a new session with given name.
|
||||
/// </summary>
|
||||
/// <param name="name">Name of the session (and output files).</param>
|
||||
public void Start(string session)
|
||||
{
|
||||
this.iteration = 0;
|
||||
this.session = session;
|
||||
|
||||
if (this.stream != null)
|
||||
{
|
||||
throw new Exception("A session is active. Finish before starting a new.");
|
||||
}
|
||||
|
||||
this.tmpFile = Path.GetTempFileName();
|
||||
this.stream = new StreamWriter(tmpFile);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Write complete mesh to file.
|
||||
/// </summary>
|
||||
public void Write(Mesh mesh, bool skip = false)
|
||||
{
|
||||
this.WriteMesh(mesh, skip);
|
||||
|
||||
this.triangles = mesh.Triangles.Count;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finish this session.
|
||||
/// </summary>
|
||||
public void Finish()
|
||||
{
|
||||
this.Finish(session + ".mshx");
|
||||
}
|
||||
|
||||
private void Finish(string path)
|
||||
{
|
||||
if (stream != null)
|
||||
{
|
||||
stream.Flush();
|
||||
stream.Dispose();
|
||||
stream = null;
|
||||
|
||||
string header = "#!N" + this.iteration + Environment.NewLine;
|
||||
|
||||
using (var gzFile = new FileStream(path, FileMode.Create))
|
||||
{
|
||||
using (var gzStream = new GZipStream(gzFile, CompressionMode.Compress, false))
|
||||
{
|
||||
byte[] bytes = Encoding.UTF8.GetBytes(header);
|
||||
gzStream.Write(bytes, 0, bytes.Length);
|
||||
|
||||
// TODO: read with stream
|
||||
bytes = File.ReadAllBytes(tmpFile);
|
||||
gzStream.Write(bytes, 0, bytes.Length);
|
||||
}
|
||||
}
|
||||
|
||||
File.Delete(this.tmpFile);
|
||||
}
|
||||
}
|
||||
|
||||
private void WriteGeometry(IPolygon geometry)
|
||||
{
|
||||
stream.WriteLine("#!G{0}", this.iteration++);
|
||||
}
|
||||
|
||||
private void WriteMesh(Mesh mesh, bool skip)
|
||||
{
|
||||
// Mesh may have changed, but we choose to skip
|
||||
if (triangles == mesh.triangles.Count && skip)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Header line
|
||||
stream.WriteLine("#!M{0}", this.iteration++);
|
||||
|
||||
Vertex p1, p2, p3;
|
||||
|
||||
if (VerticesChanged(mesh))
|
||||
{
|
||||
HashVertices(mesh);
|
||||
|
||||
// Number of vertices.
|
||||
stream.WriteLine("{0}", mesh.vertices.Count);
|
||||
|
||||
foreach (var v in mesh.vertices.Values)
|
||||
{
|
||||
// Vertex number, x and y coordinates and marker.
|
||||
stream.WriteLine("{0} {1} {2} {3}", v.id, v.x.ToString(nfi), v.y.ToString(nfi), v.label);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
stream.WriteLine("0");
|
||||
}
|
||||
|
||||
// Number of segments.
|
||||
stream.WriteLine("{0}", mesh.subsegs.Count);
|
||||
|
||||
Osub subseg = default(Osub);
|
||||
subseg.orient = 0;
|
||||
|
||||
foreach (var item in mesh.subsegs.Values)
|
||||
{
|
||||
if (item.hash <= 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
subseg.seg = item;
|
||||
|
||||
p1 = subseg.Org();
|
||||
p2 = subseg.Dest();
|
||||
|
||||
// Segment number, indices of its two endpoints, and marker.
|
||||
stream.WriteLine("{0} {1} {2} {3}", subseg.seg.hash, p1.id, p2.id, subseg.seg.boundary);
|
||||
}
|
||||
|
||||
Otri tri = default(Otri), trisym = default(Otri);
|
||||
tri.orient = 0;
|
||||
|
||||
int n1, n2, n3, h1, h2, h3;
|
||||
|
||||
// Number of triangles.
|
||||
stream.WriteLine("{0}", mesh.triangles.Count);
|
||||
|
||||
foreach (var item in mesh.triangles)
|
||||
{
|
||||
tri.tri = item;
|
||||
|
||||
p1 = tri.Org();
|
||||
p2 = tri.Dest();
|
||||
p3 = tri.Apex();
|
||||
|
||||
h1 = (p1 == null) ? -1 : p1.id;
|
||||
h2 = (p2 == null) ? -1 : p2.id;
|
||||
h3 = (p3 == null) ? -1 : p3.id;
|
||||
|
||||
// Triangle number, indices for three vertices.
|
||||
stream.Write("{0} {1} {2} {3}", tri.tri.hash, h1, h2, h3);
|
||||
|
||||
tri.orient = 1;
|
||||
tri.Sym(ref trisym);
|
||||
n1 = trisym.tri.hash;
|
||||
|
||||
tri.orient = 2;
|
||||
tri.Sym(ref trisym);
|
||||
n2 = trisym.tri.hash;
|
||||
|
||||
tri.orient = 0;
|
||||
tri.Sym(ref trisym);
|
||||
n3 = trisym.tri.hash;
|
||||
|
||||
// Neighboring triangle numbers.
|
||||
stream.WriteLine(" {0} {1} {2}", n1, n2, n3);
|
||||
}
|
||||
}
|
||||
|
||||
private bool VerticesChanged(Mesh mesh)
|
||||
{
|
||||
if (vertices == null || mesh.Vertices.Count != vertices.Length)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
int i = 0;
|
||||
foreach (var v in mesh.Vertices)
|
||||
{
|
||||
if (v.id != vertices[i++])
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void HashVertices(Mesh mesh)
|
||||
{
|
||||
if (vertices == null || mesh.Vertices.Count != vertices.Length)
|
||||
{
|
||||
vertices = new int[mesh.Vertices.Count];
|
||||
}
|
||||
|
||||
int i = 0;
|
||||
foreach (var v in mesh.Vertices)
|
||||
{
|
||||
vertices[i++] = v.id;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 56ae7d772de344c1a9a4484e952b7d33
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,127 @@
|
|||
// -----------------------------------------------------------------------
|
||||
// <copyright file="FileProcessor.cs" company="">
|
||||
// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/
|
||||
// </copyright>
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
namespace UnityEngine.U2D.Animation.TriangleNet
|
||||
.IO
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Animation.TriangleNet.Geometry;
|
||||
using Animation.TriangleNet.Meshing;
|
||||
|
||||
internal static class FileProcessor
|
||||
{
|
||||
static List<IFileFormat> formats;
|
||||
|
||||
static FileProcessor()
|
||||
{
|
||||
formats = new List<IFileFormat>();
|
||||
|
||||
// Add Triangle file format as default.
|
||||
formats.Add(new TriangleFormat());
|
||||
}
|
||||
|
||||
internal static void Add(IFileFormat format)
|
||||
{
|
||||
formats.Add(format);
|
||||
}
|
||||
|
||||
internal static bool IsSupported(string file)
|
||||
{
|
||||
foreach (var format in formats)
|
||||
{
|
||||
if (format.IsSupported(file))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
#region Polygon read/write
|
||||
|
||||
/// <summary>
|
||||
/// Read a file containing polygon geometry.
|
||||
/// </summary>
|
||||
/// <param name="filename">The path of the file to read.</param>
|
||||
/// <returns>An instance of the <see cref="IPolygon" /> class.</returns>
|
||||
internal static IPolygon Read(string filename)
|
||||
{
|
||||
foreach (IPolygonFormat format in formats)
|
||||
{
|
||||
if (format != null && format.IsSupported(filename))
|
||||
{
|
||||
return format.Read(filename);
|
||||
}
|
||||
}
|
||||
|
||||
throw new Exception("File format not supported.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Save a polygon geometry to disk.
|
||||
/// </summary>
|
||||
/// <param name="mesh">An instance of the <see cref="IPolygon" /> class.</param>
|
||||
/// <param name="filename">The path of the file to save.</param>
|
||||
internal static void Write(IPolygon polygon, string filename)
|
||||
{
|
||||
foreach (IPolygonFormat format in formats)
|
||||
{
|
||||
if (format != null && format.IsSupported(filename))
|
||||
{
|
||||
format.Write(polygon, filename);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
throw new Exception("File format not supported.");
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Mesh read/write
|
||||
|
||||
/// <summary>
|
||||
/// Read a file containing a mesh.
|
||||
/// </summary>
|
||||
/// <param name="filename">The path of the file to read.</param>
|
||||
/// <returns>An instance of the <see cref="IMesh" /> interface.</returns>
|
||||
internal static IMesh Import(string filename)
|
||||
{
|
||||
foreach (IMeshFormat format in formats)
|
||||
{
|
||||
if (format != null && format.IsSupported(filename))
|
||||
{
|
||||
return format.Import(filename);
|
||||
}
|
||||
}
|
||||
|
||||
throw new Exception("File format not supported.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Save a mesh to disk.
|
||||
/// </summary>
|
||||
/// <param name="mesh">An instance of the <see cref="IMesh" /> interface.</param>
|
||||
/// <param name="filename">The path of the file to save.</param>
|
||||
internal static void Write(IMesh mesh, string filename)
|
||||
{
|
||||
foreach (IMeshFormat format in formats)
|
||||
{
|
||||
if (format != null && format.IsSupported(filename))
|
||||
{
|
||||
format.Write(mesh, filename);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
throw new Exception("File format not supported.");
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: ca1b61f918cd44aec95c1fcde79a51f9
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,14 @@
|
|||
// -----------------------------------------------------------------------
|
||||
// <copyright file="IFileFormat.cs" company="">
|
||||
// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/
|
||||
// </copyright>
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
namespace UnityEngine.U2D.Animation.TriangleNet
|
||||
.IO
|
||||
{
|
||||
internal interface IFileFormat
|
||||
{
|
||||
bool IsSupported(string file);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 189b37483531447aa818511aea18b139
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,39 @@
|
|||
// -----------------------------------------------------------------------
|
||||
// <copyright file="IMeshFormat.cs" company="">
|
||||
// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/
|
||||
// </copyright>
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
namespace UnityEngine.U2D.Animation.TriangleNet
|
||||
.IO
|
||||
{
|
||||
using System.IO;
|
||||
using Animation.TriangleNet.Meshing;
|
||||
|
||||
/// <summary>
|
||||
/// Interface for mesh I/O.
|
||||
/// </summary>
|
||||
internal interface IMeshFormat : IFileFormat
|
||||
{
|
||||
/// <summary>
|
||||
/// Read a file containing a mesh.
|
||||
/// </summary>
|
||||
/// <param name="filename">The path of the file to read.</param>
|
||||
/// <returns>An instance of the <see cref="IMesh" /> interface.</returns>
|
||||
IMesh Import(string filename);
|
||||
|
||||
/// <summary>
|
||||
/// Save a mesh to disk.
|
||||
/// </summary>
|
||||
/// <param name="mesh">An instance of the <see cref="IMesh" /> interface.</param>
|
||||
/// <param name="filename">The path of the file to save.</param>
|
||||
void Write(IMesh mesh, string filename);
|
||||
|
||||
/// <summary>
|
||||
/// Save a mesh to a <see cref="Stream" />.
|
||||
/// </summary>
|
||||
/// <param name="mesh">An instance of the <see cref="IMesh" /> interface.</param>
|
||||
/// <param name="stream">The stream to save to.</param>
|
||||
void Write(IMesh mesh, Stream stream);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 6376b9aaff8504ac697cd831a610f1e8
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,39 @@
|
|||
// -----------------------------------------------------------------------
|
||||
// <copyright file="IPolygonFormat.cs" company="">
|
||||
// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/
|
||||
// </copyright>
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
namespace UnityEngine.U2D.Animation.TriangleNet
|
||||
.IO
|
||||
{
|
||||
using System.IO;
|
||||
using Animation.TriangleNet.Geometry;
|
||||
|
||||
/// <summary>
|
||||
/// Interface for geometry input.
|
||||
/// </summary>
|
||||
internal interface IPolygonFormat : IFileFormat
|
||||
{
|
||||
/// <summary>
|
||||
/// Read a file containing polygon geometry.
|
||||
/// </summary>
|
||||
/// <param name="filename">The path of the file to read.</param>
|
||||
/// <returns>An instance of the <see cref="IPolygon" /> class.</returns>
|
||||
IPolygon Read(string filename);
|
||||
|
||||
/// <summary>
|
||||
/// Save a polygon geometry to disk.
|
||||
/// </summary>
|
||||
/// <param name="polygon">An instance of the <see cref="IPolygon" /> class.</param>
|
||||
/// <param name="filename">The path of the file to save.</param>
|
||||
void Write(IPolygon polygon, string filename);
|
||||
|
||||
/// <summary>
|
||||
/// Save a polygon geometry to a <see cref="Stream" />.
|
||||
/// </summary>
|
||||
/// <param name="polygon">An instance of the <see cref="IPolygon" /> class.</param>
|
||||
/// <param name="stream">The stream to save to.</param>
|
||||
void Write(IPolygon polygon, Stream stream);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: c5038e46090aa4f809c75db148ca3b5f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,86 @@
|
|||
// -----------------------------------------------------------------------
|
||||
// <copyright file="InputTriangle.cs" company="">
|
||||
// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/
|
||||
// </copyright>
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
namespace UnityEngine.U2D.Animation.TriangleNet
|
||||
.IO
|
||||
{
|
||||
using Animation.TriangleNet.Topology;
|
||||
using Animation.TriangleNet.Geometry;
|
||||
|
||||
/// <summary>
|
||||
/// Simple triangle class for input.
|
||||
/// </summary>
|
||||
internal class InputTriangle : ITriangle
|
||||
{
|
||||
internal int[] vertices;
|
||||
internal int label;
|
||||
internal double area;
|
||||
|
||||
public InputTriangle(int p0, int p1, int p2)
|
||||
{
|
||||
this.vertices = new int[] { p0, p1, p2 };
|
||||
}
|
||||
|
||||
#region Public properties
|
||||
|
||||
/// <summary>
|
||||
/// Gets the triangle id.
|
||||
/// </summary>
|
||||
public int ID
|
||||
{
|
||||
get { return 0; }
|
||||
set {}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Region ID the triangle belongs to.
|
||||
/// </summary>
|
||||
public int Label
|
||||
{
|
||||
get { return label; }
|
||||
set { label = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the triangle area constraint.
|
||||
/// </summary>
|
||||
public double Area
|
||||
{
|
||||
get { return area; }
|
||||
set { area = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the specified corners vertex.
|
||||
/// </summary>
|
||||
public Vertex GetVertex(int index)
|
||||
{
|
||||
return null; // TODO: throw NotSupportedException?
|
||||
}
|
||||
|
||||
public int GetVertexID(int index)
|
||||
{
|
||||
return vertices[index];
|
||||
}
|
||||
|
||||
public ITriangle GetNeighbor(int index)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public int GetNeighborID(int index)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
public ISegment GetSegment(int index)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 8586f5d059bef4957927fa6f9b5f33f2
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,92 @@
|
|||
// -----------------------------------------------------------------------
|
||||
// <copyright file="TriangleFormat.cs" company="">
|
||||
// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/
|
||||
// </copyright>
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
namespace UnityEngine.U2D.Animation.TriangleNet
|
||||
.IO
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using Animation.TriangleNet.Geometry;
|
||||
using Animation.TriangleNet.Meshing;
|
||||
|
||||
/// <summary>
|
||||
/// Implements geometry and mesh file formats of the the original Triangle code.
|
||||
/// </summary>
|
||||
internal class TriangleFormat : IPolygonFormat, IMeshFormat
|
||||
{
|
||||
public bool IsSupported(string file)
|
||||
{
|
||||
string ext = Path.GetExtension(file).ToLower();
|
||||
|
||||
if (ext == ".node" || ext == ".poly" || ext == ".ele")
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public IMesh Import(string filename)
|
||||
{
|
||||
string ext = Path.GetExtension(filename);
|
||||
|
||||
if (ext == ".node" || ext == ".poly" || ext == ".ele")
|
||||
{
|
||||
List<ITriangle> triangles;
|
||||
Polygon geometry;
|
||||
|
||||
(new TriangleReader()).Read(filename, out geometry, out triangles);
|
||||
|
||||
if (geometry != null && triangles != null)
|
||||
{
|
||||
return Converter.ToMesh(geometry, triangles.ToArray());
|
||||
}
|
||||
}
|
||||
|
||||
throw new NotSupportedException("Could not load '" + filename + "' file.");
|
||||
}
|
||||
|
||||
public void Write(IMesh mesh, string filename)
|
||||
{
|
||||
var writer = new TriangleWriter();
|
||||
|
||||
writer.WritePoly((Mesh)mesh, Path.ChangeExtension(filename, ".poly"));
|
||||
writer.WriteElements((Mesh)mesh, Path.ChangeExtension(filename, ".ele"));
|
||||
}
|
||||
|
||||
public void Write(IMesh mesh, Stream stream)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public IPolygon Read(string filename)
|
||||
{
|
||||
string ext = Path.GetExtension(filename);
|
||||
|
||||
if (ext == ".node")
|
||||
{
|
||||
return (new TriangleReader()).ReadNodeFile(filename);
|
||||
}
|
||||
else if (ext == ".poly")
|
||||
{
|
||||
return (new TriangleReader()).ReadPolyFile(filename);
|
||||
}
|
||||
|
||||
throw new NotSupportedException("File format '" + ext + "' not supported.");
|
||||
}
|
||||
|
||||
public void Write(IPolygon polygon, string filename)
|
||||
{
|
||||
(new TriangleWriter()).WritePoly(polygon, filename);
|
||||
}
|
||||
|
||||
public void Write(IPolygon polygon, Stream stream)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 4a1337b0388344c16a0e94a38038934f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,757 @@
|
|||
// -----------------------------------------------------------------------
|
||||
// <copyright file="TriangleReader.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
|
||||
.IO
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using Animation.TriangleNet.Geometry;
|
||||
|
||||
/// <summary>
|
||||
/// Helper methods for reading Triangle file formats.
|
||||
/// </summary>
|
||||
internal class TriangleReader
|
||||
{
|
||||
static NumberFormatInfo nfi = NumberFormatInfo.InvariantInfo;
|
||||
|
||||
int startIndex = 0;
|
||||
|
||||
#region Helper methods
|
||||
|
||||
private bool TryReadLine(StreamReader reader, out string[] token)
|
||||
{
|
||||
token = null;
|
||||
|
||||
if (reader.EndOfStream)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
string line = reader.ReadLine().Trim();
|
||||
|
||||
while (IsStringNullOrWhiteSpace(line) || line.StartsWith("#"))
|
||||
{
|
||||
if (reader.EndOfStream)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
line = reader.ReadLine().Trim();
|
||||
}
|
||||
|
||||
token = line.Split(new char[] { ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read vertex information of the given line.
|
||||
/// </summary>
|
||||
/// <param name="data">The input geometry.</param>
|
||||
/// <param name="index">The current vertex index.</param>
|
||||
/// <param name="line">The current line.</param>
|
||||
/// <param name="attributes">Number of point attributes</param>
|
||||
/// <param name="marks">Number of point markers (0 or 1)</param>
|
||||
private void ReadVertex(List<Vertex> data, int index, string[] line, int attributes, int marks)
|
||||
{
|
||||
double x = double.Parse(line[1], nfi);
|
||||
double y = double.Parse(line[2], nfi);
|
||||
|
||||
var v = new Vertex(x, y);
|
||||
|
||||
// Read a vertex marker.
|
||||
if (marks > 0 && line.Length > 3 + attributes)
|
||||
{
|
||||
v.Label = int.Parse(line[3 + attributes]);
|
||||
}
|
||||
|
||||
if (attributes > 0)
|
||||
{
|
||||
#if USE_ATTRIBS
|
||||
var attribs = new double[attributes];
|
||||
|
||||
// Read the vertex attributes.
|
||||
for (int j = 0; j < attributes; j++)
|
||||
{
|
||||
if (line.Length > 3 + j)
|
||||
{
|
||||
attribs[j] = double.Parse(line[3 + j], nfi);
|
||||
}
|
||||
}
|
||||
|
||||
v.attributes = attribs;
|
||||
#endif
|
||||
}
|
||||
|
||||
data.Add(v);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Main I/O methods
|
||||
|
||||
/// <summary>
|
||||
/// Reads geometry information from .node or .poly files.
|
||||
/// </summary>
|
||||
public void Read(string filename, out Polygon polygon)
|
||||
{
|
||||
polygon = null;
|
||||
|
||||
string path = Path.ChangeExtension(filename, ".poly");
|
||||
|
||||
if (File.Exists(path))
|
||||
{
|
||||
polygon = ReadPolyFile(path);
|
||||
}
|
||||
else
|
||||
{
|
||||
path = Path.ChangeExtension(filename, ".node");
|
||||
polygon = ReadNodeFile(path);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a mesh from .node, .poly or .ele files.
|
||||
/// </summary>
|
||||
public void Read(string filename, out Polygon geometry, out List<ITriangle> triangles)
|
||||
{
|
||||
triangles = null;
|
||||
|
||||
Read(filename, out geometry);
|
||||
|
||||
string path = Path.ChangeExtension(filename, ".ele");
|
||||
|
||||
if (File.Exists(path) && geometry != null)
|
||||
{
|
||||
triangles = ReadEleFile(path);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads geometry information from .node or .poly files.
|
||||
/// </summary>
|
||||
public IPolygon Read(string filename)
|
||||
{
|
||||
Polygon geometry = null;
|
||||
|
||||
Read(filename, out geometry);
|
||||
|
||||
return geometry;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Read the vertices from a file, which may be a .node or .poly file.
|
||||
/// </summary>
|
||||
/// <param name="nodefilename"></param>
|
||||
/// <remarks>Will NOT read associated .ele by default.</remarks>
|
||||
public Polygon ReadNodeFile(string nodefilename)
|
||||
{
|
||||
return ReadNodeFile(nodefilename, false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read the vertices from a file, which may be a .node or .poly file.
|
||||
/// </summary>
|
||||
/// <param name="nodefilename"></param>
|
||||
/// <param name="readElements"></param>
|
||||
public Polygon ReadNodeFile(string nodefilename, bool readElements)
|
||||
{
|
||||
Polygon data;
|
||||
|
||||
startIndex = 0;
|
||||
|
||||
string[] line;
|
||||
int invertices = 0, attributes = 0, nodemarkers = 0;
|
||||
|
||||
using (var reader = new StreamReader(nodefilename))
|
||||
{
|
||||
if (!TryReadLine(reader, out line))
|
||||
{
|
||||
throw new Exception("Can't read input file.");
|
||||
}
|
||||
|
||||
// Read number of vertices, number of dimensions, number of vertex
|
||||
// attributes, and number of boundary markers.
|
||||
invertices = int.Parse(line[0]);
|
||||
|
||||
if (invertices < 3)
|
||||
{
|
||||
throw new Exception("Input must have at least three input vertices.");
|
||||
}
|
||||
|
||||
if (line.Length > 1)
|
||||
{
|
||||
if (int.Parse(line[1]) != 2)
|
||||
{
|
||||
throw new Exception("Triangle only works with two-dimensional meshes.");
|
||||
}
|
||||
}
|
||||
|
||||
if (line.Length > 2)
|
||||
{
|
||||
attributes = int.Parse(line[2]);
|
||||
}
|
||||
|
||||
if (line.Length > 3)
|
||||
{
|
||||
nodemarkers = int.Parse(line[3]);
|
||||
}
|
||||
|
||||
data = new Polygon(invertices);
|
||||
|
||||
// Read the vertices.
|
||||
if (invertices > 0)
|
||||
{
|
||||
for (int i = 0; i < invertices; i++)
|
||||
{
|
||||
if (!TryReadLine(reader, out line))
|
||||
{
|
||||
throw new Exception("Can't read input file (vertices).");
|
||||
}
|
||||
|
||||
if (line.Length < 3)
|
||||
{
|
||||
throw new Exception("Invalid vertex.");
|
||||
}
|
||||
|
||||
if (i == 0)
|
||||
{
|
||||
startIndex = int.Parse(line[0], nfi);
|
||||
}
|
||||
|
||||
ReadVertex(data.Points, i, line, attributes, nodemarkers);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (readElements)
|
||||
{
|
||||
// Read area file
|
||||
string elefile = Path.ChangeExtension(nodefilename, ".ele");
|
||||
if (File.Exists(elefile))
|
||||
{
|
||||
ReadEleFile(elefile, true);
|
||||
}
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read the vertices and segments from a .poly file.
|
||||
/// </summary>
|
||||
/// <param name="polyfilename"></param>
|
||||
/// <remarks>Will NOT read associated .ele by default.</remarks>
|
||||
public Polygon ReadPolyFile(string polyfilename)
|
||||
{
|
||||
return ReadPolyFile(polyfilename, false, false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read the vertices and segments from a .poly file.
|
||||
/// </summary>
|
||||
/// <param name="polyfilename"></param>
|
||||
/// <param name="readElements">If true, look for an associated .ele file.</param>
|
||||
/// <remarks>Will NOT read associated .area by default.</remarks>
|
||||
public Polygon ReadPolyFile(string polyfilename, bool readElements)
|
||||
{
|
||||
return ReadPolyFile(polyfilename, readElements, false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read the vertices and segments from a .poly file.
|
||||
/// </summary>
|
||||
/// <param name="polyfilename"></param>
|
||||
/// <param name="readElements">If true, look for an associated .ele file.</param>
|
||||
/// <param name="readElements">If true, look for an associated .area file.</param>
|
||||
public Polygon ReadPolyFile(string polyfilename, bool readElements, bool readArea)
|
||||
{
|
||||
// Read poly file
|
||||
Polygon data;
|
||||
|
||||
startIndex = 0;
|
||||
|
||||
string[] line;
|
||||
int invertices = 0, attributes = 0, nodemarkers = 0;
|
||||
|
||||
using (var reader = new StreamReader(polyfilename))
|
||||
{
|
||||
if (!TryReadLine(reader, out line))
|
||||
{
|
||||
throw new Exception("Can't read input file.");
|
||||
}
|
||||
|
||||
// Read number of vertices, number of dimensions, number of vertex
|
||||
// attributes, and number of boundary markers.
|
||||
invertices = int.Parse(line[0]);
|
||||
|
||||
if (line.Length > 1)
|
||||
{
|
||||
if (int.Parse(line[1]) != 2)
|
||||
{
|
||||
throw new Exception("Triangle only works with two-dimensional meshes.");
|
||||
}
|
||||
}
|
||||
|
||||
if (line.Length > 2)
|
||||
{
|
||||
attributes = int.Parse(line[2]);
|
||||
}
|
||||
|
||||
if (line.Length > 3)
|
||||
{
|
||||
nodemarkers = int.Parse(line[3]);
|
||||
}
|
||||
|
||||
// Read the vertices.
|
||||
if (invertices > 0)
|
||||
{
|
||||
data = new Polygon(invertices);
|
||||
|
||||
for (int i = 0; i < invertices; i++)
|
||||
{
|
||||
if (!TryReadLine(reader, out line))
|
||||
{
|
||||
throw new Exception("Can't read input file (vertices).");
|
||||
}
|
||||
|
||||
if (line.Length < 3)
|
||||
{
|
||||
throw new Exception("Invalid vertex.");
|
||||
}
|
||||
|
||||
if (i == 0)
|
||||
{
|
||||
// Set the start index!
|
||||
startIndex = int.Parse(line[0], nfi);
|
||||
}
|
||||
|
||||
ReadVertex(data.Points, i, line, attributes, nodemarkers);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// If the .poly file claims there are zero vertices, that means that
|
||||
// the vertices should be read from a separate .node file.
|
||||
data = ReadNodeFile(Path.ChangeExtension(polyfilename, ".node"));
|
||||
|
||||
invertices = data.Points.Count;
|
||||
}
|
||||
|
||||
var points = data.Points;
|
||||
|
||||
if (points.Count == 0)
|
||||
{
|
||||
throw new Exception("No nodes available.");
|
||||
}
|
||||
|
||||
// Read the segments from a .poly file.
|
||||
|
||||
// Read number of segments and number of boundary markers.
|
||||
if (!TryReadLine(reader, out line))
|
||||
{
|
||||
throw new Exception("Can't read input file (segments).");
|
||||
}
|
||||
|
||||
int insegments = int.Parse(line[0]);
|
||||
|
||||
int segmentmarkers = 0;
|
||||
if (line.Length > 1)
|
||||
{
|
||||
segmentmarkers = int.Parse(line[1]);
|
||||
}
|
||||
|
||||
int end1, end2, mark;
|
||||
// Read and insert the segments.
|
||||
for (int i = 0; i < insegments; i++)
|
||||
{
|
||||
if (!TryReadLine(reader, out line))
|
||||
{
|
||||
throw new Exception("Can't read input file (segments).");
|
||||
}
|
||||
|
||||
if (line.Length < 3)
|
||||
{
|
||||
throw new Exception("Segment has no endpoints.");
|
||||
}
|
||||
|
||||
// TODO: startIndex ok?
|
||||
end1 = int.Parse(line[1]) - startIndex;
|
||||
end2 = int.Parse(line[2]) - startIndex;
|
||||
mark = 0;
|
||||
|
||||
if (segmentmarkers > 0 && line.Length > 3)
|
||||
{
|
||||
mark = int.Parse(line[3]);
|
||||
}
|
||||
|
||||
if ((end1 < 0) || (end1 >= invertices))
|
||||
{
|
||||
if (Log.Verbose)
|
||||
{
|
||||
Log.Instance.Warning("Invalid first endpoint of segment.",
|
||||
"MeshReader.ReadPolyfile()");
|
||||
}
|
||||
}
|
||||
else if ((end2 < 0) || (end2 >= invertices))
|
||||
{
|
||||
if (Log.Verbose)
|
||||
{
|
||||
Log.Instance.Warning("Invalid second endpoint of segment.",
|
||||
"MeshReader.ReadPolyfile()");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
data.Add(new Segment(points[end1], points[end2], mark));
|
||||
}
|
||||
}
|
||||
|
||||
// Read holes from a .poly file.
|
||||
|
||||
// Read the holes.
|
||||
if (!TryReadLine(reader, out line))
|
||||
{
|
||||
throw new Exception("Can't read input file (holes).");
|
||||
}
|
||||
|
||||
int holes = int.Parse(line[0]);
|
||||
if (holes > 0)
|
||||
{
|
||||
for (int i = 0; i < holes; i++)
|
||||
{
|
||||
if (!TryReadLine(reader, out line))
|
||||
{
|
||||
throw new Exception("Can't read input file (holes).");
|
||||
}
|
||||
|
||||
if (line.Length < 3)
|
||||
{
|
||||
throw new Exception("Invalid hole.");
|
||||
}
|
||||
|
||||
data.Holes.Add(new Point(double.Parse(line[1], nfi),
|
||||
double.Parse(line[2], nfi)));
|
||||
}
|
||||
}
|
||||
|
||||
// Read area constraints (optional).
|
||||
if (TryReadLine(reader, out line))
|
||||
{
|
||||
int id, regions = int.Parse(line[0]);
|
||||
|
||||
if (regions > 0)
|
||||
{
|
||||
for (int i = 0; i < regions; i++)
|
||||
{
|
||||
if (!TryReadLine(reader, out line))
|
||||
{
|
||||
throw new Exception("Can't read input file (region).");
|
||||
}
|
||||
|
||||
if (line.Length < 4)
|
||||
{
|
||||
throw new Exception("Invalid region attributes.");
|
||||
}
|
||||
|
||||
if (!int.TryParse(line[3], out id))
|
||||
{
|
||||
id = i;
|
||||
}
|
||||
|
||||
double area = 0.0;
|
||||
|
||||
if (line.Length > 4)
|
||||
{
|
||||
double.TryParse(line[4], NumberStyles.Number, nfi, out area);
|
||||
}
|
||||
|
||||
// Triangle's .poly file format allows region definitions with
|
||||
// either 4 or 5 parameters, and different interpretations for
|
||||
// them depending on the number of parameters.
|
||||
//
|
||||
// See http://www.cs.cmu.edu/~quake/triangle.poly.html
|
||||
//
|
||||
// The .NET version will interpret the fourth parameter always
|
||||
// as an integer region id and the optional fifth parameter as
|
||||
// an area constraint.
|
||||
|
||||
data.Regions.Add(new RegionPointer(
|
||||
double.Parse(line[1], nfi), // Region x
|
||||
double.Parse(line[2], nfi), // Region y
|
||||
id, area));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Read ele file
|
||||
if (readElements)
|
||||
{
|
||||
string elefile = Path.ChangeExtension(polyfilename, ".ele");
|
||||
if (File.Exists(elefile))
|
||||
{
|
||||
ReadEleFile(elefile, readArea);
|
||||
}
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read elements from an .ele file.
|
||||
/// </summary>
|
||||
/// <param name="elefilename">The file name.</param>
|
||||
/// <returns>A list of triangles.</returns>
|
||||
public List<ITriangle> ReadEleFile(string elefilename)
|
||||
{
|
||||
return ReadEleFile(elefilename, false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read the elements from an .ele file.
|
||||
/// </summary>
|
||||
/// <param name="elefilename"></param>
|
||||
/// <param name="data"></param>
|
||||
/// <param name="readArea"></param>
|
||||
private List<ITriangle> ReadEleFile(string elefilename, bool readArea)
|
||||
{
|
||||
int intriangles = 0, attributes = 0;
|
||||
|
||||
List<ITriangle> triangles;
|
||||
|
||||
using (var reader = new StreamReader(elefilename))
|
||||
{
|
||||
// Read number of elements and number of attributes.
|
||||
string[] line;
|
||||
bool validRegion = false;
|
||||
|
||||
if (!TryReadLine(reader, out line))
|
||||
{
|
||||
throw new Exception("Can't read input file (elements).");
|
||||
}
|
||||
|
||||
intriangles = int.Parse(line[0]);
|
||||
|
||||
// We irgnore index 1 (number of nodes per triangle)
|
||||
attributes = 0;
|
||||
if (line.Length > 2)
|
||||
{
|
||||
attributes = int.Parse(line[2]);
|
||||
validRegion = true;
|
||||
}
|
||||
|
||||
if (attributes > 1)
|
||||
{
|
||||
Log.Instance.Warning("Triangle attributes not supported.", "FileReader.Read");
|
||||
}
|
||||
|
||||
triangles = new List<ITriangle>(intriangles);
|
||||
|
||||
InputTriangle tri;
|
||||
|
||||
// Read triangles.
|
||||
for (int i = 0; i < intriangles; i++)
|
||||
{
|
||||
if (!TryReadLine(reader, out line))
|
||||
{
|
||||
throw new Exception("Can't read input file (elements).");
|
||||
}
|
||||
|
||||
if (line.Length < 4)
|
||||
{
|
||||
throw new Exception("Triangle has no nodes.");
|
||||
}
|
||||
|
||||
// TODO: startIndex ok?
|
||||
tri = new InputTriangle(
|
||||
int.Parse(line[1]) - startIndex,
|
||||
int.Parse(line[2]) - startIndex,
|
||||
int.Parse(line[3]) - startIndex);
|
||||
|
||||
// Read triangle region
|
||||
if (attributes > 0 && validRegion)
|
||||
{
|
||||
int region = 0;
|
||||
validRegion = int.TryParse(line[4], out region);
|
||||
tri.label = region;
|
||||
}
|
||||
|
||||
triangles.Add(tri);
|
||||
}
|
||||
}
|
||||
|
||||
// Read area file
|
||||
if (readArea)
|
||||
{
|
||||
string areafile = Path.ChangeExtension(elefilename, ".area");
|
||||
if (File.Exists(areafile))
|
||||
{
|
||||
ReadAreaFile(areafile, intriangles);
|
||||
}
|
||||
}
|
||||
|
||||
return triangles;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read the area constraints from an .area file.
|
||||
/// </summary>
|
||||
/// <param name="areafilename"></param>
|
||||
/// <param name="intriangles"></param>
|
||||
/// <param name="data"></param>
|
||||
private double[] ReadAreaFile(string areafilename, int intriangles)
|
||||
{
|
||||
double[] data = null;
|
||||
|
||||
using (var reader = new StreamReader(areafilename))
|
||||
{
|
||||
string[] line;
|
||||
|
||||
if (!TryReadLine(reader, out line))
|
||||
{
|
||||
throw new Exception("Can't read input file (area).");
|
||||
}
|
||||
|
||||
if (int.Parse(line[0]) != intriangles)
|
||||
{
|
||||
Log.Instance.Warning("Number of area constraints doesn't match number of triangles.",
|
||||
"ReadAreaFile()");
|
||||
return null;
|
||||
}
|
||||
|
||||
data = new double[intriangles];
|
||||
|
||||
// Read area constraints.
|
||||
for (int i = 0; i < intriangles; i++)
|
||||
{
|
||||
if (!TryReadLine(reader, out line))
|
||||
{
|
||||
throw new Exception("Can't read input file (area).");
|
||||
}
|
||||
|
||||
if (line.Length != 2)
|
||||
{
|
||||
throw new Exception("Triangle has no nodes.");
|
||||
}
|
||||
|
||||
data[i] = double.Parse(line[1], nfi);
|
||||
}
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read an .edge file.
|
||||
/// </summary>
|
||||
/// <param name="edgeFile">The file name.</param>
|
||||
/// <param name="invertices">The number of input vertices (read from a .node or .poly file).</param>
|
||||
/// <returns>A List of edges.</returns>
|
||||
public List<Edge> ReadEdgeFile(string edgeFile, int invertices)
|
||||
{
|
||||
// Read poly file
|
||||
List<Edge> data = null;
|
||||
|
||||
startIndex = 0;
|
||||
|
||||
string[] line;
|
||||
|
||||
using (var reader = new StreamReader(edgeFile))
|
||||
{
|
||||
// Read the edges from a .edge file.
|
||||
|
||||
// Read number of segments and number of boundary markers.
|
||||
if (!TryReadLine(reader, out line))
|
||||
{
|
||||
throw new Exception("Can't read input file (segments).");
|
||||
}
|
||||
|
||||
int inedges = int.Parse(line[0]);
|
||||
|
||||
int edgemarkers = 0;
|
||||
if (line.Length > 1)
|
||||
{
|
||||
edgemarkers = int.Parse(line[1]);
|
||||
}
|
||||
|
||||
if (inedges > 0)
|
||||
{
|
||||
data = new List<Edge>(inedges);
|
||||
}
|
||||
|
||||
int end1, end2, mark;
|
||||
// Read and insert the segments.
|
||||
for (int i = 0; i < inedges; i++)
|
||||
{
|
||||
if (!TryReadLine(reader, out line))
|
||||
{
|
||||
throw new Exception("Can't read input file (segments).");
|
||||
}
|
||||
|
||||
if (line.Length < 3)
|
||||
{
|
||||
throw new Exception("Segment has no endpoints.");
|
||||
}
|
||||
|
||||
// TODO: startIndex ok?
|
||||
end1 = int.Parse(line[1]) - startIndex;
|
||||
end2 = int.Parse(line[2]) - startIndex;
|
||||
mark = 0;
|
||||
|
||||
if (edgemarkers > 0 && line.Length > 3)
|
||||
{
|
||||
mark = int.Parse(line[3]);
|
||||
}
|
||||
|
||||
if ((end1 < 0) || (end1 >= invertices))
|
||||
{
|
||||
if (Log.Verbose)
|
||||
{
|
||||
Log.Instance.Warning("Invalid first endpoint of segment.",
|
||||
"MeshReader.ReadPolyfile()");
|
||||
}
|
||||
}
|
||||
else if ((end2 < 0) || (end2 >= invertices))
|
||||
{
|
||||
if (Log.Verbose)
|
||||
{
|
||||
Log.Instance.Warning("Invalid second endpoint of segment.",
|
||||
"MeshReader.ReadPolyfile()");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
data.Add(new Edge(end1, end2, mark));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
bool IsStringNullOrWhiteSpace(string value)
|
||||
{
|
||||
if (value != null)
|
||||
{
|
||||
for (int i = 0; i < value.Length; i++)
|
||||
{
|
||||
if (!char.IsWhiteSpace(value[i]))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 9bd4358267e2d4fe3966c374d0a5b5b7
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,460 @@
|
|||
// -----------------------------------------------------------------------
|
||||
// <copyright file="TriangleWriter.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
|
||||
.IO
|
||||
{
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using Animation.TriangleNet.Geometry;
|
||||
using Animation.TriangleNet.Topology;
|
||||
|
||||
/// <summary>
|
||||
/// Helper methods for writing Triangle file formats.
|
||||
/// </summary>
|
||||
internal class TriangleWriter
|
||||
{
|
||||
static NumberFormatInfo nfi = NumberFormatInfo.InvariantInfo;
|
||||
|
||||
/// <summary>
|
||||
/// Number the vertices and write them to a .node file.
|
||||
/// </summary>
|
||||
/// <param name="mesh"></param>
|
||||
/// <param name="filename"></param>
|
||||
public void Write(Mesh mesh, string filename)
|
||||
{
|
||||
WritePoly(mesh, Path.ChangeExtension(filename, ".poly"));
|
||||
WriteElements(mesh, Path.ChangeExtension(filename, ".ele"));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Number the vertices and write them to a .node file.
|
||||
/// </summary>
|
||||
/// <param name="mesh"></param>
|
||||
/// <param name="filename"></param>
|
||||
public void WriteNodes(Mesh mesh, string filename)
|
||||
{
|
||||
using (var writer = new StreamWriter(filename))
|
||||
{
|
||||
WriteNodes(writer, mesh);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Number the vertices and write them to a .node file.
|
||||
/// </summary>
|
||||
private void WriteNodes(StreamWriter writer, Mesh mesh)
|
||||
{
|
||||
int outvertices = mesh.vertices.Count;
|
||||
int nextras = mesh.nextras;
|
||||
|
||||
Behavior behavior = mesh.behavior;
|
||||
|
||||
if (behavior.Jettison)
|
||||
{
|
||||
outvertices = mesh.vertices.Count - mesh.undeads;
|
||||
}
|
||||
|
||||
if (writer != null)
|
||||
{
|
||||
// Number of vertices, number of dimensions, number of vertex attributes,
|
||||
// and number of boundary markers (zero or one).
|
||||
writer.WriteLine("{0} {1} {2} {3}", outvertices, mesh.mesh_dim, nextras,
|
||||
behavior.UseBoundaryMarkers ? "1" : "0");
|
||||
|
||||
if (mesh.numbering == NodeNumbering.None)
|
||||
{
|
||||
// If the mesh isn't numbered yet, use linear node numbering.
|
||||
mesh.Renumber();
|
||||
}
|
||||
|
||||
if (mesh.numbering == NodeNumbering.Linear)
|
||||
{
|
||||
// If numbering is linear, just use the dictionary values.
|
||||
WriteNodes(writer, mesh.vertices.Values, behavior.UseBoundaryMarkers,
|
||||
nextras, behavior.Jettison);
|
||||
}
|
||||
else
|
||||
{
|
||||
// If numbering is not linear, a simple 'foreach' traversal of the dictionary
|
||||
// values doesn't reflect the actual numbering. Use an array instead.
|
||||
|
||||
// TODO: Could use a custom sorting function on dictionary values instead.
|
||||
Vertex[] nodes = new Vertex[mesh.vertices.Count];
|
||||
|
||||
foreach (var node in mesh.vertices.Values)
|
||||
{
|
||||
nodes[node.id] = node;
|
||||
}
|
||||
|
||||
WriteNodes(writer, nodes, behavior.UseBoundaryMarkers,
|
||||
nextras, behavior.Jettison);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Write the vertices to a stream.
|
||||
/// </summary>
|
||||
/// <param name="nodes"></param>
|
||||
/// <param name="writer"></param>
|
||||
private void WriteNodes(StreamWriter writer, IEnumerable<Vertex> nodes, bool markers,
|
||||
int attribs, bool jettison)
|
||||
{
|
||||
int index = 0;
|
||||
|
||||
foreach (var vertex in nodes)
|
||||
{
|
||||
if (!jettison || vertex.type != VertexType.UndeadVertex)
|
||||
{
|
||||
// Vertex number, x and y coordinates.
|
||||
writer.Write("{0} {1} {2}", index, vertex.x.ToString(nfi), vertex.y.ToString(nfi));
|
||||
|
||||
#if USE_ATTRIBS
|
||||
// Write attributes.
|
||||
for (int j = 0; j < attribs; j++)
|
||||
{
|
||||
writer.Write(" {0}", vertex.attributes[j].ToString(nfi));
|
||||
}
|
||||
#endif
|
||||
|
||||
if (markers)
|
||||
{
|
||||
// Write the boundary marker.
|
||||
writer.Write(" {0}", vertex.label);
|
||||
}
|
||||
|
||||
writer.WriteLine();
|
||||
|
||||
index++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Write the triangles to an .ele file.
|
||||
/// </summary>
|
||||
/// <param name="mesh"></param>
|
||||
/// <param name="filename"></param>
|
||||
public void WriteElements(Mesh mesh, string filename)
|
||||
{
|
||||
Otri tri = default(Otri);
|
||||
Vertex p1, p2, p3;
|
||||
bool regions = mesh.behavior.useRegions;
|
||||
|
||||
int j = 0;
|
||||
|
||||
tri.orient = 0;
|
||||
|
||||
using (var writer = new StreamWriter(filename))
|
||||
{
|
||||
// Number of triangles, vertices per triangle, attributes per triangle.
|
||||
writer.WriteLine("{0} 3 {1}", mesh.triangles.Count, regions ? 1 : 0);
|
||||
|
||||
foreach (var item in mesh.triangles)
|
||||
{
|
||||
tri.tri = item;
|
||||
|
||||
p1 = tri.Org();
|
||||
p2 = tri.Dest();
|
||||
p3 = tri.Apex();
|
||||
|
||||
// Triangle number, indices for three vertices.
|
||||
writer.Write("{0} {1} {2} {3}", j, p1.id, p2.id, p3.id);
|
||||
|
||||
if (regions)
|
||||
{
|
||||
writer.Write(" {0}", tri.tri.label);
|
||||
}
|
||||
|
||||
writer.WriteLine();
|
||||
|
||||
// Number elements
|
||||
item.id = j++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Write the segments and holes to a .poly file.
|
||||
/// </summary>
|
||||
/// <param name="polygon">Data source.</param>
|
||||
/// <param name="filename">File name.</param>
|
||||
/// <param name="writeNodes">Write nodes into this file.</param>
|
||||
/// <remarks>If the nodes should not be written into this file,
|
||||
/// make sure a .node file was written before, so that the nodes
|
||||
/// are numbered right.</remarks>
|
||||
public void WritePoly(IPolygon polygon, string filename)
|
||||
{
|
||||
bool hasMarkers = polygon.HasSegmentMarkers;
|
||||
|
||||
using (var writer = new StreamWriter(filename))
|
||||
{
|
||||
// TODO: write vertex attributes
|
||||
|
||||
writer.WriteLine("{0} 2 0 {1}", polygon.Points.Count, polygon.HasPointMarkers ? "1" : "0");
|
||||
|
||||
// Write nodes to this file.
|
||||
WriteNodes(writer, polygon.Points, polygon.HasPointMarkers, 0, false);
|
||||
|
||||
// Number of segments, number of boundary markers (zero or one).
|
||||
writer.WriteLine("{0} {1}", polygon.Segments.Count, hasMarkers ? "1" : "0");
|
||||
|
||||
Vertex p, q;
|
||||
|
||||
int j = 0;
|
||||
foreach (var seg in polygon.Segments)
|
||||
{
|
||||
p = seg.GetVertex(0);
|
||||
q = seg.GetVertex(1);
|
||||
|
||||
// Segment number, indices of its two endpoints, and possibly a marker.
|
||||
if (hasMarkers)
|
||||
{
|
||||
writer.WriteLine("{0} {1} {2} {3}", j, p.ID, q.ID, seg.Label);
|
||||
}
|
||||
else
|
||||
{
|
||||
writer.WriteLine("{0} {1} {2}", j, p.ID, q.ID);
|
||||
}
|
||||
|
||||
j++;
|
||||
}
|
||||
|
||||
// Holes
|
||||
j = 0;
|
||||
writer.WriteLine("{0}", polygon.Holes.Count);
|
||||
foreach (var hole in polygon.Holes)
|
||||
{
|
||||
writer.WriteLine("{0} {1} {2}", j++, hole.X.ToString(nfi), hole.Y.ToString(nfi));
|
||||
}
|
||||
|
||||
// Regions
|
||||
if (polygon.Regions.Count > 0)
|
||||
{
|
||||
j = 0;
|
||||
writer.WriteLine("{0}", polygon.Regions.Count);
|
||||
foreach (var region in polygon.Regions)
|
||||
{
|
||||
writer.WriteLine("{0} {1} {2} {3}", j, region.point.X.ToString(nfi),
|
||||
region.point.Y.ToString(nfi), region.id);
|
||||
|
||||
j++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Write the segments and holes to a .poly file.
|
||||
/// </summary>
|
||||
/// <param name="mesh"></param>
|
||||
/// <param name="filename"></param>
|
||||
public void WritePoly(Mesh mesh, string filename)
|
||||
{
|
||||
WritePoly(mesh, filename, true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Write the segments and holes to a .poly file.
|
||||
/// </summary>
|
||||
/// <param name="mesh">Data source.</param>
|
||||
/// <param name="filename">File name.</param>
|
||||
/// <param name="writeNodes">Write nodes into this file.</param>
|
||||
/// <remarks>If the nodes should not be written into this file,
|
||||
/// make sure a .node file was written before, so that the nodes
|
||||
/// are numbered right.</remarks>
|
||||
public void WritePoly(Mesh mesh, string filename, bool writeNodes)
|
||||
{
|
||||
Osub subseg = default(Osub);
|
||||
Vertex pt1, pt2;
|
||||
|
||||
bool useBoundaryMarkers = mesh.behavior.UseBoundaryMarkers;
|
||||
|
||||
using (var writer = new StreamWriter(filename))
|
||||
{
|
||||
if (writeNodes)
|
||||
{
|
||||
// Write nodes to this file.
|
||||
WriteNodes(writer, mesh);
|
||||
}
|
||||
else
|
||||
{
|
||||
// The zero indicates that the vertices are in a separate .node file.
|
||||
// Followed by number of dimensions, number of vertex attributes,
|
||||
// and number of boundary markers (zero or one).
|
||||
writer.WriteLine("0 {0} {1} {2}", mesh.mesh_dim, mesh.nextras,
|
||||
useBoundaryMarkers ? "1" : "0");
|
||||
}
|
||||
|
||||
// Number of segments, number of boundary markers (zero or one).
|
||||
writer.WriteLine("{0} {1}", mesh.subsegs.Count,
|
||||
useBoundaryMarkers ? "1" : "0");
|
||||
|
||||
subseg.orient = 0;
|
||||
|
||||
int j = 0;
|
||||
foreach (var item in mesh.subsegs.Values)
|
||||
{
|
||||
subseg.seg = item;
|
||||
|
||||
pt1 = subseg.Org();
|
||||
pt2 = subseg.Dest();
|
||||
|
||||
// Segment number, indices of its two endpoints, and possibly a marker.
|
||||
if (useBoundaryMarkers)
|
||||
{
|
||||
writer.WriteLine("{0} {1} {2} {3}", j, pt1.id, pt2.id, subseg.seg.boundary);
|
||||
}
|
||||
else
|
||||
{
|
||||
writer.WriteLine("{0} {1} {2}", j, pt1.id, pt2.id);
|
||||
}
|
||||
|
||||
j++;
|
||||
}
|
||||
|
||||
// Holes
|
||||
j = 0;
|
||||
writer.WriteLine("{0}", mesh.holes.Count);
|
||||
foreach (var hole in mesh.holes)
|
||||
{
|
||||
writer.WriteLine("{0} {1} {2}", j++, hole.X.ToString(nfi), hole.Y.ToString(nfi));
|
||||
}
|
||||
|
||||
// Regions
|
||||
if (mesh.regions.Count > 0)
|
||||
{
|
||||
j = 0;
|
||||
writer.WriteLine("{0}", mesh.regions.Count);
|
||||
foreach (var region in mesh.regions)
|
||||
{
|
||||
writer.WriteLine("{0} {1} {2} {3}", j, region.point.X.ToString(nfi),
|
||||
region.point.Y.ToString(nfi), region.id);
|
||||
|
||||
j++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Write the edges to an .edge file.
|
||||
/// </summary>
|
||||
/// <param name="mesh"></param>
|
||||
/// <param name="filename"></param>
|
||||
public void WriteEdges(Mesh mesh, string filename)
|
||||
{
|
||||
Otri tri = default(Otri), trisym = default(Otri);
|
||||
Osub checkmark = default(Osub);
|
||||
Vertex p1, p2;
|
||||
|
||||
Behavior behavior = mesh.behavior;
|
||||
|
||||
using (var writer = new StreamWriter(filename))
|
||||
{
|
||||
// Number of edges, number of boundary markers (zero or one).
|
||||
writer.WriteLine("{0} {1}", mesh.NumberOfEdges, behavior.UseBoundaryMarkers ? "1" : "0");
|
||||
|
||||
long index = 0;
|
||||
// To loop over the set of edges, loop over all triangles, and look at
|
||||
// the three edges of each triangle. If there isn't another triangle
|
||||
// adjacent to the edge, operate on the edge. If there is another
|
||||
// adjacent triangle, operate on the edge only if the current triangle
|
||||
// has a smaller pointer than its neighbor. This way, each edge is
|
||||
// considered only once.
|
||||
foreach (var item in mesh.triangles)
|
||||
{
|
||||
tri.tri = item;
|
||||
|
||||
for (tri.orient = 0; tri.orient < 3; tri.orient++)
|
||||
{
|
||||
tri.Sym(ref trisym);
|
||||
if ((tri.tri.id < trisym.tri.id) || (trisym.tri.id == Mesh.DUMMY))
|
||||
{
|
||||
p1 = tri.Org();
|
||||
p2 = tri.Dest();
|
||||
|
||||
if (behavior.UseBoundaryMarkers)
|
||||
{
|
||||
// Edge number, indices of two endpoints, and a boundary marker.
|
||||
// If there's no subsegment, the boundary marker is zero.
|
||||
if (behavior.useSegments)
|
||||
{
|
||||
tri.Pivot(ref checkmark);
|
||||
|
||||
if (checkmark.seg.hash == Mesh.DUMMY)
|
||||
{
|
||||
writer.WriteLine("{0} {1} {2} {3}", index, p1.id, p2.id, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
writer.WriteLine("{0} {1} {2} {3}", index, p1.id, p2.id,
|
||||
checkmark.seg.boundary);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
writer.WriteLine("{0} {1} {2} {3}", index, p1.id, p2.id,
|
||||
trisym.tri.id == Mesh.DUMMY ? "1" : "0");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Edge number, indices of two endpoints.
|
||||
writer.WriteLine("{0} {1} {2}", index, p1.id, p2.id);
|
||||
}
|
||||
|
||||
index++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Write the triangle neighbors to a .neigh file.
|
||||
/// </summary>
|
||||
/// <param name="mesh"></param>
|
||||
/// <param name="filename"></param>
|
||||
/// <remarks>WARNING: Be sure WriteElements has been called before,
|
||||
/// so the elements are numbered right!</remarks>
|
||||
public void WriteNeighbors(Mesh mesh, string filename)
|
||||
{
|
||||
Otri tri = default(Otri), trisym = default(Otri);
|
||||
int n1, n2, n3;
|
||||
int i = 0;
|
||||
|
||||
using (StreamWriter writer = new StreamWriter(filename))
|
||||
{
|
||||
// Number of triangles, three neighbors per triangle.
|
||||
writer.WriteLine("{0} 3", mesh.triangles.Count);
|
||||
|
||||
foreach (var item in mesh.triangles)
|
||||
{
|
||||
tri.tri = item;
|
||||
|
||||
tri.orient = 1;
|
||||
tri.Sym(ref trisym);
|
||||
n1 = trisym.tri.id;
|
||||
|
||||
tri.orient = 2;
|
||||
tri.Sym(ref trisym);
|
||||
n2 = trisym.tri.id;
|
||||
|
||||
tri.orient = 0;
|
||||
tri.Sym(ref trisym);
|
||||
n3 = trisym.tri.id;
|
||||
|
||||
// Triangle number, neighboring triangle numbers.
|
||||
writer.WriteLine("{0} {1} {2} {3}", i++, n1, n2, n3);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 2d813b6701ce64ba398e4a1d159ae1a3
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,22 @@
|
|||
// -----------------------------------------------------------------------
|
||||
// <copyright file="IPredicates.cs">
|
||||
// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/
|
||||
// </copyright>
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
namespace UnityEngine.U2D.Animation.TriangleNet
|
||||
{
|
||||
using Animation.TriangleNet.Geometry;
|
||||
|
||||
internal interface IPredicates
|
||||
{
|
||||
double CounterClockwise(Point a, Point b, Point c);
|
||||
|
||||
double InCircle(Point a, Point b, Point c, Point p);
|
||||
|
||||
Point FindCircumcenter(Point org, Point dest, Point apex, ref double xi, ref double eta);
|
||||
|
||||
Point FindCircumcenter(Point org, Point dest, Point apex, ref double xi, ref double eta,
|
||||
double offconstant);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: cb2b414b37e204a0e9107e0b489ce263
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,84 @@
|
|||
// -----------------------------------------------------------------------
|
||||
// <copyright file="Log.cs" company="">
|
||||
// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/
|
||||
// </copyright>
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
namespace UnityEngine.U2D.Animation.TriangleNet
|
||||
{
|
||||
using System.Collections.Generic;
|
||||
using Animation.TriangleNet.Logging;
|
||||
|
||||
/// <summary>
|
||||
/// A simple logger, which logs messages to a List.
|
||||
/// </summary>
|
||||
/// <remarks>Using singleton pattern as proposed by Jon Skeet.
|
||||
/// http://csharpindepth.com/Articles/General/Singleton.aspx
|
||||
/// </remarks>
|
||||
internal sealed class Log : ILog<LogItem>
|
||||
{
|
||||
/// <summary>
|
||||
/// Log detailed information.
|
||||
/// </summary>
|
||||
internal static bool Verbose { get; set; }
|
||||
|
||||
private List<LogItem> log = new List<LogItem>();
|
||||
|
||||
private LogLevel level = LogLevel.Info;
|
||||
|
||||
#region Singleton pattern
|
||||
|
||||
private static readonly Log instance = new Log();
|
||||
|
||||
// Explicit static constructor to tell C# compiler
|
||||
// not to mark type as beforefieldinit
|
||||
static Log() {}
|
||||
|
||||
private Log() {}
|
||||
|
||||
internal static ILog<LogItem> Instance
|
||||
{
|
||||
get
|
||||
{
|
||||
return instance;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public void Add(LogItem item)
|
||||
{
|
||||
log.Add(item);
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
log.Clear();
|
||||
}
|
||||
|
||||
public void Info(string message)
|
||||
{
|
||||
log.Add(new LogItem(LogLevel.Info, message));
|
||||
}
|
||||
|
||||
public void Warning(string message, string location)
|
||||
{
|
||||
log.Add(new LogItem(LogLevel.Warning, message, location));
|
||||
}
|
||||
|
||||
public void Error(string message, string location)
|
||||
{
|
||||
log.Add(new LogItem(LogLevel.Error, message, location));
|
||||
}
|
||||
|
||||
public IList<LogItem> Data
|
||||
{
|
||||
get { return log; }
|
||||
}
|
||||
|
||||
public LogLevel Level
|
||||
{
|
||||
get { return level; }
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 196e2f3da40aa4a94a0a42a5e8fe60b9
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 12bf6e7f64391465d8d8ef95ca3a996b
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,35 @@
|
|||
// -----------------------------------------------------------------------
|
||||
// <copyright file="ILog.cs" company="">
|
||||
// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/
|
||||
// </copyright>
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
namespace UnityEngine.U2D.Animation.TriangleNet
|
||||
.Logging
|
||||
{
|
||||
using System.Collections.Generic;
|
||||
|
||||
internal enum LogLevel
|
||||
{
|
||||
Info = 0,
|
||||
Warning = 1,
|
||||
Error = 2
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A basic log interface.
|
||||
/// </summary>
|
||||
internal interface ILog<T> where T : ILogItem
|
||||
{
|
||||
void Add(T item);
|
||||
void Clear();
|
||||
|
||||
void Info(string message);
|
||||
void Error(string message, string info);
|
||||
void Warning(string message, string info);
|
||||
|
||||
IList<T> Data { get; }
|
||||
|
||||
LogLevel Level { get; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 3abac741b8a6142f39b485177be8e65d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,22 @@
|
|||
// -----------------------------------------------------------------------
|
||||
// <copyright file="ILogItem.cs" company="">
|
||||
// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/
|
||||
// </copyright>
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
namespace UnityEngine.U2D.Animation.TriangleNet
|
||||
.Logging
|
||||
{
|
||||
using System;
|
||||
|
||||
/// <summary>
|
||||
/// A basic log item interface.
|
||||
/// </summary>
|
||||
internal interface ILogItem
|
||||
{
|
||||
DateTime Time { get; }
|
||||
LogLevel Level { get; }
|
||||
string Message { get; }
|
||||
string Info { get; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 833bedb11c85e4260a9ec09f2f773258
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,54 @@
|
|||
// -----------------------------------------------------------------------
|
||||
// <copyright file="SimpleLogItem.cs" company="">
|
||||
// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/
|
||||
// </copyright>
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
namespace UnityEngine.U2D.Animation.TriangleNet
|
||||
.Logging
|
||||
{
|
||||
using System;
|
||||
|
||||
/// <summary>
|
||||
/// Represents an item stored in the log.
|
||||
/// </summary>
|
||||
internal class LogItem : ILogItem
|
||||
{
|
||||
DateTime time;
|
||||
LogLevel level;
|
||||
string message;
|
||||
string info;
|
||||
|
||||
public DateTime Time
|
||||
{
|
||||
get { return time; }
|
||||
}
|
||||
|
||||
public LogLevel Level
|
||||
{
|
||||
get { return level; }
|
||||
}
|
||||
|
||||
public string Message
|
||||
{
|
||||
get { return message; }
|
||||
}
|
||||
|
||||
public string Info
|
||||
{
|
||||
get { return info; }
|
||||
}
|
||||
|
||||
public LogItem(LogLevel level, string message)
|
||||
: this(level, message, "")
|
||||
{}
|
||||
|
||||
public LogItem(LogLevel level, string message, string info)
|
||||
{
|
||||
this.time = DateTime.Now;
|
||||
this.level = level;
|
||||
this.message = message;
|
||||
this.info = info;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: a4a265973daf84c31b1feb0b2f1f44a7
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
File diff suppressed because it is too large
Load diff
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: cf5fb0e34d9b14ac88f67bf18b0bc902
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,214 @@
|
|||
// -----------------------------------------------------------------------
|
||||
// <copyright file="MeshValidator.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
|
||||
{
|
||||
using System;
|
||||
using Animation.TriangleNet.Topology;
|
||||
using Animation.TriangleNet.Geometry;
|
||||
|
||||
internal static class MeshValidator
|
||||
{
|
||||
private static RobustPredicates predicates = RobustPredicates.Default;
|
||||
|
||||
/// <summary>
|
||||
/// Test the mesh for topological consistency.
|
||||
/// </summary>
|
||||
internal static bool IsConsistent(Mesh mesh)
|
||||
{
|
||||
Otri tri = default(Otri);
|
||||
Otri oppotri = default(Otri), oppooppotri = default(Otri);
|
||||
Vertex org, dest, apex;
|
||||
Vertex oppoorg, oppodest;
|
||||
|
||||
var logger = Log.Instance;
|
||||
|
||||
// Temporarily turn on exact arithmetic if it's off.
|
||||
bool saveexact = Behavior.NoExact;
|
||||
Behavior.NoExact = false;
|
||||
|
||||
int horrors = 0;
|
||||
|
||||
// Run through the list of triangles, checking each one.
|
||||
foreach (var t in mesh.triangles)
|
||||
{
|
||||
tri.tri = t;
|
||||
|
||||
// Check all three edges of the triangle.
|
||||
for (tri.orient = 0; tri.orient < 3; tri.orient++)
|
||||
{
|
||||
org = tri.Org();
|
||||
dest = tri.Dest();
|
||||
if (tri.orient == 0)
|
||||
{
|
||||
// Only test for inversion once.
|
||||
// Test if the triangle is flat or inverted.
|
||||
apex = tri.Apex();
|
||||
if (predicates.CounterClockwise(org, dest, apex) <= 0.0)
|
||||
{
|
||||
if (Log.Verbose)
|
||||
{
|
||||
logger.Warning(String.Format("Triangle is flat or inverted (ID {0}).", t.id),
|
||||
"MeshValidator.IsConsistent()");
|
||||
}
|
||||
|
||||
horrors++;
|
||||
}
|
||||
}
|
||||
|
||||
// Find the neighboring triangle on this edge.
|
||||
tri.Sym(ref oppotri);
|
||||
if (oppotri.tri.id != Mesh.DUMMY)
|
||||
{
|
||||
// Check that the triangle's neighbor knows it's a neighbor.
|
||||
oppotri.Sym(ref oppooppotri);
|
||||
if ((tri.tri != oppooppotri.tri) || (tri.orient != oppooppotri.orient))
|
||||
{
|
||||
if (tri.tri == oppooppotri.tri && Log.Verbose)
|
||||
{
|
||||
logger.Warning("Asymmetric triangle-triangle bond: (Right triangle, wrong orientation)",
|
||||
"MeshValidator.IsConsistent()");
|
||||
}
|
||||
|
||||
horrors++;
|
||||
}
|
||||
// Check that both triangles agree on the identities
|
||||
// of their shared vertices.
|
||||
oppoorg = oppotri.Org();
|
||||
oppodest = oppotri.Dest();
|
||||
if ((org != oppodest) || (dest != oppoorg))
|
||||
{
|
||||
if (Log.Verbose)
|
||||
{
|
||||
logger.Warning("Mismatched edge coordinates between two triangles.",
|
||||
"MeshValidator.IsConsistent()");
|
||||
}
|
||||
|
||||
horrors++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check for unconnected vertices
|
||||
mesh.MakeVertexMap();
|
||||
foreach (var v in mesh.vertices.Values)
|
||||
{
|
||||
if (v.tri.tri == null && Log.Verbose)
|
||||
{
|
||||
logger.Warning("Vertex (ID " + v.id + ") not connected to mesh (duplicate input vertex?)",
|
||||
"MeshValidator.IsConsistent()");
|
||||
}
|
||||
}
|
||||
|
||||
// Restore the status of exact arithmetic.
|
||||
Behavior.NoExact = saveexact;
|
||||
|
||||
return (horrors == 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if the mesh is (conforming) Delaunay.
|
||||
/// </summary>
|
||||
internal static bool IsDelaunay(Mesh mesh)
|
||||
{
|
||||
return IsDelaunay(mesh, false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if that the mesh is (constrained) Delaunay.
|
||||
/// </summary>
|
||||
internal static bool IsConstrainedDelaunay(Mesh mesh)
|
||||
{
|
||||
return IsDelaunay(mesh, true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ensure that the mesh is (constrained) Delaunay.
|
||||
/// </summary>
|
||||
private static bool IsDelaunay(Mesh mesh, bool constrained)
|
||||
{
|
||||
Otri loop = default(Otri);
|
||||
Otri oppotri = default(Otri);
|
||||
Osub opposubseg = default(Osub);
|
||||
Vertex org, dest, apex;
|
||||
Vertex oppoapex;
|
||||
|
||||
bool shouldbedelaunay;
|
||||
|
||||
var logger = Log.Instance;
|
||||
|
||||
// Temporarily turn on exact arithmetic if it's off.
|
||||
bool saveexact = Behavior.NoExact;
|
||||
Behavior.NoExact = false;
|
||||
|
||||
int horrors = 0;
|
||||
|
||||
var inf1 = mesh.infvertex1;
|
||||
var inf2 = mesh.infvertex2;
|
||||
var inf3 = mesh.infvertex3;
|
||||
|
||||
// Run through the list of triangles, checking each one.
|
||||
foreach (var tri in mesh.triangles)
|
||||
{
|
||||
loop.tri = tri;
|
||||
|
||||
// Check all three edges of the triangle.
|
||||
for (loop.orient = 0; loop.orient < 3; loop.orient++)
|
||||
{
|
||||
org = loop.Org();
|
||||
dest = loop.Dest();
|
||||
apex = loop.Apex();
|
||||
|
||||
loop.Sym(ref oppotri);
|
||||
oppoapex = oppotri.Apex();
|
||||
|
||||
// Only test that the edge is locally Delaunay if there is an
|
||||
// adjoining triangle whose pointer is larger (to ensure that
|
||||
// each pair isn't tested twice).
|
||||
shouldbedelaunay = (loop.tri.id < oppotri.tri.id) &&
|
||||
!Otri.IsDead(oppotri.tri) && (oppotri.tri.id != Mesh.DUMMY) &&
|
||||
(org != inf1) && (org != inf2) && (org != inf3) &&
|
||||
(dest != inf1) && (dest != inf2) && (dest != inf3) &&
|
||||
(apex != inf1) && (apex != inf2) && (apex != inf3) &&
|
||||
(oppoapex != inf1) && (oppoapex != inf2) && (oppoapex != inf3);
|
||||
|
||||
if (constrained && mesh.checksegments && shouldbedelaunay)
|
||||
{
|
||||
// If a subsegment separates the triangles, then the edge is
|
||||
// constrained, so no local Delaunay test should be done.
|
||||
loop.Pivot(ref opposubseg);
|
||||
|
||||
if (opposubseg.seg.hash != Mesh.DUMMY)
|
||||
{
|
||||
shouldbedelaunay = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (shouldbedelaunay)
|
||||
{
|
||||
if (predicates.NonRegular(org, dest, apex, oppoapex) > 0.0)
|
||||
{
|
||||
if (Log.Verbose)
|
||||
{
|
||||
logger.Warning(String.Format("Non-regular pair of triangles found (IDs {0}/{1}).",
|
||||
loop.tri.id, oppotri.tri.id), "MeshValidator.IsDelaunay()");
|
||||
}
|
||||
|
||||
horrors++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Restore the status of exact arithmetic.
|
||||
Behavior.NoExact = saveexact;
|
||||
|
||||
return (horrors == 0);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 8688318786c2246dbae9df88b4e94a46
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 2e613ffc4de344b0cb197af2ee53feb5
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 4637d45a8a4db4499b2c164b519ca3c3
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 9c2c3aea8fdba4a288f591cbf028963d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 3ffa994dccc684238af9f51c4b71a053
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 0e71212805f8c4fc7a7db8dd552b73a6
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
File diff suppressed because it is too large
Load diff
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: efa4ba0c38e16414f80d3ccfb6cbd821
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -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; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 8bb3407583ec744c0ba8307cebefdc1d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 5e1a1f401d8254b9386c06b5f3e122a4
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: a36d23163bf694f5b9953efa040989db
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: ab0e12b6f8d3e4699af8cc8018397a8d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 427677fae20f0460e9d05b080a3d93ad
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 99e6114d2bbce43ca94c406f19ba2eaf
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 4da748bcb2b2d41bbad9ec2561d1d2e9
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 4cd626f08811c47b092c491b658d4ef1
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: afa9255f65ec947029394580abdc1070
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 6f606e975498d44149054ec8fedf092e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 22395c0668d29483bbb5685bf1385ecb
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 7c5150d54e763414287e3bea1fbda552
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue