XML/SVG, Vector-Boolean Operations, Triangulations

This commit is contained in:
Josef 2024-10-25 08:28:58 +02:00
parent fed16cb75c
commit 521d609dd1
148 changed files with 33277 additions and 451 deletions

413
External/Clipper/CHANGELOG.md vendored Normal file
View File

@ -0,0 +1,413 @@
=====================================================================
Clipper Change Log
=====================================================================
v6.4.2 (27 February 2017) Rev 512
* Several minor bugfixes: #152 #160 #161 #162
v6.4 (2 July 2015) Rev 495
* Numerous generally minor bugfixes
v6.2.1 (31 October 2014) Rev 482
* Bugfix in ClipperOffset.Execute where the Polytree.IsHole property
was returning incorrect values with negative offsets
* Very minor improvement to join rounding in ClipperOffset
* Fixed CPP OpenGL demo.
v6.2.0 (17 October 2014) Rev 477
* Numerous minor bugfixes, too many to list.
(See revisions 454-475 in Sourceforge Repository)
* The ZFillFunction (custom callback function) has had its parameters
changed.
* Curves demo removed (temporarily).
* Deprecated functions have been removed.
v6.1.5 (26 February 2014) Rev 460
* Improved the joining of output polygons sharing a common edge
when those common edges are horizontal.
* Fixed a bug in ClipperOffset.AddPath() which would produce
incorrect solutions when open paths were added before closed paths.
* Minor code tidy and performance improvement
v6.1.4 (6 February 2014)
* Fixed bugs in MinkowskiSum
* Fixed minor bug when using Clipper.ForceSimplify.
* Modified use_xyz callback so that all 4 vertices around an
intersection point are now passed to the callback function.
v6.1.3a (22 January 2014) Rev 453
* Fixed buggy PointInPolygon function (C++ and C# only).
Note this bug only affected the newly exported function, the
internal PointInPolygon function used by Clipper was OK.
v6.1.3 (19 January 2014) Rev 452
* Fixed potential endless loop condition when adding open
paths to Clipper.
* Fixed missing implementation of SimplifyPolygon function
in C++ code.
* Fixed incorrect upper range constant for polygon coordinates
in Delphi code.
* Added PointInPolygon function.
* Overloaded MinkowskiSum function to accommodate multi-contour
paths.
v6.1.2 (15 December 2013) Rev 444
* Fixed broken C++ header file.
* Minor improvement to joining polygons.
v6.1.1 (13 December 2013) Rev 441
* Fixed a couple of bugs affecting open paths that could
raise unhandled exceptions.
v6.1.0 (12 December 2013)
* Deleted: Previously deprecated code has been removed.
* Modified: The OffsetPaths function is now deprecated as it has
been replaced by the ClipperOffset class which is much more
flexible.
* Bugfixes: Several minor bugs have been fixed including
occasionally an incorrect nesting within the PolyTree structure.
v6.0.0 (30 October 2013)
* Added: Open path (polyline) clipping. A new 'Curves' demo
application showcases this (see the 'Curves' directory).
* Update: Major improvement in the merging of
shared/collinear edges in clip solutions (see Execute).
* Added: The IntPoint structure now has an optional 'Z' member.
(See the precompiler directive use_xyz.)
* Added: Users can now force Clipper to use 32bit integers
(via the precompiler directive use_int32) instead of using
64bit integers.
* Modified: To accommodate open paths, the Polygon and Polygons
structures have been renamed Path and Paths respectively. The
AddPolygon and AddPolygons methods of the ClipperBase class
have been renamed AddPath and AddPaths respectively. Several
other functions have been similarly renamed.
* Modified: The PolyNode Class has a new IsOpen property.
* Modified: The Clipper class has a new ZFillFunction property.
* Added: MinkowskiSum and MinkowskiDiff functions added.
* Added: Several other new functions have been added including
PolyTreeToPaths, OpenPathsFromPolyTree and ClosedPathsFromPolyTree.
* Added: The Clipper constructor now accepts an optional InitOptions
parameter to simplify setting properties.
* Bugfixes: Numerous minor bugs have been fixed.
* Deprecated: Version 6 is a major upgrade from previous versions
and quite a number of changes have been made to exposed structures
and functions. To minimize inconvenience to existing library users,
some code has been retained and some added to maintain backward
compatibility. However, because this code will be removed in a
future update, it has been marked as deprecated and a precompiler
directive use_deprecated has been defined.
v5.1.6 (23 May 2013)
* BugFix: CleanPolygon function was buggy.
* Changed: The behaviour of the 'miter' JoinType has been
changed so that when squaring occurs, it's no longer
extended up to the miter limit but is squared off at
exactly 'delta' units. (This improves the look of mitering
with larger limits at acute angles.)
* Added: New OffsetPolyLines function
* Update: Minor code refactoring and optimisations
v5.1.5 (5 May 2013)
* Added: ForceSimple property to Clipper class
* Update: Improved documentation
v5.1.4 (24 March 2013)
* Update: CleanPolygon function enhanced.
* Update: Documentation improved.
v5.1.3 (14 March 2013)
* Bugfix: Minor bugfixes.
* Update: Documentation significantly improved.
v5.1.2 (26 February 2013)
* Bugfix: PolyNode class was missing a constructor.
* Update: The MiterLimit parameter in the OffsetPolygons
function has been renamed Limit and can now also be used to
limit the number of vertices used to construct arcs when
JoinType is set to jtRound.
v5.1.0 (17 February 2013)
* Update: ExPolygons has been replaced with the PolyTree &
PolyNode classes to more fully represent the parent-child
relationships of the polygons returned by Clipper.
* Added: New CleanPolygon and CleanPolygons functions.
* Bugfix: Another orientation bug fixed.
v5.0.2 - 30 December 2012
* Bugfix: Significant fixes in and tidy of the internal
Int128 class (which is used only when polygon coordinate
values are greater than ±0x3FFFFFFF (~1.07e9)).
* Update: The Area algorithm has been updated and is faster.
* Update: Documentation updates. The newish but undocumented
'CheckInputs' parameter of the OffsetPolygons function has been
renamed 'AutoFix' and documented too. The comments on rounding
have also been improved (ie clearer and expanded).
v4.10.0 - 25 December 2012
* Bugfix: Orientation bugs should now be resolved (finally!).
* Bugfix: Bug in Int128 class
v4.9.8 - 2 December 2012
* Bugfix: Further fixes to rare Orientation bug.
v4.9.7 - 29 November 2012
* Bugfix: Bug that very rarely returned the wrong polygon
orientation.
* Bugfix: Obscure bug affecting OffsetPolygons when using
jtRound for the JoinType parameter and when polygons also
contain very large coordinate values (> +/-100000000000).
v4.9.6 - 9 November 2012
* Bugfix: Another obscure bug related to joining polygons.
v4.9.4 - 2 November 2012
* Bugfix: Bugs in Int128 class occasionally causing
wrong orientations.
* Bugfix: Further fixes related to joining polygons.
v4.9.0 - 9 October 2012
* Bugfix: Obscure bug related to joining polygons.
v4.8.9 - 25 September 2012
* Bugfix: Obscure bug related to precision of intersections.
v4.8.8 - 30 August 2012
* Bugfix: Fixed bug in OffsetPolygons function introduced in
version 4.8.5.
v4.8.7 - 24 August 2012
* Bugfix: ReversePolygon function in C++ translation was broken.
* Bugfix: Two obscure bugs affecting orientation fixed too.
v4.8.6 - 11 August 2012
* Bugfix: Potential for memory overflow errors when using
ExPolygons structure.
* Bugfix: The polygon coordinate range has been reduced to
+/- 0x3FFFFFFFFFFFFFFF (4.6e18).
* Update: ReversePolygons function was misnamed ReversePoints in C++.
* Update: SimplifyPolygon function now takes a PolyFillType parameter.
v4.8.5 - 15 July 2012
* Bugfix: Potential for memory overflow errors in OffsetPolygons().
v4.8.4 - 1 June 2012
* Bugfix: Another obscure bug affecting ExPolygons structure.
v4.8.3 - 27 May 2012
* Bugfix: Obscure bug causing incorrect removal of a vertex.
v4.8.2 - 21 May 2012
* Bugfix: Obscure bug could cause an exception when using
ExPolygon structure.
v4.8.1 - 12 May 2012
* Update: Cody tidy and minor bug fixes.
v4.8.0 - 30 April 2012
* Bugfix: Occasional errors in orientation fixed.
* Update: Added notes on rounding to the documentation.
v4.7.6 - 11 April 2012
* Fixed a bug in Orientation function (affecting C# translations only).
* Minor documentation update.
v4.7.5 - 28 March 2012
* Bugfix: Fixed a recently introduced bug that occasionally caused an
unhandled exception in C++ and C# translations.
v4.7.4 - 15 March 2012
* Bugfix: Another minor bugfix.
v4.7.2 - 4 March 2012
* Bugfix: Fixed bug introduced in ver 4.7 which sometimes caused
an exception if ExPolygon structure was passed to Clipper's
Execute method.
v4.7.1 - 3 March 2012
* Bugfix: Rare crash when JoinCommonEdges joined polygons that
'cancelled' each other.
* Bugfix: Clipper's internal Orientation method occasionally
returned wrong result.
* Update: Improved C# code (thanks to numerous excellent suggestions
from David Piepgrass)
v4.7 - 10 February 2012
* Improved the joining of output polygons sharing a common edge.
v4.6.6 - 3 February 2012
* Bugfix: Another obscure bug occasionally causing incorrect
polygon orientation.
v4.6.5 - 17 January 2012
* Bugfix: Obscure bug occasionally causing incorrect hole
assignment in ExPolygon structure.
v4.6.4 - 8 November 2011
* Added: SimplifyPolygon and SimplifyPolygons functions.
v4.6.3 - 11 November 2011
* Bugfix: Fixed another minor mitering bug in OffsetPolygons.
v4.6.2 - 10 November 2011
* Bugfix: Fixed a rare bug in the orientation of polygons
returned by Clipper's Execute() method.
* Bugfix: Previous update introduced a mitering bug in the
OffsetPolygons function.
v4.6 - 29 October 2011
* Added: Support for Positive and Negative polygon fill
types (in addition to the EvenOdd and NonZero fill types).
* Bugfix: The OffsetPolygons function was generating the
occasional artefact when 'shrinking' polygons.
v4.5.5 - 8 October 2011
* Bugfix: Fixed an obscure bug in Clipper's JoinCommonEdges
method.
* Update: Replaced IsClockwise function with Orientation
function. The orientation issues affecting OffsetPolygons
should now be finally resolved.
* Change: The Area function once again returns a signed value.
v4.5.1 - 28 September 2011
* Deleted: The UseFullCoordinateRange property has been
deleted since integer range is now managed implicitly.
* BugFix: Minor bug in OffsetPolygon mitering.
* Change: C# JoinType enum moved from Clipper class to
ClipperLib namespace.
* Change: The Area function now returns the absolute area
(irrespective of orientation).
* Change: The IsClockwise function now requires a second
parameter - YAxisPositiveUpward - to accommodate displays
with Y-axis oriented in either direction
v4.4.4 - 10 September 2011
* Change: Deleted jtButt from JoinType (used by the
OffsetPolygons function).
* BugFix: Fixed another minor bug in OffsetPolygons function.
* Update: Further improvements to the help file
v4.4.3 - 29 August 2011
* BugFix: fixed a minor rounding issue in OffsetPolygons
function (affected C++ & C# translations).
* BugFix: fixed a minor bug in OffsetPolygons' function
declaration (affected C++ translation only).
* Change: 'clipper' namespace changed to 'ClipperLib'
namespace in both C++ and C# code to remove the ambiguity
between the Clipper class and the namespace. (This also
required numerous updates to the accompanying demos.)
v4.4.2 - 26 August 2011
* BugFix: minor bugfixes in Clipper.
* Update: the OffsetPolygons function has been significantly
improved by offering 4 different join styles.
v4.4.0 - 6 August 2011
* BugFix: A number of minor bugs have been fixed that mostly
affected the new ExPolygons structure.
v4.3.0 - 17 June 2011
* New: ExPolygons structure that explicitly associates 'hole'
polygons with their 'outer' container polygons.
* New: Execute method overloaded so the solution parameter
can now be either Polygons or ExPolygons.
* BugFix: Fixed a rare bug in solution polygons orientation.
v4.2.8 - 21 May 2011
* Update: JoinCommonEdges() improved once more.
* BugFix: Several minor bugs fixed.
v4.2.6 - 1 May 2011
* Bugfix: minor bug in SlopesEqual function.
* Update: Merging of output polygons sharing common edges
has been significantly inproved
v4.2.4 - 26 April 2011
Input polygon coordinates can now contain the full range of
signed 64bit integers (ie +/-9,223,372,036,854,775,807). This
means that floating point values can be converted to and from
Clipper's 64bit integer coordinates structure (IntPoint) and
still retain a precision of up to 18 decimal places. However,
since the large-integer math that supports this expanded range
imposes a small cost on performance (~15%), a new property
UseFullCoordinateRange has been added to the Clipper class to
allow users the choice of whether or not to use this expanded
coordinate range. If this property is disabled, coordinate values
are restricted to +/-1,500,000,000.
v4.2 - 12 April 2011
JoinCommonEdges() code significantly improved plus other minor
improvements.
v4.1.2 - 9 April 2011
* Update: Minor code tidy.
* Bugfix: Possible endless loop in JoinCommonEdges() in clipper.pas.
v4.1.1 - 8 April 2011
* Update: All polygon coordinates are now stored as 64bit integers
(though they're still restricted to range -1.5e9 to +1.5e9 pending
the inclusion of code supporting 64bit math).
* Change: AddPolygon and AddPolygons methods now return boolean
values.
* Bugfix: Bug in JoinCommonEdges() caused potential endless loop.
* Bugfix: Bug in IsClockwise(). (C++ code only)
v4.0 - 5 April 2011
* Clipper 4 is a major rewrite of earlier versions. The biggest
change is that floating point values are no longer used,
except for the storing of edge slope values. The main benefit
of this is the issue of numerical robustness has been
addressed. Due to other major code improvements Clipper v4
is approximately 40% faster than Clipper v3.
* The AddPolyPolygon method has been renamed to AddPolygons.
* The IgnoreOrientation property has been removed.
* The clipper_misc library has been merged back into the
main clipper library.
v3.1.0 - 17 February 2011
* Bugfix: Obscure bug in TClipperBase.SetDx method that caused
problems with very small edges ( edges <1/1000th pixel in size).
v3.0.3 - 9 February 2011
* Bugfix: Significant bug, but only in C# code.
* Update: Minor refactoring.
v3.0 - 31 January 2011
* Update: Major rewrite of the portion of code that calculates
the output polygons' orientation.
* Update: Help file significantly improved.
* Change: Renamed ForceOrientation property to IgnoreOrientation.
If the orientation of output polygons is not important, or can
be managed separately, clipping routines can be sped up by about
60% by setting IgnoreOrientation to true. Defaults to false.
* Change: The OffsetPolygon and Area functions have been moved to
the new unit - clipper_misc.
2.99 - 15 January 2011
* Bugfix: Obscure bug in AddPolygon method could cause an endless loop.
2.8 - 20 November 2010
* Updated: Output polygons which previously shared a common
edge are now merged.
* Changed: The orientation of outer polygons is now clockwise
when the display's Y axis is positive downwards (as is
typical for most Windows applications). Inner polygons
(holes) have the opposite orientation.
* Added: Support module for Cairo Graphics Library (with demo).
* Updated: C# and C++ demos.
2.522 - 15 October 2010
* Added C# translation (thanks to Olivier Lejeune) and
a link to Ruby bindings (thanks to Mike Owens).
2.0 - 30 July 2010
* Clipper now clips using both the Even-Odd (alternate) and
Non-Zero (winding) polygon filling rules. (Previously Clipper
assumed the Even-Odd rule for polygon filling.)
1.4c - 16 June 2010
* Added C++ support for AGG graphics library
1.2s - 2 June 2010
* Added C++ translation of clipper.pas
1.0 - 9 May 2010

4913
External/Clipper/Clipper.cs vendored Normal file

File diff suppressed because it is too large Load Diff

24
External/Clipper/LICENSE.md vendored Normal file
View File

@ -0,0 +1,24 @@
Boost Software License - Version 1.0 - August 17th, 2003
http://www.boost.org/LICENSE_1_0.txt
Permission is hereby granted, free of charge, to any person or organization
obtaining a copy of the software and accompanying documentation covered by
this license (the "Software") to use, reproduce, display, distribute,
execute, and transmit the Software, and to prepare derivative works of the
Software, and to permit third-parties to whom the Software is furnished to
do so, all subject to the following:
The copyright notices in the Software and this entire statement, including
the above license grant, this restriction and the following disclaimer,
must be included in all copies of the Software, in whole or in part, and
all derivative works of the Software, unless such copies or derivative
works are solely in the form of machine-executable object code generated by
a source language processor.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.

11
External/Clipper/README.md vendored Normal file
View File

@ -0,0 +1,11 @@
# **Clipper** (6.4.2) by Angus Johnson
The Clipper library performs clipping and offsetting for both lines and polygons. All four boolean clipping operations are supported - intersection, union, difference and exclusive-or. Polygons can be of any shape including self-intersecting polygons.
## Attribution
The libray is created and maintained by Angus Johnson. This repository is a fork of the original [**Clipper** repository at SourceForge](https://sourceforge.net/projects/polyclipping). This branch contains the C# source only. See [`master`](https://github.com/eppz/Clipper/tree/master) for the full **Clipper** repository mirror, or visit the original [**Clipper** repository at SourceForge](https://sourceforge.net/projects/polyclipping).
## License
> Licensed under the [**Boost Software License** (BSL1.0)](http://www.boost.org/LICENSE_1_0.txt).

26
External/Triangle.NET/CHANGELOG.md vendored Normal file
View File

@ -0,0 +1,26 @@
Triangle.NET (Release)
* beta 4.03
+ Contributions (removed `AssemblyInfo.cs`)
* beta 4.02
+ Contributions (to fix Unity compiler warnings) by [@eppz](https://github.com/eppz)
+ Supressed unused variable warning in `Dwyer.cs`
* beta 4.01
+ Contributions (to fix Unity compiler errors) by [@eppz](https://github.com/eppz)
+ Explicit collection type casting fixes
+ Emulate .NET 4 `String.IsNullOrWhiteSpace` in `TriangleReader.cs`
+ See `bool IsStringNullOrWhiteSpace(string value)`
* beta 4
+ See details in [**beta_4** commit history](https://github.com/eppz/Triangle.NET/commits/beta_4).
* beta 3
+ See details in [**beta_3** commit history](https://github.com/eppz/Triangle.NET/commits/beta_3).

11
External/Triangle.NET/README.md vendored Normal file
View File

@ -0,0 +1,11 @@
# **Triangle.NET** (beta 4) by Christian Woltering
Triangle.NET generates 2D (constrained) Delaunay triangulations and high-quality meshes of point sets or planar straight line graphs. It is a C# port of Jonathan Shewchuk's [Triangle](http://www.cs.cmu.edu/~quake/triangle.html) software.
## Attribution
This C# libray is created and maintained by Christian Woltering. This repository is a fork of the original [**Triangle.NET** repository at CodePlex](https://triangle.codeplex.com). This branch contains the C# source only. See [`master`](https://github.com/eppz/Triangle.NET/tree/master) for the full Triangle.NET repository mirror, or visit the original [**Triangle.NET** repository at CodePlex](https://triangle.codeplex.com).
## License
> Licensed under the [**MIT License**](https://en.wikipedia.org/wiki/MIT_License).

View File

@ -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 TriangleNet
{
using System;
using 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>
public 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
}
}

View File

@ -0,0 +1,44 @@
// -----------------------------------------------------------------------
// <copyright file="Configuration.cs" company="">
// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/
// </copyright>
// -----------------------------------------------------------------------
namespace TriangleNet
{
using System;
using TriangleNet.Meshing;
using TriangleNet.Meshing.Algorithm;
/// <summary>
/// Configure advanced aspects of the library.
/// </summary>
public 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; }
}
}

46
External/Triangle.NET/Triangle/Enums.cs vendored Normal file
View File

@ -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 TriangleNet
{
/// <summary>
/// The type of the mesh vertex.
/// </summary>
public enum VertexType { InputVertex, SegmentVertex, FreeVertex, DeadVertex, UndeadVertex };
/// <summary>
/// Node renumbering algorithms.
/// </summary>
public 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>
public 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 };
}

View File

@ -0,0 +1,247 @@
// -----------------------------------------------------------------------
// <copyright file="Contour.cs" company="">
// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/
// </copyright>
// -----------------------------------------------------------------------
namespace TriangleNet.Geometry
{
using System;
using System.Linq;
using System.Collections.Generic;
public 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
}
}

View File

@ -0,0 +1,58 @@
// -----------------------------------------------------------------------
// <copyright file="Edge.cs" company="">
// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/
// </copyright>
// -----------------------------------------------------------------------
namespace TriangleNet.Geometry
{
/// <summary>
/// Represents a straight line segment in 2D space.
/// </summary>
public 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;
}
}
}

View File

@ -0,0 +1,143 @@

namespace TriangleNet.Geometry
{
using System;
using TriangleNet.Meshing;
public static class ExtensionMethods
{
#region IPolygon extensions
/// <summary>
/// Triangulates a polygon.
/// </summary>
public 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>
public 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>
public 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>
public 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>
public 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>
public 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>
public 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;
}
public 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
}
}

View File

@ -0,0 +1,29 @@
// -----------------------------------------------------------------------
// <copyright file="IEdge.cs" company="">
// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/
// </copyright>
// -----------------------------------------------------------------------
namespace TriangleNet.Geometry
{
public 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; }
}
}

View File

@ -0,0 +1,93 @@
// -----------------------------------------------------------------------
// <copyright file="IPolygon.cs" company="">
// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/
// </copyright>
// -----------------------------------------------------------------------
namespace TriangleNet.Geometry
{
using System;
using System.Collections.Generic;
/// <summary>
/// Polygon interface.
/// </summary>
public 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);
}
}

View File

@ -0,0 +1,26 @@
// -----------------------------------------------------------------------
// <copyright file="ISegment.cs" company="">
// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/
// </copyright>
// -----------------------------------------------------------------------
namespace TriangleNet.Geometry
{
/// <summary>
/// Interface for segment geometry.
/// </summary>
public 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);
}
}

View File

@ -0,0 +1,69 @@
// -----------------------------------------------------------------------
// <copyright file="ITriangle.cs" company="">
// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/
// </copyright>
// -----------------------------------------------------------------------
namespace TriangleNet.Geometry
{
using TriangleNet.Topology;
/// <summary>
/// Triangle interface.
/// </summary>
public 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);
}
}

View File

@ -0,0 +1,180 @@
// -----------------------------------------------------------------------
// <copyright file="Point.cs" company="">
// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/
// </copyright>
// -----------------------------------------------------------------------
namespace 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
public 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;
}
}
}

View File

@ -0,0 +1,184 @@
// -----------------------------------------------------------------------
// <copyright file="Polygon.cs" company="">
// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/
// </copyright>
// -----------------------------------------------------------------------
namespace TriangleNet.Geometry
{
using System;
using System.Linq;
using System.Collections.Generic;
/// <summary>
/// A polygon represented as a planar straight line graph.
/// </summary>
public 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);
}
}
}

View File

@ -0,0 +1,189 @@
// -----------------------------------------------------------------------
// <copyright file="Rectangle.cs" company="">
// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/
// </copyright>
// -----------------------------------------------------------------------
namespace TriangleNet.Geometry
{
using System;
using System.Collections.Generic;
/// <summary>
/// A simple rectangle class.
/// </summary>
public 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);
}
}
}

View File

@ -0,0 +1,63 @@
// -----------------------------------------------------------------------
// <copyright file="RegionPointer.cs" company="">
// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/
// </copyright>
// -----------------------------------------------------------------------
namespace 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>
public 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;
}
}
}

View File

@ -0,0 +1,92 @@
// -----------------------------------------------------------------------
// <copyright file="Segment.cs" company="">
// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/
// </copyright>
// -----------------------------------------------------------------------
namespace TriangleNet.Geometry
{
using System;
/// <summary>
/// Represents a straight line segment in 2D space.
/// </summary>
public 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();
}
}
}

View File

@ -0,0 +1,125 @@
// -----------------------------------------------------------------------
// <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 TriangleNet.Geometry
{
using System;
using TriangleNet.Topology;
/// <summary>
/// The vertex data structure.
/// </summary>
public 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;
}
}
}

View File

@ -0,0 +1,263 @@
// -----------------------------------------------------------------------
// <copyright file="DebugWriter.cs" company="">
// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/
// </copyright>
// -----------------------------------------------------------------------
namespace TriangleNet.IO
{
using System;
using System.Globalization;
using System.IO;
using System.IO.Compression;
using System.Text;
using TriangleNet.Topology;
using 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() { }
public 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;
}
}
}
}

View File

@ -0,0 +1,126 @@
// -----------------------------------------------------------------------
// <copyright file="FileProcessor.cs" company="">
// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/
// </copyright>
// -----------------------------------------------------------------------
namespace TriangleNet.IO
{
using System;
using System.Collections.Generic;
using TriangleNet.Geometry;
using TriangleNet.Meshing;
public static class FileProcessor
{
static List<IFileFormat> formats;
static FileProcessor()
{
formats = new List<IFileFormat>();
// Add Triangle file format as default.
formats.Add(new TriangleFormat());
}
public static void Add(IFileFormat format)
{
formats.Add(format);
}
public 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>
public 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>
public 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>
public 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>
public 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
}
}

View File

@ -0,0 +1,13 @@
// -----------------------------------------------------------------------
// <copyright file="IFileFormat.cs" company="">
// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/
// </copyright>
// -----------------------------------------------------------------------
namespace TriangleNet.IO
{
public interface IFileFormat
{
bool IsSupported(string file);
}
}

View File

@ -0,0 +1,38 @@
// -----------------------------------------------------------------------
// <copyright file="IMeshFormat.cs" company="">
// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/
// </copyright>
// -----------------------------------------------------------------------
namespace TriangleNet.IO
{
using System.IO;
using TriangleNet.Meshing;
/// <summary>
/// Interface for mesh I/O.
/// </summary>
public 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);
}
}

View File

@ -0,0 +1,38 @@
// -----------------------------------------------------------------------
// <copyright file="IPolygonFormat.cs" company="">
// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/
// </copyright>
// -----------------------------------------------------------------------
namespace TriangleNet.IO
{
using System.IO;
using TriangleNet.Geometry;
/// <summary>
/// Interface for geometry input.
/// </summary>
public 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);
}
}

View File

@ -0,0 +1,85 @@
// -----------------------------------------------------------------------
// <copyright file="InputTriangle.cs" company="">
// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/
// </copyright>
// -----------------------------------------------------------------------
namespace TriangleNet.IO
{
using TriangleNet.Topology;
using TriangleNet.Geometry;
/// <summary>
/// Simple triangle class for input.
/// </summary>
public 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
}
}

View File

@ -0,0 +1,92 @@
// -----------------------------------------------------------------------
// <copyright file="TriangleFormat.cs" company="">
// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/
// </copyright>
// -----------------------------------------------------------------------
namespace TriangleNet.IO
{
using System;
using System.Collections.Generic;
using System.IO;
using TriangleNet.Geometry;
using TriangleNet.Meshing;
/// <summary>
/// Implements geometry and mesh file formats of the the original Triangle code.
/// </summary>
public 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();
}
}
}

View File

@ -0,0 +1,756 @@
// -----------------------------------------------------------------------
// <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 TriangleNet.IO
{
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using TriangleNet.Geometry;
/// <summary>
/// Helper methods for reading Triangle file formats.
/// </summary>
public 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;
}
}
}

View File

@ -0,0 +1,459 @@
// -----------------------------------------------------------------------
// <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 TriangleNet.IO
{
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using TriangleNet.Geometry;
using TriangleNet.Topology;
/// <summary>
/// Helper methods for writing Triangle file formats.
/// </summary>
public 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);
}
}
}
}
}

View File

@ -0,0 +1,22 @@
// -----------------------------------------------------------------------
// <copyright file="IPredicates.cs">
// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/
// </copyright>
// -----------------------------------------------------------------------
namespace TriangleNet
{
using TriangleNet.Geometry;
public 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);
}
}

84
External/Triangle.NET/Triangle/Log.cs vendored Normal file
View File

@ -0,0 +1,84 @@
// -----------------------------------------------------------------------
// <copyright file="Log.cs" company="">
// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/
// </copyright>
// -----------------------------------------------------------------------
namespace TriangleNet
{
using System.Collections.Generic;
using 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>
public sealed class Log : ILog<LogItem>
{
/// <summary>
/// Log detailed information.
/// </summary>
public 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() { }
public 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; }
}
}
}

View File

@ -0,0 +1,34 @@
// -----------------------------------------------------------------------
// <copyright file="ILog.cs" company="">
// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/
// </copyright>
// -----------------------------------------------------------------------
namespace TriangleNet.Logging
{
using System.Collections.Generic;
public enum LogLevel
{
Info = 0,
Warning = 1,
Error = 2
}
/// <summary>
/// A basic log interface.
/// </summary>
public 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; }
}
}

View File

@ -0,0 +1,21 @@
// -----------------------------------------------------------------------
// <copyright file="ILogItem.cs" company="">
// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/
// </copyright>
// -----------------------------------------------------------------------
namespace TriangleNet.Logging
{
using System;
/// <summary>
/// A basic log item interface.
/// </summary>
public interface ILogItem
{
DateTime Time { get; }
LogLevel Level { get; }
string Message { get; }
string Info { get; }
}
}

View File

@ -0,0 +1,53 @@
// -----------------------------------------------------------------------
// <copyright file="SimpleLogItem.cs" company="">
// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/
// </copyright>
// -----------------------------------------------------------------------
namespace TriangleNet.Logging
{
using System;
/// <summary>
/// Represents an item stored in the log.
/// </summary>
public 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;
}
}
}

1768
External/Triangle.NET/Triangle/Mesh.cs vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,215 @@
// -----------------------------------------------------------------------
// <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 TriangleNet
{
using System;
using TriangleNet.Topology;
using TriangleNet.Geometry;
public static class MeshValidator
{
private static RobustPredicates predicates = RobustPredicates.Default;
/// <summary>
/// Test the mesh for topological consistency.
/// </summary>
public 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>
public static bool IsDelaunay(Mesh mesh)
{
return IsDelaunay(mesh, false);
}
/// <summary>
/// Check if that the mesh is (constrained) Delaunay.
/// </summary>
public 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);
}
}
}

View File

@ -0,0 +1,694 @@
// -----------------------------------------------------------------------
// <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 TriangleNet.Meshing.Algorithm
{
using System;
using System.Collections.Generic;
using TriangleNet.Geometry;
using TriangleNet.Tools;
using 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>
public 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 entry, '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;
}
}
}

View File

@ -0,0 +1,191 @@
// -----------------------------------------------------------------------
// <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 TriangleNet.Meshing.Algorithm
{
using System.Collections.Generic;
using TriangleNet.Topology;
using TriangleNet.Geometry;
/// <summary>
/// Builds a delaunay triangulation using the incremental algorithm.
/// </summary>
public 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;
}
}
}

View File

@ -0,0 +1,808 @@
// -----------------------------------------------------------------------
// <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 TriangleNet.Meshing.Algorithm
{
using System;
using System.Collections.Generic;
using TriangleNet.Topology;
using TriangleNet.Geometry;
using TriangleNet.Tools;
/// <summary>
/// Builds a delaunay triangulation using the sweepline algorithm.
/// </summary>
public 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
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,40 @@

namespace TriangleNet.Meshing
{
/// <summary>
/// Mesh constraint options for polygon triangulation.
/// </summary>
public 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; }
}
}

View File

@ -0,0 +1,487 @@
// -----------------------------------------------------------------------
// <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 TriangleNet.Meshing
{
using System;
using System.Collections.Generic;
using System.Linq;
using TriangleNet.Geometry;
using TriangleNet.Topology;
using TriangleNet.Topology.DCEL;
using HVertex = TriangleNet.Topology.DCEL.Vertex;
using TVertex = TriangleNet.Geometry.Vertex;
/// <summary>
/// The Converter class provides methods for mesh reconstruction and conversion.
/// </summary>
public static class Converter
{
#region Triangle mesh conversion
/// <summary>
/// Reconstruct a triangulation from its raw data representation.
/// </summary>
public static Mesh ToMesh(Polygon polygon, IList<ITriangle> triangles)
{
return ToMesh(polygon, triangles.ToArray());
}
/// <summary>
/// Reconstruct a triangulation from its raw data representation.
/// </summary>
public 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
public 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);
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
}
}

View File

@ -0,0 +1,36 @@
// -----------------------------------------------------------------------
// <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 TriangleNet.Meshing.Data
{
using System;
using TriangleNet.Geometry;
using 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);
}
}
}

View File

@ -0,0 +1,194 @@
// -----------------------------------------------------------------------
// <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 TriangleNet.Meshing.Data
{
using TriangleNet.Geometry;
using 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;
}
}
}

View File

@ -0,0 +1,34 @@
// -----------------------------------------------------------------------
// <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 TriangleNet.Meshing.Data
{
using System;
using TriangleNet.Geometry;
using 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);
}
}
}

View File

@ -0,0 +1,233 @@
// -----------------------------------------------------------------------
// <copyright file="GenericMesher.cs">
// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/
// </copyright>
// -----------------------------------------------------------------------
namespace TriangleNet.Meshing
{
using System;
using System.Collections.Generic;
using TriangleNet.Geometry;
using TriangleNet.IO;
using TriangleNet.Meshing.Algorithm;
/// <summary>
/// Create meshes of point sets or polygons.
/// </summary>
public 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>
public 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>
public 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);
}
}
}

View File

@ -0,0 +1,26 @@

namespace TriangleNet.Meshing
{
using TriangleNet.Geometry;
/// <summary>
/// Interface for polygon triangulation.
/// </summary>
public 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);
}
}

View File

@ -0,0 +1,57 @@

namespace TriangleNet.Meshing
{
using System.Collections.Generic;
using TriangleNet.Topology;
using TriangleNet.Geometry;
/// <summary>
/// Mesh interface.
/// </summary>
public 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);
}
}

View File

@ -0,0 +1,28 @@

namespace TriangleNet.Meshing
{
using TriangleNet.Geometry;
/// <summary>
/// Interface for polygon triangulation with quality constraints.
/// </summary>
public 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);
}
}

View File

@ -0,0 +1,25 @@
// -----------------------------------------------------------------------
// <copyright file="ITriangulator.cs" company="">
// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/
// </copyright>
// -----------------------------------------------------------------------
namespace TriangleNet.Meshing
{
using System.Collections.Generic;
using TriangleNet.Geometry;
/// <summary>
/// Interface for point set triangulation.
/// </summary>
public 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);
}
}

View File

@ -0,0 +1,101 @@
// -----------------------------------------------------------------------
// <copyright file="EdgeEnumerator.cs" company="">
// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/
// </copyright>
// -----------------------------------------------------------------------
namespace TriangleNet.Meshing.Iterators
{
using System.Collections.Generic;
using TriangleNet.Topology;
using TriangleNet.Geometry;
/// <summary>
/// Enumerates the edges of a triangulation.
/// </summary>
public 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();
}
}
}

View File

@ -0,0 +1,135 @@
// -----------------------------------------------------------------------
// <copyright file="RegionIterator.cs" company="">
// Original Matlab code by John Burkardt, Florida State University
// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/
// </copyright>
// -----------------------------------------------------------------------
namespace TriangleNet.Meshing.Iterators
{
using System;
using System.Collections.Generic;
using TriangleNet.Topology;
/// <summary>
/// Iterates the region a given triangle belongs to and applies an action
/// to each connected trianlge in that region.
/// </summary>
/// <remarks>
/// The default action is to set the region id and area constraint.
/// </remarks>
public class RegionIterator
{
List<Triangle> region;
public RegionIterator(Mesh mesh)
{
this.region = new List<Triangle>();
}
/// <summary>
/// Set the region attribute of all trianlges connected to given triangle.
/// </summary>
/// <param name="triangle">The triangle seed.</param>
/// <param name="boundary">If non-zero, process all triangles of the
/// region that is enclosed by segments with given boundary label.</param>
public void Process(Triangle triangle, int boundary = 0)
{
this.Process(triangle, (tri) =>
{
// Set the region id and area constraint.
tri.label = triangle.label;
tri.area = triangle.area;
}, boundary);
}
/// <summary>
/// Process all trianlges connected to given triangle and apply given action.
/// </summary>
/// <param name="triangle">The seeding triangle.</param>
/// <param name="action">The action to apply to each triangle.</param>
/// <param name="boundary">If non-zero, process all triangles of the
/// region that is enclosed by segments with given boundary label.</param>
public void Process(Triangle triangle, Action<Triangle> action, int boundary = 0)
{
// Make sure the triangle under consideration still exists.
// It may have been eaten by the virus.
if (triangle.id == Mesh.DUMMY || Otri.IsDead(triangle))
{
return;
}
// Add the seeding triangle to the region.
region.Add(triangle);
triangle.infected = true;
if (boundary == 0)
{
// Stop at any subsegment.
ProcessRegion(action, seg => seg.hash == Mesh.DUMMY);
}
else
{
// Stop at segments that have the given boundary label.
ProcessRegion(action, seg => seg.boundary != boundary);
}
// Free up memory (virus pool should be empty anyway).
region.Clear();
}
/// <summary>
/// Apply given action to each triangle of selected region.
/// </summary>
/// <param name="action"></param>
/// <param name="protector"></param>
void ProcessRegion(Action<Triangle> action, Func<SubSegment, bool> protector)
{
Otri testtri = default(Otri);
Otri neighbor = default(Otri);
Osub neighborsubseg = default(Osub);
// Loop through all the infected triangles, spreading the attribute
// and/or area constraint to their neighbors, then to their neighbors'
// neighbors.
for (int i = 0; i < region.Count; i++)
{
// WARNING: Don't use foreach, viri list gets modified.
testtri.tri = region[i];
// Apply function.
action(testtri.tri);
// Check each of the triangle's three neighbors.
for (testtri.orient = 0; testtri.orient < 3; testtri.orient++)
{
// Find the neighbor.
testtri.Sym(ref neighbor);
// Check for a subsegment between the triangle and its neighbor.
testtri.Pivot(ref neighborsubseg);
// Make sure the neighbor exists, is not already infected, and
// isn't protected by a subsegment.
if ((neighbor.tri.id != Mesh.DUMMY) && !neighbor.IsInfected()
&& protector(neighborsubseg.seg))
{
// Infect the neighbor.
neighbor.Infect();
// Ensure that the neighbor's neighbors will be infected.
region.Add(neighbor.tri);
}
}
}
// Uninfect all triangles.
foreach (var virus in region)
{
virus.infected = false;
}
// Empty the virus pool.
region.Clear();
}
}
}

View File

@ -0,0 +1,100 @@

namespace TriangleNet.Meshing.Iterators
{
using System.Collections.Generic;
using TriangleNet.Geometry;
using TriangleNet.Topology;
public class VertexCirculator
{
List<Otri> cache = new List<Otri>();
public VertexCirculator(Mesh mesh)
{
mesh.MakeVertexMap();
}
/// <summary>
/// Enumerate all vertices adjacent to given vertex.
/// </summary>
/// <param name="vertex">The center vertex.</param>
/// <returns></returns>
public IEnumerable<Vertex> EnumerateVertices(Vertex vertex)
{
BuildCache(vertex, true);
foreach (var item in cache)
{
yield return item.Dest();
}
}
/// <summary>
/// Enumerate all triangles adjacent to given vertex.
/// </summary>
/// <param name="vertex">The center vertex.</param>
/// <returns></returns>
public IEnumerable<ITriangle> EnumerateTriangles(Vertex vertex)
{
BuildCache(vertex, false);
foreach (var item in cache)
{
yield return item.tri;
}
}
private void BuildCache(Vertex vertex, bool vertices)
{
cache.Clear();
Otri init = vertex.tri;
Otri next = default(Otri);
Otri prev = default(Otri);
init.Copy(ref next);
// Move counter-clockwise around the vertex.
while (next.tri.id != Mesh.DUMMY)
{
cache.Add(next);
next.Copy(ref prev);
next.Onext();
if (next.Equals(init))
{
break;
}
}
if (next.tri.id == Mesh.DUMMY)
{
// We reached the boundary. To get all adjacent triangles, start
// again at init triangle and now move clockwise.
init.Copy(ref next);
if (vertices)
{
// Don't forget to add the vertex lying on the boundary.
prev.Lnext();
cache.Add(prev);
}
next.Oprev();
while (next.tri.id != Mesh.DUMMY)
{
cache.Insert(0, next);
next.Oprev();
if (next.Equals(init))
{
break;
}
}
}
}
}
}

View File

@ -0,0 +1,898 @@
// -----------------------------------------------------------------------
// <copyright file="QualityMesher.cs">
// Original Triangle code by Jonathan Richard Shewchuk, http://www.cs.cmu.edu/~quake/triangle.html
// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/
// </copyright>
// -----------------------------------------------------------------------
namespace TriangleNet.Meshing
{
using System;
using System.Collections.Generic;
using TriangleNet.Geometry;
using TriangleNet.Logging;
using TriangleNet.Meshing.Data;
using TriangleNet.Tools;
using TriangleNet.Topology;
/// <summary>
/// Provides methods for mesh quality enforcement and testing.
/// </summary>
class QualityMesher
{
IPredicates predicates;
Queue<BadSubseg> badsubsegs;
BadTriQueue queue;
Mesh mesh;
Behavior behavior;
NewLocation newLocation;
ILog<LogItem> logger;
// Stores the vertices of the triangle that contains newvertex
// in SplitTriangle method.
Triangle newvertex_tri;
public QualityMesher(Mesh mesh, Configuration config)
{
logger = Log.Instance;
badsubsegs = new Queue<BadSubseg>();
queue = new BadTriQueue();
this.mesh = mesh;
this.predicates = config.Predicates();
this.behavior = mesh.behavior;
newLocation = new NewLocation(mesh, predicates);
newvertex_tri = new Triangle();
}
/// <summary>
/// Apply quality constraints to a mesh.
/// </summary>
/// <param name="quality">The quality constraints.</param>
/// <param name="delaunay">A value indicating, if the refined mesh should be Conforming Delaunay.</param>
public void Apply(QualityOptions quality, bool delaunay = false)
{
// Copy quality options
if (quality != null)
{
behavior.Quality = true;
behavior.MinAngle = quality.MinimumAngle;
behavior.MaxAngle = quality.MaximumAngle;
behavior.MaxArea = quality.MaximumArea;
behavior.UserTest = quality.UserTest;
behavior.VarArea = quality.VariableArea;
behavior.ConformingDelaunay = behavior.ConformingDelaunay || delaunay;
mesh.steinerleft = quality.SteinerPoints == 0 ? -1 : quality.SteinerPoints;
}
// TODO: remove
if (!behavior.Poly)
{
// Be careful not to allocate space for element area constraints that
// will never be assigned any value (other than the default -1.0).
behavior.VarArea = false;
}
// Ensure that no vertex can be mistaken for a triangular bounding
// box vertex in insertvertex().
mesh.infvertex1 = null;
mesh.infvertex2 = null;
mesh.infvertex3 = null;
if (behavior.useSegments)
{
mesh.checksegments = true;
}
if (behavior.Quality && mesh.triangles.Count > 0)
{
// Enforce angle and area constraints.
EnforceQuality();
}
}
/// <summary>
/// Add a bad subsegment to the queue.
/// </summary>
/// <param name="badseg">Bad subsegment.</param>
public void AddBadSubseg(BadSubseg badseg)
{
badsubsegs.Enqueue(badseg);
}
#region Check
/// <summary>
/// Check a subsegment to see if it is encroached; add it to the list if it is.
/// </summary>
/// <param name="testsubseg">The subsegment to check.</param>
/// <returns>Returns a nonzero value if the subsegment is encroached.</returns>
/// <remarks>
/// A subsegment is encroached if there is a vertex in its diametral lens.
/// For Ruppert's algorithm (-D switch), the "diametral lens" is the
/// diametral circle. For Chew's algorithm (default), the diametral lens is
/// just big enough to enclose two isosceles triangles whose bases are the
/// subsegment. Each of the two isosceles triangles has two angles equal
/// to 'b.minangle'.
///
/// Chew's algorithm does not require diametral lenses at all--but they save
/// time. Any vertex inside a subsegment's diametral lens implies that the
/// triangle adjoining the subsegment will be too skinny, so it's only a
/// matter of time before the encroaching vertex is deleted by Chew's
/// algorithm. It's faster to simply not insert the doomed vertex in the
/// first place, which is why I use diametral lenses with Chew's algorithm.
/// </remarks>
public int CheckSeg4Encroach(ref Osub testsubseg)
{
Otri neighbortri = default(Otri);
Osub testsym = default(Osub);
BadSubseg encroachedseg;
double dotproduct;
int encroached;
int sides;
Vertex eorg, edest, eapex;
encroached = 0;
sides = 0;
eorg = testsubseg.Org();
edest = testsubseg.Dest();
// Check one neighbor of the subsegment.
testsubseg.Pivot(ref neighbortri);
// Does the neighbor exist, or is this a boundary edge?
if (neighbortri.tri.id != Mesh.DUMMY)
{
sides++;
// Find a vertex opposite this subsegment.
eapex = neighbortri.Apex();
// Check whether the apex is in the diametral lens of the subsegment
// (the diametral circle if 'conformdel' is set). A dot product
// of two sides of the triangle is used to check whether the angle
// at the apex is greater than (180 - 2 'minangle') degrees (for
// lenses; 90 degrees for diametral circles).
dotproduct = (eorg.x - eapex.x) * (edest.x - eapex.x) +
(eorg.y - eapex.y) * (edest.y - eapex.y);
if (dotproduct < 0.0)
{
if (behavior.ConformingDelaunay ||
(dotproduct * dotproduct >=
(2.0 * behavior.goodAngle - 1.0) * (2.0 * behavior.goodAngle - 1.0) *
((eorg.x - eapex.x) * (eorg.x - eapex.x) +
(eorg.y - eapex.y) * (eorg.y - eapex.y)) *
((edest.x - eapex.x) * (edest.x - eapex.x) +
(edest.y - eapex.y) * (edest.y - eapex.y))))
{
encroached = 1;
}
}
}
// Check the other neighbor of the subsegment.
testsubseg.Sym(ref testsym);
testsym.Pivot(ref neighbortri);
// Does the neighbor exist, or is this a boundary edge?
if (neighbortri.tri.id != Mesh.DUMMY)
{
sides++;
// Find the other vertex opposite this subsegment.
eapex = neighbortri.Apex();
// Check whether the apex is in the diametral lens of the subsegment
// (or the diametral circle, if 'conformdel' is set).
dotproduct = (eorg.x - eapex.x) * (edest.x - eapex.x) +
(eorg.y - eapex.y) * (edest.y - eapex.y);
if (dotproduct < 0.0)
{
if (behavior.ConformingDelaunay ||
(dotproduct * dotproduct >=
(2.0 * behavior.goodAngle - 1.0) * (2.0 * behavior.goodAngle - 1.0) *
((eorg.x - eapex.x) * (eorg.x - eapex.x) +
(eorg.y - eapex.y) * (eorg.y - eapex.y)) *
((edest.x - eapex.x) * (edest.x - eapex.x) +
(edest.y - eapex.y) * (edest.y - eapex.y))))
{
encroached += 2;
}
}
}
if (encroached > 0 && (behavior.NoBisect == 0 || ((behavior.NoBisect == 1) && (sides == 2))))
{
// Add the subsegment to the list of encroached subsegments.
// Be sure to get the orientation right.
encroachedseg = new BadSubseg();
if (encroached == 1)
{
encroachedseg.subseg = testsubseg;
encroachedseg.org = eorg;
encroachedseg.dest = edest;
}
else
{
encroachedseg.subseg = testsym;
encroachedseg.org = edest;
encroachedseg.dest = eorg;
}
badsubsegs.Enqueue(encroachedseg);
}
return encroached;
}
/// <summary>
/// Test a triangle for quality and size.
/// </summary>
/// <param name="testtri">Triangle to check.</param>
/// <remarks>
/// Tests a triangle to see if it satisfies the minimum angle condition and
/// the maximum area condition. Triangles that aren't up to spec are added
/// to the bad triangle queue.
/// </remarks>
public void TestTriangle(ref Otri testtri)
{
Otri tri1 = default(Otri), tri2 = default(Otri);
Osub testsub = default(Osub);
Vertex torg, tdest, tapex;
Vertex base1, base2;
Vertex org1, dest1, org2, dest2;
Vertex joinvertex;
double dxod, dyod, dxda, dyda, dxao, dyao;
double dxod2, dyod2, dxda2, dyda2, dxao2, dyao2;
double apexlen, orglen, destlen, minedge;
double angle;
double area;
double dist1, dist2;
double maxangle;
torg = testtri.Org();
tdest = testtri.Dest();
tapex = testtri.Apex();
dxod = torg.x - tdest.x;
dyod = torg.y - tdest.y;
dxda = tdest.x - tapex.x;
dyda = tdest.y - tapex.y;
dxao = tapex.x - torg.x;
dyao = tapex.y - torg.y;
dxod2 = dxod * dxod;
dyod2 = dyod * dyod;
dxda2 = dxda * dxda;
dyda2 = dyda * dyda;
dxao2 = dxao * dxao;
dyao2 = dyao * dyao;
// Find the lengths of the triangle's three edges.
apexlen = dxod2 + dyod2;
orglen = dxda2 + dyda2;
destlen = dxao2 + dyao2;
if ((apexlen < orglen) && (apexlen < destlen))
{
// The edge opposite the apex is shortest.
minedge = apexlen;
// Find the square of the cosine of the angle at the apex.
angle = dxda * dxao + dyda * dyao;
angle = angle * angle / (orglen * destlen);
base1 = torg;
base2 = tdest;
testtri.Copy(ref tri1);
}
else if (orglen < destlen)
{
// The edge opposite the origin is shortest.
minedge = orglen;
// Find the square of the cosine of the angle at the origin.
angle = dxod * dxao + dyod * dyao;
angle = angle * angle / (apexlen * destlen);
base1 = tdest;
base2 = tapex;
testtri.Lnext(ref tri1);
}
else
{
// The edge opposite the destination is shortest.
minedge = destlen;
// Find the square of the cosine of the angle at the destination.
angle = dxod * dxda + dyod * dyda;
angle = angle * angle / (apexlen * orglen);
base1 = tapex;
base2 = torg;
testtri.Lprev(ref tri1);
}
if (behavior.VarArea || behavior.fixedArea || (behavior.UserTest != null))
{
// Check whether the area is larger than permitted.
area = 0.5 * (dxod * dyda - dyod * dxda);
if (behavior.fixedArea && (area > behavior.MaxArea))
{
// Add this triangle to the list of bad triangles.
queue.Enqueue(ref testtri, minedge, tapex, torg, tdest);
return;
}
// Nonpositive area constraints are treated as unconstrained.
if ((behavior.VarArea) && (area > testtri.tri.area) && (testtri.tri.area > 0.0))
{
// Add this triangle to the list of bad triangles.
queue.Enqueue(ref testtri, minedge, tapex, torg, tdest);
return;
}
// Check whether the user thinks this triangle is too large.
if ((behavior.UserTest != null) && behavior.UserTest(testtri.tri, area))
{
queue.Enqueue(ref testtri, minedge, tapex, torg, tdest);
return;
}
}
// find the maximum edge and accordingly the pqr orientation
if ((apexlen > orglen) && (apexlen > destlen))
{
// The edge opposite the apex is longest.
// maxedge = apexlen;
// Find the cosine of the angle at the apex.
maxangle = (orglen + destlen - apexlen) / (2 * Math.Sqrt(orglen * destlen));
}
else if (orglen > destlen)
{
// The edge opposite the origin is longest.
// maxedge = orglen;
// Find the cosine of the angle at the origin.
maxangle = (apexlen + destlen - orglen) / (2 * Math.Sqrt(apexlen * destlen));
}
else
{
// The edge opposite the destination is longest.
// maxedge = destlen;
// Find the cosine of the angle at the destination.
maxangle = (apexlen + orglen - destlen) / (2 * Math.Sqrt(apexlen * orglen));
}
// Check whether the angle is smaller than permitted.
if ((angle > behavior.goodAngle) || (maxangle < behavior.maxGoodAngle && behavior.MaxAngle != 0.0))
{
// Use the rules of Miller, Pav, and Walkington to decide that certain
// triangles should not be split, even if they have bad angles.
// A skinny triangle is not split if its shortest edge subtends a
// small input angle, and both endpoints of the edge lie on a
// concentric circular shell. For convenience, I make a small
// adjustment to that rule: I check if the endpoints of the edge
// both lie in segment interiors, equidistant from the apex where
// the two segments meet.
// First, check if both points lie in segment interiors.
if ((base1.type == VertexType.SegmentVertex) &&
(base2.type == VertexType.SegmentVertex))
{
// Check if both points lie in a common segment. If they do, the
// skinny triangle is enqueued to be split as usual.
tri1.Pivot(ref testsub);
if (testsub.seg.hash == Mesh.DUMMY)
{
// No common segment. Find a subsegment that contains 'torg'.
tri1.Copy(ref tri2);
do
{
tri1.Oprev();
tri1.Pivot(ref testsub);
} while (testsub.seg.hash == Mesh.DUMMY);
// Find the endpoints of the containing segment.
org1 = testsub.SegOrg();
dest1 = testsub.SegDest();
// Find a subsegment that contains 'tdest'.
do
{
tri2.Dnext();
tri2.Pivot(ref testsub);
} while (testsub.seg.hash == Mesh.DUMMY);
// Find the endpoints of the containing segment.
org2 = testsub.SegOrg();
dest2 = testsub.SegDest();
// Check if the two containing segments have an endpoint in common.
joinvertex = null;
if ((dest1.x == org2.x) && (dest1.y == org2.y))
{
joinvertex = dest1;
}
else if ((org1.x == dest2.x) && (org1.y == dest2.y))
{
joinvertex = org1;
}
if (joinvertex != null)
{
// Compute the distance from the common endpoint (of the two
// segments) to each of the endpoints of the shortest edge.
dist1 = ((base1.x - joinvertex.x) * (base1.x - joinvertex.x) +
(base1.y - joinvertex.y) * (base1.y - joinvertex.y));
dist2 = ((base2.x - joinvertex.x) * (base2.x - joinvertex.x) +
(base2.y - joinvertex.y) * (base2.y - joinvertex.y));
// If the two distances are equal, don't split the triangle.
if ((dist1 < 1.001 * dist2) && (dist1 > 0.999 * dist2))
{
// Return now to avoid enqueueing the bad triangle.
return;
}
}
}
}
// Add this triangle to the list of bad triangles.
queue.Enqueue(ref testtri, minedge, tapex, torg, tdest);
}
}
#endregion
#region Maintanance
/// <summary>
/// Traverse the entire list of subsegments, and check each to see if it
/// is encroached. If so, add it to the list.
/// </summary>
private void TallyEncs()
{
Osub subsegloop = default(Osub);
subsegloop.orient = 0;
foreach (var seg in mesh.subsegs.Values)
{
subsegloop.seg = seg;
// If the segment is encroached, add it to the list.
CheckSeg4Encroach(ref subsegloop);
}
}
/// <summary>
/// Split all the encroached subsegments.
/// </summary>
/// <param name="triflaws">A flag that specifies whether one should take
/// note of new bad triangles that result from inserting vertices to repair
/// encroached subsegments.</param>
/// <remarks>
/// Each encroached subsegment is repaired by splitting it - inserting a
/// vertex at or near its midpoint. Newly inserted vertices may encroach
/// upon other subsegments; these are also repaired.
/// </remarks>
private void SplitEncSegs(bool triflaws)
{
Otri enctri = default(Otri);
Otri testtri = default(Otri);
Osub testsh = default(Osub);
Osub currentenc = default(Osub);
BadSubseg seg;
Vertex eorg, edest, eapex;
Vertex newvertex;
InsertVertexResult success;
double segmentlength, nearestpoweroftwo;
double split;
double multiplier, divisor;
bool acuteorg, acuteorg2, acutedest, acutedest2;
// Note that steinerleft == -1 if an unlimited number
// of Steiner points is allowed.
while (badsubsegs.Count > 0)
{
if (mesh.steinerleft == 0)
{
break;
}
seg = badsubsegs.Dequeue();
currentenc = seg.subseg;
eorg = currentenc.Org();
edest = currentenc.Dest();
// Make sure that this segment is still the same segment it was
// when it was determined to be encroached. If the segment was
// enqueued multiple times (because several newly inserted
// vertices encroached it), it may have already been split.
if (!Osub.IsDead(currentenc.seg) && (eorg == seg.org) && (edest == seg.dest))
{
// To decide where to split a segment, we need to know if the
// segment shares an endpoint with an adjacent segment.
// The concern is that, if we simply split every encroached
// segment in its center, two adjacent segments with a small
// angle between them might lead to an infinite loop; each
// vertex added to split one segment will encroach upon the
// other segment, which must then be split with a vertex that
// will encroach upon the first segment, and so on forever.
// To avoid this, imagine a set of concentric circles, whose
// radii are powers of two, about each segment endpoint.
// These concentric circles determine where the segment is
// split. (If both endpoints are shared with adjacent
// segments, split the segment in the middle, and apply the
// concentric circles for later splittings.)
// Is the origin shared with another segment?
currentenc.Pivot(ref enctri);
enctri.Lnext(ref testtri);
testtri.Pivot(ref testsh);
acuteorg = testsh.seg.hash != Mesh.DUMMY;
// Is the destination shared with another segment?
testtri.Lnext();
testtri.Pivot(ref testsh);
acutedest = testsh.seg.hash != Mesh.DUMMY;
// If we're using Chew's algorithm (rather than Ruppert's)
// to define encroachment, delete free vertices from the
// subsegment's diametral circle.
if (!behavior.ConformingDelaunay && !acuteorg && !acutedest)
{
eapex = enctri.Apex();
while ((eapex.type == VertexType.FreeVertex) &&
((eorg.x - eapex.x) * (edest.x - eapex.x) +
(eorg.y - eapex.y) * (edest.y - eapex.y) < 0.0))
{
mesh.DeleteVertex(ref testtri);
currentenc.Pivot(ref enctri);
eapex = enctri.Apex();
enctri.Lprev(ref testtri);
}
}
// Now, check the other side of the segment, if there's a triangle there.
enctri.Sym(ref testtri);
if (testtri.tri.id != Mesh.DUMMY)
{
// Is the destination shared with another segment?
testtri.Lnext();
testtri.Pivot(ref testsh);
acutedest2 = testsh.seg.hash != Mesh.DUMMY;
acutedest = acutedest || acutedest2;
// Is the origin shared with another segment?
testtri.Lnext();
testtri.Pivot(ref testsh);
acuteorg2 = testsh.seg.hash != Mesh.DUMMY;
acuteorg = acuteorg || acuteorg2;
// Delete free vertices from the subsegment's diametral circle.
if (!behavior.ConformingDelaunay && !acuteorg2 && !acutedest2)
{
eapex = testtri.Org();
while ((eapex.type == VertexType.FreeVertex) &&
((eorg.x - eapex.x) * (edest.x - eapex.x) +
(eorg.y - eapex.y) * (edest.y - eapex.y) < 0.0))
{
mesh.DeleteVertex(ref testtri);
enctri.Sym(ref testtri);
eapex = testtri.Apex();
testtri.Lprev();
}
}
}
// Use the concentric circles if exactly one endpoint is shared
// with another adjacent segment.
if (acuteorg || acutedest)
{
segmentlength = Math.Sqrt((edest.x - eorg.x) * (edest.x - eorg.x) +
(edest.y - eorg.y) * (edest.y - eorg.y));
// Find the power of two that most evenly splits the segment.
// The worst case is a 2:1 ratio between subsegment lengths.
nearestpoweroftwo = 1.0;
while (segmentlength > 3.0 * nearestpoweroftwo)
{
nearestpoweroftwo *= 2.0;
}
while (segmentlength < 1.5 * nearestpoweroftwo)
{
nearestpoweroftwo *= 0.5;
}
// Where do we split the segment?
split = nearestpoweroftwo / segmentlength;
if (acutedest)
{
split = 1.0 - split;
}
}
else
{
// If we're not worried about adjacent segments, split
// this segment in the middle.
split = 0.5;
}
// Create the new vertex (interpolate coordinates).
newvertex = new Vertex(
eorg.x + split * (edest.x - eorg.x),
eorg.y + split * (edest.y - eorg.y),
currentenc.seg.boundary
#if USE_ATTRIBS
, mesh.nextras
#endif
);
newvertex.type = VertexType.SegmentVertex;
newvertex.hash = mesh.hash_vtx++;
newvertex.id = newvertex.hash;
mesh.vertices.Add(newvertex.hash, newvertex);
#if USE_ATTRIBS
// Interpolate attributes.
for (int i = 0; i < mesh.nextras; i++)
{
newvertex.attributes[i] = eorg.attributes[i]
+ split * (edest.attributes[i] - eorg.attributes[i]);
}
#endif
#if USE_Z
newvertex.z = eorg.z + split * (edest.z - eorg.z);
#endif
if (!Behavior.NoExact)
{
// Roundoff in the above calculation may yield a 'newvertex'
// that is not precisely collinear with 'eorg' and 'edest'.
// Improve collinearity by one step of iterative refinement.
multiplier = predicates.CounterClockwise(eorg, edest, newvertex);
divisor = ((eorg.x - edest.x) * (eorg.x - edest.x) +
(eorg.y - edest.y) * (eorg.y - edest.y));
if ((multiplier != 0.0) && (divisor != 0.0))
{
multiplier = multiplier / divisor;
// Watch out for NANs.
if (!double.IsNaN(multiplier))
{
newvertex.x += multiplier * (edest.y - eorg.y);
newvertex.y += multiplier * (eorg.x - edest.x);
}
}
}
// Check whether the new vertex lies on an endpoint.
if (((newvertex.x == eorg.x) && (newvertex.y == eorg.y)) ||
((newvertex.x == edest.x) && (newvertex.y == edest.y)))
{
logger.Error("Ran out of precision: I attempted to split a"
+ " segment to a smaller size than can be accommodated by"
+ " the finite precision of floating point arithmetic.",
"Quality.SplitEncSegs()");
throw new Exception("Ran out of precision");
}
// Insert the splitting vertex. This should always succeed.
success = mesh.InsertVertex(newvertex, ref enctri, ref currentenc, true, triflaws);
if ((success != InsertVertexResult.Successful) && (success != InsertVertexResult.Encroaching))
{
logger.Error("Failure to split a segment.", "Quality.SplitEncSegs()");
throw new Exception("Failure to split a segment.");
}
if (mesh.steinerleft > 0)
{
mesh.steinerleft--;
}
// Check the two new subsegments to see if they're encroached.
CheckSeg4Encroach(ref currentenc);
currentenc.Next();
CheckSeg4Encroach(ref currentenc);
}
// Set subsegment's origin to NULL. This makes it possible to detect dead
// badsubsegs when traversing the list of all badsubsegs.
seg.org = null;
}
}
/// <summary>
/// Test every triangle in the mesh for quality measures.
/// </summary>
private void TallyFaces()
{
Otri triangleloop = default(Otri);
triangleloop.orient = 0;
foreach (var tri in mesh.triangles)
{
triangleloop.tri = tri;
// If the triangle is bad, enqueue it.
TestTriangle(ref triangleloop);
}
}
/// <summary>
/// Inserts a vertex at the circumcenter of a triangle. Deletes
/// the newly inserted vertex if it encroaches upon a segment.
/// </summary>
/// <param name="badtri"></param>
private void SplitTriangle(BadTriangle badtri)
{
Otri badotri = default(Otri);
Vertex borg, bdest, bapex;
Point newloc; // Location of the new vertex
double xi = 0, eta = 0;
InsertVertexResult success;
bool errorflag;
badotri = badtri.poortri;
borg = badotri.Org();
bdest = badotri.Dest();
bapex = badotri.Apex();
// Make sure that this triangle is still the same triangle it was
// when it was tested and determined to be of bad quality.
// Subsequent transformations may have made it a different triangle.
if (!Otri.IsDead(badotri.tri) && (borg == badtri.org) &&
(bdest == badtri.dest) && (bapex == badtri.apex))
{
errorflag = false;
// Create a new vertex at the triangle's circumcenter.
// Using the original (simpler) Steiner point location method
// for mesh refinement.
// TODO: NewLocation doesn't work for refinement. Why? Maybe
// reset VertexType?
if (behavior.fixedArea || behavior.VarArea)
{
newloc = predicates.FindCircumcenter(borg, bdest, bapex, ref xi, ref eta, behavior.offconstant);
}
else
{
newloc = newLocation.FindLocation(borg, bdest, bapex, ref xi, ref eta, true, badotri);
}
// Check whether the new vertex lies on a triangle vertex.
if (((newloc.x == borg.x) && (newloc.y == borg.y)) ||
((newloc.x == bdest.x) && (newloc.y == bdest.y)) ||
((newloc.x == bapex.x) && (newloc.y == bapex.y)))
{
if (Log.Verbose)
{
logger.Warning("New vertex falls on existing vertex.", "Quality.SplitTriangle()");
errorflag = true;
}
}
else
{
// The new vertex must be in the interior, and therefore is a
// free vertex with a marker of zero.
Vertex newvertex = new Vertex(newloc.x, newloc.y, 0
#if USE_ATTRIBS
, mesh.nextras
#endif
);
newvertex.type = VertexType.FreeVertex;
// Ensure that the handle 'badotri' does not represent the longest
// edge of the triangle. This ensures that the circumcenter must
// fall to the left of this edge, so point location will work.
// (If the angle org-apex-dest exceeds 90 degrees, then the
// circumcenter lies outside the org-dest edge, and eta is
// negative. Roundoff error might prevent eta from being
// negative when it should be, so I test eta against xi.)
if (eta < xi)
{
badotri.Lprev();
}
// Assign triangle for attributes interpolation.
newvertex.tri.tri = newvertex_tri;
// Insert the circumcenter, searching from the edge of the triangle,
// and maintain the Delaunay property of the triangulation.
Osub tmp = default(Osub);
success = mesh.InsertVertex(newvertex, ref badotri, ref tmp, true, true);
if (success == InsertVertexResult.Successful)
{
newvertex.hash = mesh.hash_vtx++;
newvertex.id = newvertex.hash;
#if USE_ATTRIBS
if (mesh.nextras > 0)
{
Interpolation.InterpolateAttributes(newvertex, newvertex.tri.tri, mesh.nextras);
}
#endif
#if USE_Z
Interpolation.InterpolateZ(newvertex, newvertex.tri.tri);
#endif
mesh.vertices.Add(newvertex.hash, newvertex);
if (mesh.steinerleft > 0)
{
mesh.steinerleft--;
}
}
else if (success == InsertVertexResult.Encroaching)
{
// If the newly inserted vertex encroaches upon a subsegment,
// delete the new vertex.
mesh.UndoVertex();
}
else if (success == InsertVertexResult.Violating)
{
// Failed to insert the new vertex, but some subsegment was
// marked as being encroached.
}
else
{ // success == DUPLICATEVERTEX
// Couldn't insert the new vertex because a vertex is already there.
if (Log.Verbose)
{
logger.Warning("New vertex falls on existing vertex.", "Quality.SplitTriangle()");
errorflag = true;
}
}
}
if (errorflag)
{
logger.Error("The new vertex is at the circumcenter of triangle: This probably "
+ "means that I am trying to refine triangles to a smaller size than can be "
+ "accommodated by the finite precision of floating point arithmetic.",
"Quality.SplitTriangle()");
throw new Exception("The new vertex is at the circumcenter of triangle.");
}
}
}
/// <summary>
/// Remove all the encroached subsegments and bad triangles from the triangulation.
/// </summary>
private void EnforceQuality()
{
BadTriangle badtri;
// Test all segments to see if they're encroached.
TallyEncs();
// Fix encroached subsegments without noting bad triangles.
SplitEncSegs(false);
// At this point, if we haven't run out of Steiner points, the
// triangulation should be (conforming) Delaunay.
// Next, we worry about enforcing triangle quality.
if ((behavior.MinAngle > 0.0) || behavior.VarArea || behavior.fixedArea || behavior.UserTest != null)
{
// TODO: Reset queue? (Or is it always empty at this point)
// Test all triangles to see if they're bad.
TallyFaces();
mesh.checkquality = true;
while ((queue.Count > 0) && (mesh.steinerleft != 0))
{
// Fix one bad triangle by inserting a vertex at its circumcenter.
badtri = queue.Dequeue();
SplitTriangle(badtri);
if (badsubsegs.Count > 0)
{
// Put bad triangle back in queue for another try later.
queue.Enqueue(badtri);
// Fix any encroached subsegments that resulted.
// Record any new bad triangles that result.
SplitEncSegs(true);
}
}
}
// At this point, if the "-D" switch was selected and we haven't run out
// of Steiner points, the triangulation should be (conforming) Delaunay
// and have no low-quality triangles.
// Might we have run out of Steiner points too soon?
if (Log.Verbose && behavior.ConformingDelaunay && (badsubsegs.Count > 0) && (mesh.steinerleft == 0))
{
logger.Warning("I ran out of Steiner points, but the mesh has encroached subsegments, "
+ "and therefore might not be truly Delaunay. If the Delaunay property is important "
+ "to you, try increasing the number of Steiner points.",
"Quality.EnforceQuality()");
}
}
#endregion
}
}

View File

@ -0,0 +1,55 @@

namespace TriangleNet.Meshing
{
using System;
using TriangleNet.Geometry;
/// <summary>
/// Mesh constraint options for quality triangulation.
/// </summary>
public class QualityOptions
{
/// <summary>
/// Gets or sets a maximum angle constraint.
/// </summary>
public double MaximumAngle { get; set; }
/// <summary>
/// Gets or sets a minimum angle constraint.
/// </summary>
public double MinimumAngle { get; set; }
/// <summary>
/// Gets or sets a maximum triangle area constraint.
/// </summary>
public double MaximumArea { get; set; }
/// <summary>
/// Gets or sets a user-defined triangle constraint.
/// </summary>
/// <remarks>
/// The test function will be called for each triangle in the mesh. The
/// second argument is the area of the triangle tested. If the function
/// returns true, the triangle is considered bad and will be refined.
/// </remarks>
public Func<ITriangle, double, bool> UserTest { get; set; }
/// <summary>
/// Gets or sets an area constraint per triangle.
/// </summary>
/// <remarks>
/// If this flag is set to true, the <see cref="ITriangle.Area"/> value will
/// be used to check if a triangle needs refinement.
/// </remarks>
public bool VariableArea { get; set; }
/// <summary>
/// Gets or sets the maximum number of Steiner points to be inserted into the mesh.
/// </summary>
/// <remarks>
/// If the value is 0 (default), an unknown number of Steiner points may be inserted
/// to meet the other quality constraints.
/// </remarks>
public int SteinerPoints { get; set; }
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,19 @@
// -----------------------------------------------------------------------
// <copyright file="ISmoother.cs">
// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/
// </copyright>
// -----------------------------------------------------------------------
namespace TriangleNet.Smoothing
{
using TriangleNet.Meshing;
/// <summary>
/// Interface for mesh smoothers.
/// </summary>
public interface ISmoother
{
void Smooth(IMesh mesh);
void Smooth(IMesh mesh, int limit);
}
}

View File

@ -0,0 +1,171 @@
// -----------------------------------------------------------------------
// <copyright file="SimpleSmoother.cs" company="">
// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/
// </copyright>
// -----------------------------------------------------------------------
namespace TriangleNet.Smoothing
{
using System.Linq;
using TriangleNet.Geometry;
using TriangleNet.Meshing;
using TriangleNet.Topology.DCEL;
using TriangleNet.Voronoi;
/// <summary>
/// Simple mesh smoother implementation.
/// </summary>
/// <remarks>
/// Vertices wich should not move (e.g. segment vertices) MUST have a
/// boundary mark greater than 0.
/// </remarks>
public class SimpleSmoother : ISmoother
{
TrianglePool pool;
Configuration config;
IVoronoiFactory factory;
ConstraintOptions options;
/// <summary>
/// Initializes a new instance of the <see cref="SimpleSmoother" /> class.
/// </summary>
public SimpleSmoother()
: this(new VoronoiFactory())
{
}
/// <summary>
/// Initializes a new instance of the <see cref="SimpleSmoother" /> class.
/// </summary>
public SimpleSmoother(IVoronoiFactory factory)
{
this.factory = factory;
this.pool = new TrianglePool();
this.config = new Configuration(
() => RobustPredicates.Default,
() => pool.Restart());
this.options = new ConstraintOptions() { ConformingDelaunay = true };
}
/// <summary>
/// Initializes a new instance of the <see cref="SimpleSmoother" /> class.
/// </summary>
/// <param name="factory">Voronoi object factory.</param>
/// <param name="config">Configuration.</param>
public SimpleSmoother(IVoronoiFactory factory, Configuration config)
{
this.factory = factory;
this.config = config;
this.options = new ConstraintOptions() { ConformingDelaunay = true };
}
public void Smooth(IMesh mesh)
{
Smooth(mesh, 10);
}
public void Smooth(IMesh mesh, int limit)
{
var smoothedMesh = (Mesh)mesh;
var mesher = new GenericMesher(config);
var predicates = config.Predicates();
// The smoother should respect the mesh segment splitting behavior.
this.options.SegmentSplitting = smoothedMesh.behavior.NoBisect;
// Take a few smoothing rounds (Lloyd's algorithm).
for (int i = 0; i < limit; i++)
{
Step(smoothedMesh, factory, predicates);
// Actually, we only want to rebuild, if the mesh is no longer
// Delaunay. Flipping edges could be the right choice instead
// of re-triangulating...
smoothedMesh = (Mesh)mesher.Triangulate(Rebuild(smoothedMesh), options);
factory.Reset();
}
smoothedMesh.CopyTo((Mesh)mesh);
}
private void Step(Mesh mesh, IVoronoiFactory factory, IPredicates predicates)
{
var voronoi = new BoundedVoronoi(mesh, factory, predicates);
double x, y;
foreach (var face in voronoi.Faces)
{
if (face.generator.label == 0)
{
Centroid(face, out x, out y);
face.generator.x = x;
face.generator.y = y;
}
}
}
/// <summary>
/// Calculate the centroid of a polygon.
/// </summary>
private void Centroid(Face face, out double x, out double y)
{
double ai, atmp = 0, xtmp = 0, ytmp = 0;
var edge = face.Edge;
var first = edge.Next.ID;
Point p, q;
do
{
p = edge.Origin;
q = edge.Twin.Origin;
ai = p.x * q.y - q.x * p.y;
atmp += ai;
xtmp += (q.x + p.x) * ai;
ytmp += (q.y + p.y) * ai;
edge = edge.Next;
} while (edge.Next.ID != first);
x = xtmp / (3 * atmp);
y = ytmp / (3 * atmp);
//area = atmp / 2;
}
/// <summary>
/// Rebuild the input geometry.
/// </summary>
private Polygon Rebuild(Mesh mesh)
{
var data = new Polygon(mesh.vertices.Count);
foreach (var v in mesh.vertices.Values)
{
// Reset to input vertex.
v.type = VertexType.InputVertex;
data.Points.Add(v);
}
data.Segments.AddRange(mesh.subsegs.Values.Cast<ISegment>());
data.Holes.AddRange(mesh.holes);
data.Regions.AddRange(mesh.regions);
return data;
}
}
}

View File

@ -0,0 +1,201 @@

namespace TriangleNet.Smoothing
{
using System;
using TriangleNet.Topology.DCEL;
using TriangleNet.Voronoi;
/// <summary>
/// Factory which re-uses objects in the smoothing loop to enhance performance.
/// </summary>
/// <remarks>
/// See <see cref="SimpleSmoother"/>.
/// </remarks>
class VoronoiFactory : IVoronoiFactory
{
ObjectPool<Vertex> vertices;
ObjectPool<HalfEdge> edges;
ObjectPool<Face> faces;
public VoronoiFactory()
{
vertices = new ObjectPool<Vertex>();
edges = new ObjectPool<HalfEdge>();
faces = new ObjectPool<Face>();
}
public void Initialize(int vertexCount, int edgeCount, int faceCount)
{
vertices.Capacity = vertexCount;
edges.Capacity = edgeCount;
faces.Capacity = faceCount;
for (int i = vertices.Count; i < vertexCount; i++)
{
vertices.Put(new Vertex(0, 0));
}
for (int i = edges.Count; i < edgeCount; i++)
{
edges.Put(new HalfEdge(null));
}
for (int i = faces.Count; i < faceCount; i++)
{
faces.Put(new Face(null));
}
Reset();
}
public void Reset()
{
vertices.Release();
edges.Release();
faces.Release();
}
public Vertex CreateVertex(double x, double y)
{
Vertex vertex;
if (vertices.TryGet(out vertex))
{
vertex.x = x;
vertex.y = y;
vertex.leaving = null;
return vertex;
}
vertex = new Vertex(x, y);
vertices.Put(vertex);
return vertex;
}
public HalfEdge CreateHalfEdge(Vertex origin, Face face)
{
HalfEdge edge;
if (edges.TryGet(out edge))
{
edge.origin = origin;
edge.face = face;
edge.next = null;
edge.twin = null;
if (face != null && face.edge == null)
{
face.edge = edge;
}
return edge;
}
edge = new HalfEdge(origin, face);
edges.Put(edge);
return edge;
}
public Face CreateFace(Geometry.Vertex vertex)
{
Face face;
if (faces.TryGet(out face))
{
face.id = vertex.id;
face.generator = vertex;
face.edge = null;
return face;
}
face = new Face(vertex);
faces.Put(face);
return face;
}
class ObjectPool<T> where T : class
{
int index, count;
T[] pool;
public int Count
{
get { return count; }
}
public int Capacity
{
get { return this.pool.Length; }
set { Resize(value); }
}
public ObjectPool(int capacity = 3)
{
this.index = 0;
this.count = 0;
this.pool = new T[capacity];
}
public ObjectPool(T[] pool)
{
this.index = 0;
this.count = 0;
this.pool = pool;
}
public bool TryGet(out T obj)
{
if (this.index < this.count)
{
obj = this.pool[this.index++];
return true;
}
obj = null;
return false;
}
public void Put(T obj)
{
var capacity = this.pool.Length;
if (capacity <= this.count)
{
Resize(2 * capacity);
}
this.pool[this.count++] = obj;
this.index++;
}
public void Release()
{
this.index = 0;
}
private void Resize(int size)
{
if (size > this.count)
{
Array.Resize(ref this.pool, size);
}
}
}
}
}

View File

@ -0,0 +1,285 @@
// -----------------------------------------------------------------------
// <copyright file="AdjacencyMatrix.cs" company="">
// Original Matlab code by John Burkardt, Florida State University
// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/
// </copyright>
// -----------------------------------------------------------------------
namespace TriangleNet.Tools
{
using System;
/// <summary>
/// The adjacency matrix of the mesh.
/// </summary>
public class AdjacencyMatrix
{
// Number of adjacency entries.
int nnz;
// Pointers into the actual adjacency structure adj. Information about row k is
// stored in entries pcol(k) through pcol(k+1)-1 of adj. Size: N + 1
int[] pcol;
// The adjacency structure. For each row, it contains the column indices
// of the nonzero entries. Size: nnz
int[] irow;
/// <summary>
/// Gets the number of columns (nodes of the mesh).
/// </summary>
public readonly int N;
/// <summary>
/// Gets the column pointers.
/// </summary>
public int[] ColumnPointers
{
get { return pcol; }
}
/// <summary>
/// Gets the row indices.
/// </summary>
public int[] RowIndices
{
get { return irow; }
}
public AdjacencyMatrix(Mesh mesh)
{
this.N = mesh.vertices.Count;
// Set up the adj_row adjacency pointer array.
this.pcol = AdjacencyCount(mesh);
this.nnz = pcol[N];
// Set up the adj adjacency array.
this.irow = AdjacencySet(mesh, this.pcol);
SortIndices();
}
public AdjacencyMatrix(int[] pcol, int[] irow)
{
this.N = pcol.Length - 1;
this.nnz = pcol[N];
this.pcol = pcol;
this.irow = irow;
if (pcol[0] != 0)
{
throw new ArgumentException("Expected 0-based indexing.", "pcol");
}
if (irow.Length < nnz)
{
throw new ArgumentException();
}
}
/// <summary>
/// Computes the bandwidth of an adjacency matrix.
/// </summary>
/// <returns>Bandwidth of the adjacency matrix.</returns>
public int Bandwidth()
{
int band_hi;
int band_lo;
int col;
int i, j;
band_lo = 0;
band_hi = 0;
for (i = 0; i < N; i++)
{
for (j = pcol[i]; j < pcol[i + 1]; j++)
{
col = irow[j];
band_lo = Math.Max(band_lo, i - col);
band_hi = Math.Max(band_hi, col - i);
}
}
return band_lo + 1 + band_hi;
}
#region Adjacency matrix
/// <summary>
/// Counts adjacencies in a triangulation.
/// </summary>
/// <remarks>
/// This routine is called to count the adjacencies, so that the
/// appropriate amount of memory can be set aside for storage when
/// the adjacency structure is created.
///
/// The triangulation is assumed to involve 3-node triangles.
///
/// Two nodes are "adjacent" if they are both nodes in some triangle.
/// Also, a node is considered to be adjacent to itself.
/// </remarks>
int[] AdjacencyCount(Mesh mesh)
{
int n = N;
int n1, n2, n3;
int tid, nid;
int[] pcol = new int[n + 1];
// Set every node to be adjacent to itself.
for (int i = 0; i < n; i++)
{
pcol[i] = 1;
}
// Examine each triangle.
foreach (var tri in mesh.triangles)
{
tid = tri.id;
n1 = tri.vertices[0].id;
n2 = tri.vertices[1].id;
n3 = tri.vertices[2].id;
// Add edge (1,2) if this is the first occurrence, that is, if
// the edge (1,2) is on a boundary (nid <= 0) or if this triangle
// is the first of the pair in which the edge occurs (tid < nid).
nid = tri.neighbors[2].tri.id;
if (nid < 0 || tid < nid)
{
pcol[n1] += 1;
pcol[n2] += 1;
}
// Add edge (2,3).
nid = tri.neighbors[0].tri.id;
if (nid < 0 || tid < nid)
{
pcol[n2] += 1;
pcol[n3] += 1;
}
// Add edge (3,1).
nid = tri.neighbors[1].tri.id;
if (nid < 0 || tid < nid)
{
pcol[n3] += 1;
pcol[n1] += 1;
}
}
// We used PCOL to count the number of entries in each column.
// Convert it to pointers into the ADJ array.
for (int i = n; i > 0; i--)
{
pcol[i] = pcol[i - 1];
}
pcol[0] = 0;
for (int i = 1; i <= n; i++)
{
pcol[i] = pcol[i - 1] + pcol[i];
}
return pcol;
}
/// <summary>
/// Sets adjacencies in a triangulation.
/// </summary>
/// <remarks>
/// This routine can be used to create the compressed column storage
/// for a linear triangle finite element discretization of Poisson's
/// equation in two dimensions.
/// </remarks>
int[] AdjacencySet(Mesh mesh, int[] pcol)
{
int n = this.N;
int[] col = new int[n];
// Copy of the adjacency rows input.
Array.Copy(pcol, col, n);
int i, nnz = pcol[n];
// Output list, stores the actual adjacency information.
int[] list = new int[nnz];
// Set every node to be adjacent to itself.
for (i = 0; i < n; i++)
{
list[col[i]] = i;
col[i] += 1;
}
int n1, n2, n3; // Vertex numbers.
int tid, nid; // Triangle and neighbor id.
// Examine each triangle.
foreach (var tri in mesh.triangles)
{
tid = tri.id;
n1 = tri.vertices[0].id;
n2 = tri.vertices[1].id;
n3 = tri.vertices[2].id;
// Add edge (1,2) if this is the first occurrence, that is, if
// the edge (1,2) is on a boundary (nid <= 0) or if this triangle
// is the first of the pair in which the edge occurs (tid < nid).
nid = tri.neighbors[2].tri.id;
if (nid < 0 || tid < nid)
{
list[col[n1]++] = n2;
list[col[n2]++] = n1;
}
// Add edge (2,3).
nid = tri.neighbors[0].tri.id;
if (nid < 0 || tid < nid)
{
list[col[n2]++] = n3;
list[col[n3]++] = n2;
}
// Add edge (3,1).
nid = tri.neighbors[1].tri.id;
if (nid < 0 || tid < nid)
{
list[col[n1]++] = n3;
list[col[n3]++] = n1;
}
}
return list;
}
public void SortIndices()
{
int k1, k2, n = N;
int[] list = this.irow;
// Ascending sort the entries for each column.
for (int i = 0; i < n; i++)
{
k1 = pcol[i];
k2 = pcol[i + 1];
Array.Sort(list, k1, k2 - k1);
}
}
#endregion
}
}

View File

@ -0,0 +1,685 @@
// -----------------------------------------------------------------------
// <copyright file="CuthillMcKee.cs" company="">
// Original Matlab code by John Burkardt, Florida State University
// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/
// </copyright>
// -----------------------------------------------------------------------
namespace TriangleNet.Tools
{
using System;
/// <summary>
/// Applies the Cuthill and McKee renumbering algorithm to reduce the bandwidth of
/// the adjacency matrix associated with the mesh.
/// </summary>
public class CuthillMcKee
{
// The adjacency matrix of the mesh.
AdjacencyMatrix matrix;
/// <summary>
/// Gets the permutation vector for the Reverse Cuthill-McKee numbering.
/// </summary>
/// <param name="mesh">The mesh.</param>
/// <returns>Permutation vector.</returns>
public int[] Renumber(Mesh mesh)
{
// Algorithm needs linear numbering of the nodes.
mesh.Renumber(NodeNumbering.Linear);
return Renumber(new AdjacencyMatrix(mesh));
}
/// <summary>
/// Gets the permutation vector for the Reverse Cuthill-McKee numbering.
/// </summary>
/// <param name="mesh">The mesh.</param>
/// <returns>Permutation vector.</returns>
public int[] Renumber(AdjacencyMatrix matrix)
{
this.matrix = matrix;
int bandwidth1 = matrix.Bandwidth();
var pcol = matrix.ColumnPointers;
// Adjust column pointers (1-based indexing).
Shift(pcol, true);
// TODO: Make RCM work with 0-based matrix.
// Compute the RCM permutation.
int[] perm = GenerateRcm();
int[] perm_inv = PermInverse(perm);
int bandwidth2 = PermBandwidth(perm, perm_inv);
if (Log.Verbose)
{
Log.Instance.Info(String.Format("Reverse Cuthill-McKee (Bandwidth: {0} > {1})",
bandwidth1, bandwidth2));
}
// Adjust column pointers (0-based indexing).
Shift(pcol, false);
return perm_inv;
}
#region RCM
/// <summary>
/// Finds the reverse Cuthill-Mckee ordering for a general graph.
/// </summary>
/// <returns>The RCM ordering.</returns>
/// <remarks>
/// For each connected component in the graph, the routine obtains
/// an ordering by calling RCM.
/// </remarks>
int[] GenerateRcm()
{
// Number of nodes in the mesh.
int n = matrix.N;
int[] perm = new int[n];
int i, num, root;
int iccsze = 0;
int level_num = 0;
/// Index vector for a level structure. The level structure is stored in the
/// currently unused spaces in the permutation vector PERM.
int[] level_row = new int[n + 1];
/// Marks variables that have been numbered.
int[] mask = new int[n];
for (i = 0; i < n; i++)
{
mask[i] = 1;
}
num = 1;
for (i = 0; i < n; i++)
{
// For each masked connected component...
if (mask[i] != 0)
{
root = i;
// Find a pseudo-peripheral node ROOT. The level structure found by
// ROOT_FIND is stored starting at PERM(NUM).
FindRoot(ref root, mask, ref level_num, level_row, perm, num - 1);
// RCM orders the component using ROOT as the starting node.
Rcm(root, mask, perm, num - 1, ref iccsze);
num += iccsze;
// We can stop once every node is in one of the connected components.
if (n < num)
{
return perm;
}
}
}
return perm;
}
/// <summary>
/// RCM renumbers a connected component by the reverse Cuthill McKee algorithm.
/// </summary>
/// <param name="root">the node that defines the connected component. It is used as the starting
/// point for the RCM ordering.</param>
/// <param name="mask">Input/output, int MASK(NODE_NUM), a mask for the nodes. Only those nodes with
/// nonzero input mask values are considered by the routine. The nodes numbered by RCM will have
/// their mask values set to zero.</param>
/// <param name="perm">Output, int PERM(NODE_NUM), the RCM ordering.</param>
/// <param name="iccsze">Output, int ICCSZE, the size of the connected component that has been numbered.</param>
/// <param name="node_num">the number of nodes.</param>
/// <remarks>
/// The connected component is specified by a node ROOT and a mask.
/// The numbering starts at the root node.
///
/// An outline of the algorithm is as follows:
///
/// X(1) = ROOT.
///
/// for ( I = 1 to N-1)
/// Find all unlabeled neighbors of X(I),
/// assign them the next available labels, in order of increasing degree.
///
/// When done, reverse the ordering.
/// </remarks>
void Rcm(int root, int[] mask, int[] perm, int offset, ref int iccsze)
{
int[] pcol = matrix.ColumnPointers;
int[] irow = matrix.RowIndices;
int fnbr;
int i, j, k, l;
int jstop, jstrt;
int lbegin, lnbr, lperm, lvlend;
int nbr, node;
// Number of nodes in the mesh.
int n = matrix.N;
/// Workspace, int DEG[NODE_NUM], a temporary vector used to hold
/// the degree of the nodes in the section graph specified by mask and root.
int[] deg = new int[n];
// Find the degrees of the nodes in the component specified by MASK and ROOT.
Degree(root, mask, deg, ref iccsze, perm, offset);
mask[root] = 0;
if (iccsze <= 1)
{
return;
}
lvlend = 0;
lnbr = 1;
// LBEGIN and LVLEND point to the beginning and
// the end of the current level respectively.
while (lvlend < lnbr)
{
lbegin = lvlend + 1;
lvlend = lnbr;
for (i = lbegin; i <= lvlend; i++)
{
// For each node in the current level...
node = perm[offset + i - 1];
jstrt = pcol[node];
jstop = pcol[node + 1] - 1;
// Find the unnumbered neighbors of NODE.
// FNBR and LNBR point to the first and last neighbors
// of the current node in PERM.
fnbr = lnbr + 1;
for (j = jstrt; j <= jstop; j++)
{
nbr = irow[j - 1];
if (mask[nbr] != 0)
{
lnbr += 1;
mask[nbr] = 0;
perm[offset + lnbr - 1] = nbr;
}
}
// Node has neighbors
if (lnbr > fnbr)
{
// Sort the neighbors of NODE in increasing order by degree.
// Linear insertion is used.
k = fnbr;
while (k < lnbr)
{
l = k;
k = k + 1;
nbr = perm[offset + k - 1];
while (fnbr < l)
{
lperm = perm[offset + l - 1];
if (deg[lperm - 1] <= deg[nbr - 1])
{
break;
}
perm[offset + l] = lperm;
l = l - 1;
}
perm[offset + l] = nbr;
}
}
}
}
// We now have the Cuthill-McKee ordering. Reverse it.
ReverseVector(perm, offset, iccsze);
return;
}
/// <summary>
/// Finds a pseudo-peripheral node.
/// </summary>
/// <param name="root">On input, ROOT is a node in the the component of the graph for
/// which a pseudo-peripheral node is sought. On output, ROOT is the pseudo-peripheral
/// node obtained.</param>
/// <param name="mask">MASK[NODE_NUM], specifies a section subgraph. Nodes for which MASK
/// is zero are ignored by FNROOT.</param>
/// <param name="level_num">Output, int LEVEL_NUM, is the number of levels in the level
/// structure rooted at the node ROOT.</param>
/// <param name="level_row">Output, int LEVEL_ROW(NODE_NUM+1), the level structure array pair
/// containing the level structure found.</param>
/// <param name="level">Output, int LEVEL(NODE_NUM), the level structure array pair
/// containing the level structure found.</param>
/// <param name="node_num">the number of nodes.</param>
/// <remarks>
/// The diameter of a graph is the maximum distance (number of edges)
/// between any two nodes of the graph.
///
/// The eccentricity of a node is the maximum distance between that
/// node and any other node of the graph.
///
/// A peripheral node is a node whose eccentricity equals the
/// diameter of the graph.
///
/// A pseudo-peripheral node is an approximation to a peripheral node;
/// it may be a peripheral node, but all we know is that we tried our
/// best.
///
/// The routine is given a graph, and seeks pseudo-peripheral nodes,
/// using a modified version of the scheme of Gibbs, Poole and
/// Stockmeyer. It determines such a node for the section subgraph
/// specified by MASK and ROOT.
///
/// The routine also determines the level structure associated with
/// the given pseudo-peripheral node; that is, how far each node
/// is from the pseudo-peripheral node. The level structure is
/// returned as a list of nodes LS, and pointers to the beginning
/// of the list of nodes that are at a distance of 0, 1, 2, ...,
/// NODE_NUM-1 from the pseudo-peripheral node.
///
/// Reference:
/// Alan George, Joseph Liu,
/// Computer Solution of Large Sparse Positive Definite Systems,
/// Prentice Hall, 1981.
///
/// Norman Gibbs, William Poole, Paul Stockmeyer,
/// An Algorithm for Reducing the Bandwidth and Profile of a Sparse Matrix,
/// SIAM Journal on Numerical Analysis,
/// Volume 13, pages 236-250, 1976.
///
/// Norman Gibbs,
/// Algorithm 509: A Hybrid Profile Reduction Algorithm,
/// ACM Transactions on Mathematical Software,
/// Volume 2, pages 378-387, 1976.
/// </remarks>
void FindRoot(ref int root, int[] mask, ref int level_num, int[] level_row,
int[] level, int offset)
{
int[] pcol = matrix.ColumnPointers;
int[] irow = matrix.RowIndices;
int iccsze;
int j, jstrt;
int k, kstop, kstrt;
int mindeg;
int nghbor, ndeg;
int node;
int level_num2 = 0;
// Determine the level structure rooted at ROOT.
GetLevelSet(ref root, mask, ref level_num, level_row, level, offset);
// Count the number of nodes in this level structure.
iccsze = level_row[level_num] - 1;
// Extreme cases:
// A complete graph has a level set of only a single level.
// Every node is equally good (or bad).
// or
// A "line graph" 0--0--0--0--0 has every node in its only level.
// By chance, we've stumbled on the ideal root.
if (level_num == 1 || level_num == iccsze)
{
return;
}
// Pick any node from the last level that has minimum degree
// as the starting point to generate a new level set.
for (; ; )
{
mindeg = iccsze;
jstrt = level_row[level_num - 1];
root = level[offset + jstrt - 1];
if (jstrt < iccsze)
{
for (j = jstrt; j <= iccsze; j++)
{
node = level[offset + j - 1];
ndeg = 0;
kstrt = pcol[node - 1];
kstop = pcol[node] - 1;
for (k = kstrt; k <= kstop; k++)
{
nghbor = irow[k - 1];
if (mask[nghbor] > 0)
{
ndeg += 1;
}
}
if (ndeg < mindeg)
{
root = node;
mindeg = ndeg;
}
}
}
// Generate the rooted level structure associated with this node.
GetLevelSet(ref root, mask, ref level_num2, level_row, level, offset);
// If the number of levels did not increase, accept the new ROOT.
if (level_num2 <= level_num)
{
break;
}
level_num = level_num2;
// In the unlikely case that ROOT is one endpoint of a line graph,
// we can exit now.
if (iccsze <= level_num)
{
break;
}
}
return;
}
/// <summary>
/// Generates the connected level structure rooted at a given node.
/// </summary>
/// <param name="root">the node at which the level structure is to be rooted.</param>
/// <param name="mask">MASK[NODE_NUM]. On input, only nodes with nonzero MASK are to be processed.
/// On output, those nodes which were included in the level set have MASK set to 1.</param>
/// <param name="level_num">Output, int LEVEL_NUM, the number of levels in the level structure. ROOT is
/// in level 1. The neighbors of ROOT are in level 2, and so on.</param>
/// <param name="level_row">Output, int LEVEL_ROW[NODE_NUM+1], the rooted level structure.</param>
/// <param name="level">Output, int LEVEL[NODE_NUM], the rooted level structure.</param>
/// <param name="node_num">the number of nodes.</param>
/// <remarks>
/// Only nodes for which MASK is nonzero will be considered.
///
/// The root node chosen by the user is assigned level 1, and masked.
/// All (unmasked) nodes reachable from a node in level 1 are
/// assigned level 2 and masked. The process continues until there
/// are no unmasked nodes adjacent to any node in the current level.
/// The number of levels may vary between 2 and NODE_NUM.
///
/// Reference:
/// Alan George, Joseph Liu,
/// Computer Solution of Large Sparse Positive Definite Systems,
/// Prentice Hall, 1981.
/// </remarks>
void GetLevelSet(ref int root, int[] mask, ref int level_num, int[] level_row,
int[] level, int offset)
{
int[] pcol = matrix.ColumnPointers;
int[] irow = matrix.RowIndices;
int i, iccsze;
int j, jstop, jstrt;
int lbegin, lvlend, lvsize;
int nbr;
int node;
mask[root] = 0;
level[offset] = root;
level_num = 0;
lvlend = 0;
iccsze = 1;
// LBEGIN is the pointer to the beginning of the current level, and
// LVLEND points to the end of this level.
for (; ; )
{
lbegin = lvlend + 1;
lvlend = iccsze;
level_num += 1;
level_row[level_num - 1] = lbegin;
// Generate the next level by finding all the masked neighbors of nodes
// in the current level.
for (i = lbegin; i <= lvlend; i++)
{
node = level[offset + i - 1];
jstrt = pcol[node];
jstop = pcol[node + 1] - 1;
for (j = jstrt; j <= jstop; j++)
{
nbr = irow[j - 1];
if (mask[nbr] != 0)
{
iccsze += 1;
level[offset + iccsze - 1] = nbr;
mask[nbr] = 0;
}
}
}
// Compute the current level width (the number of nodes encountered.)
// If it is positive, generate the next level.
lvsize = iccsze - lvlend;
if (lvsize <= 0)
{
break;
}
}
level_row[level_num] = lvlend + 1;
// Reset MASK to 1 for the nodes in the level structure.
for (i = 0; i < iccsze; i++)
{
mask[level[offset + i]] = 1;
}
return;
}
/// <summary>
/// Computes the degrees of the nodes in the connected component.
/// </summary>
/// <param name="root">the node that defines the connected component.</param>
/// <param name="mask">MASK[NODE_NUM], is nonzero for those nodes which are to be considered.</param>
/// <param name="deg">Output, int DEG[NODE_NUM], contains, for each node in the connected component, its degree.</param>
/// <param name="iccsze">Output, int ICCSIZE, the number of nodes in the connected component.</param>
/// <param name="ls">Output, int LS[NODE_NUM], stores in entries 1 through ICCSIZE the nodes in the
/// connected component, starting with ROOT, and proceeding by levels.</param>
/// <param name="node_num">the number of nodes.</param>
/// <remarks>
/// The connected component is specified by MASK and ROOT.
/// Nodes for which MASK is zero are ignored.
///
/// Reference:
/// Alan George, Joseph Liu,
/// Computer Solution of Large Sparse Positive Definite Systems,
/// Prentice Hall, 1981.
/// </remarks>
void Degree(int root, int[] mask, int[] deg, ref int iccsze, int[] ls, int offset)
{
int[] pcol = matrix.ColumnPointers;
int[] irow = matrix.RowIndices;
int i, ideg;
int j, jstop, jstrt;
int lbegin, lvlend;
int lvsize = 1;
int nbr, node;
// The sign of ADJ_ROW(I) is used to indicate if node I has been considered.
ls[offset] = root;
pcol[root] = -pcol[root];
lvlend = 0;
iccsze = 1;
// If the current level width is nonzero, generate another level.
while (lvsize > 0)
{
// LBEGIN is the pointer to the beginning of the current level, and
// LVLEND points to the end of this level.
lbegin = lvlend + 1;
lvlend = iccsze;
// Find the degrees of nodes in the current level,
// and at the same time, generate the next level.
for (i = lbegin; i <= lvlend; i++)
{
node = ls[offset + i - 1];
jstrt = -pcol[node];
jstop = Math.Abs(pcol[node + 1]) - 1;
ideg = 0;
for (j = jstrt; j <= jstop; j++)
{
nbr = irow[j - 1];
if (mask[nbr] != 0) // EDIT: [nbr - 1]
{
ideg = ideg + 1;
if (0 <= pcol[nbr]) // EDIT: [nbr - 1]
{
pcol[nbr] = -pcol[nbr]; // EDIT: [nbr - 1]
iccsze = iccsze + 1;
ls[offset + iccsze - 1] = nbr;
}
}
}
deg[node] = ideg;
}
// Compute the current level width.
lvsize = iccsze - lvlend;
}
// Reset ADJ_ROW to its correct sign and return.
for (i = 0; i < iccsze; i++)
{
node = ls[offset + i];
pcol[node] = -pcol[node];
}
return;
}
#endregion
#region Tools
/// <summary>
/// Computes the bandwidth of a permuted adjacency matrix.
/// </summary>
/// <param name="perm">The permutation.</param>
/// <param name="perm_inv">The inverse permutation.</param>
/// <returns>Bandwidth of the permuted adjacency matrix.</returns>
/// <remarks>
/// The matrix is defined by the adjacency information and a permutation.
/// The routine also computes the bandwidth and the size of the envelope.
/// </remarks>
int PermBandwidth(int[] perm, int[] perm_inv)
{
int[] pcol = matrix.ColumnPointers;
int[] irow = matrix.RowIndices;
int col, i, j;
int band_lo = 0;
int band_hi = 0;
int n = matrix.N;
for (i = 0; i < n; i++)
{
for (j = pcol[perm[i]]; j < pcol[perm[i] + 1]; j++)
{
col = perm_inv[irow[j - 1]];
band_lo = Math.Max(band_lo, i - col);
band_hi = Math.Max(band_hi, col - i);
}
}
return band_lo + 1 + band_hi;
}
/// <summary>
/// Produces the inverse of a given permutation.
/// </summary>
/// <param name="n">Number of items permuted.</param>
/// <param name="perm">PERM[N], a permutation.</param>
/// <returns>The inverse permutation.</returns>
int[] PermInverse(int[] perm)
{
int n = matrix.N;
int[] perm_inv = new int[n];
for (int i = 0; i < n; i++)
{
perm_inv[perm[i]] = i;
}
return perm_inv;
}
/// <summary>
/// Reverses the elements of an integer vector.
/// </summary>
/// <param name="size">number of entries in the array.</param>
/// <param name="a">the array to be reversed.</param>
/// <example>
/// Input:
/// N = 5,
/// A = ( 11, 12, 13, 14, 15 ).
///
/// Output:
/// A = ( 15, 14, 13, 12, 11 ).
/// </example>
void ReverseVector(int[] a, int offset, int size)
{
int i;
int j;
for (i = 0; i < size / 2; i++)
{
j = a[offset + i];
a[offset + i] = a[offset + size - 1 - i];
a[offset + size - 1 - i] = j;
}
return;
}
void Shift(int[] a, bool up)
{
int length = a.Length;
if (up)
{
for (int i = 0; i < length; a[i]++, i++) ;
}
else
{
for (int i = 0; i < length; a[i]--, i++) ;
}
}
#endregion
}
}

View File

@ -0,0 +1,105 @@

namespace TriangleNet.Tools
{
using TriangleNet.Geometry;
public static class Interpolation
{
#if USE_ATTRIBS
/// <summary>
/// Linear interpolation of vertex attributes.
/// </summary>
/// <param name="vertex">The interpolation vertex.</param>
/// <param name="triangle">The triangle containing the vertex.</param>
/// <param name="n">The number of vertex attributes.</param>
/// <remarks>
/// The vertex is expected to lie inside the triangle.
/// </remarks>
public static void InterpolateAttributes(Vertex vertex, ITriangle triangle, int n)
{
Vertex org = triangle.GetVertex(0);
Vertex dest = triangle.GetVertex(1);
Vertex apex = triangle.GetVertex(2);
double xdo, ydo, xao, yao;
double denominator;
double dx, dy;
double xi, eta;
// Compute the circumcenter of the triangle.
xdo = dest.x - org.x;
ydo = dest.y - org.y;
xao = apex.x - org.x;
yao = apex.y - org.y;
denominator = 0.5 / (xdo * yao - xao * ydo);
//dx = (yao * dodist - ydo * aodist) * denominator;
//dy = (xdo * aodist - xao * dodist) * denominator;
dx = vertex.x - org.x;
dy = vertex.y - org.y;
// To interpolate vertex attributes for the new vertex, define a
// coordinate system with a xi-axis directed from the triangle's
// origin to its destination, and an eta-axis, directed from its
// origin to its apex.
xi = (yao * dx - xao * dy) * (2.0 * denominator);
eta = (xdo * dy - ydo * dx) * (2.0 * denominator);
for (int i = 0; i < n; i++)
{
// Interpolate the vertex attributes.
vertex.attributes[i] = org.attributes[i]
+ xi * (dest.attributes[i] - org.attributes[i])
+ eta * (apex.attributes[i] - org.attributes[i]);
}
}
#endif
#if USE_Z
/// <summary>
/// Linear interpolation of a scalar value.
/// </summary>
/// <param name="p">The interpolation point.</param>
/// <param name="triangle">The triangle containing the point.</param>
/// <remarks>
/// The point is expected to lie inside the triangle.
/// </remarks>
public static void InterpolateZ(Point p, ITriangle triangle)
{
Vertex org = triangle.GetVertex(0);
Vertex dest = triangle.GetVertex(1);
Vertex apex = triangle.GetVertex(2);
double xdo, ydo, xao, yao;
double denominator;
double dx, dy;
double xi, eta;
// Compute the circumcenter of the triangle.
xdo = dest.x - org.x;
ydo = dest.y - org.y;
xao = apex.x - org.x;
yao = apex.y - org.y;
denominator = 0.5 / (xdo * yao - xao * ydo);
//dx = (yao * dodist - ydo * aodist) * denominator;
//dy = (xdo * aodist - xao * dodist) * denominator;
dx = p.x - org.x;
dy = p.y - org.y;
// To interpolate z value for the given point inserted, define a
// coordinate system with a xi-axis, directed from the triangle's
// origin to its destination, and an eta-axis, directed from its
// origin to its apex.
xi = (yao * dx - xao * dy) * (2.0 * denominator);
eta = (xdo * dy - ydo * dx) * (2.0 * denominator);
p.z = org.z + xi * (dest.z - org.z) + eta * (apex.z - org.z);
}
#endif
}
}

View File

@ -0,0 +1,225 @@
// -----------------------------------------------------------------------
// <copyright file="IntersectionHelper.cs" company="">
// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/
// </copyright>
// -----------------------------------------------------------------------
namespace TriangleNet.Tools
{
using TriangleNet.Geometry;
public static class IntersectionHelper
{
/// <summary>
/// Compute intersection of two segments.
/// </summary>
/// <param name="p0">Segment 1 start point.</param>
/// <param name="p1">Segment 1 end point.</param>
/// <param name="q0">Segment 2 start point.</param>
/// <param name="q1">Segment 2 end point.</param>
/// <param name="c0">The intersection point.</param>
/// <remarks>
/// This is a special case of segment intersection. Since the calling algorithm assures
/// that a valid intersection exists, there's no need to check for any special cases.
/// </remarks>
public static void IntersectSegments(Point p0, Point p1, Point q0, Point q1, ref Point c0)
{
double ux = p1.x - p0.x;
double uy = p1.y - p0.y;
double vx = q1.x - q0.x;
double vy = q1.y - q0.y;
double wx = p0.x - q0.x;
double wy = p0.y - q0.y;
double d = (ux * vy - uy * vx);
double s = (vx * wy - vy * wx) / d;
// Intersection point
c0.x = p0.X + s * ux;
c0.y = p0.Y + s * uy;
}
/// <summary>
/// Intersect segment with a bounding box.
/// </summary>
/// <param name="rect">The clip rectangle.</param>
/// <param name="p0">Segment endpoint.</param>
/// <param name="p1">Segment endpoint.</param>
/// <param name="c0">The new location of p0.</param>
/// <param name="c1">The new location of p1.</param>
/// <returns>Returns true, if segment is clipped.</returns>
/// <remarks>
/// Based on Liang-Barsky function by Daniel White:
/// http://www.skytopia.com/project/articles/compsci/clipping.html
/// </remarks>
public static bool LiangBarsky(Rectangle rect, Point p0, Point p1, ref Point c0, ref Point c1)
{
// Define the x/y clipping values for the border.
double xmin = rect.Left;
double xmax = rect.Right;
double ymin = rect.Bottom;
double ymax = rect.Top;
// Define the start and end points of the line.
double x0 = p0.X;
double y0 = p0.Y;
double x1 = p1.X;
double y1 = p1.Y;
double t0 = 0.0;
double t1 = 1.0;
double dx = x1 - x0;
double dy = y1 - y0;
double p = 0.0, q = 0.0, r;
for (int edge = 0; edge < 4; edge++)
{
// Traverse through left, right, bottom, top edges.
if (edge == 0) { p = -dx; q = -(xmin - x0); }
if (edge == 1) { p = dx; q = (xmax - x0); }
if (edge == 2) { p = -dy; q = -(ymin - y0); }
if (edge == 3) { p = dy; q = (ymax - y0); }
r = q / p;
if (p == 0 && q < 0) return false; // Don't draw line at all. (parallel line outside)
if (p < 0)
{
if (r > t1) return false; // Don't draw line at all.
else if (r > t0) t0 = r; // Line is clipped!
}
else if (p > 0)
{
if (r < t0) return false; // Don't draw line at all.
else if (r < t1) t1 = r; // Line is clipped!
}
}
c0.X = x0 + t0 * dx;
c0.Y = y0 + t0 * dy;
c1.X = x0 + t1 * dx;
c1.Y = y0 + t1 * dy;
return true; // (clipped) line is drawn
}
/// <summary>
/// Intersect a ray with a bounding box.
/// </summary>
/// <param name="rect">The clip rectangle.</param>
/// <param name="p0">The ray startpoint (inside the box).</param>
/// <param name="p1">Any point in ray direction (NOT the direction vector).</param>
/// <param name="c1">The intersection point.</param>
/// <returns>Returns false, if startpoint is outside the box.</returns>
public static bool BoxRayIntersection(Rectangle rect, Point p0, Point p1, ref Point c1)
{
return BoxRayIntersection(rect, p0, p1.x - p0.x, p1.y - p0.y, ref c1);
}
/// <summary>
/// Intersect a ray with a bounding box.
/// </summary>
/// <param name="rect">The clip rectangle.</param>
/// <param name="p">The ray startpoint (inside the box).</param>
/// <param name="dx">X direction.</param>
/// <param name="dy">Y direction.</param>
/// <returns>Returns false, if startpoint is outside the box.</returns>
public static Point BoxRayIntersection(Rectangle rect, Point p, double dx, double dy)
{
var intersection = new Point();
if (BoxRayIntersection(rect, p, dx, dy, ref intersection))
{
return intersection;
}
return null;
}
/// <summary>
/// Intersect a ray with a bounding box.
/// </summary>
/// <param name="rect">The clip rectangle.</param>
/// <param name="p">The ray startpoint (inside the box).</param>
/// <param name="dx">X direction.</param>
/// <param name="dy">Y direction.</param>
/// <param name="c">The intersection point.</param>
/// <returns>Returns false, if startpoint is outside the box.</returns>
public static bool BoxRayIntersection(Rectangle rect, Point p, double dx, double dy, ref Point c)
{
double x = p.X;
double y = p.Y;
double t1, x1, y1, t2, x2, y2;
// Bounding box
double xmin = rect.Left;
double xmax = rect.Right;
double ymin = rect.Bottom;
double ymax = rect.Top;
// Check if point is inside the bounds
if (x < xmin || x > xmax || y < ymin || y > ymax)
{
return false;
}
// Calculate the cut through the vertical boundaries
if (dx < 0)
{
// Line going to the left: intersect with x = minX
t1 = (xmin - x) / dx;
x1 = xmin;
y1 = y + t1 * dy;
}
else if (dx > 0)
{
// Line going to the right: intersect with x = maxX
t1 = (xmax - x) / dx;
x1 = xmax;
y1 = y + t1 * dy;
}
else
{
// Line going straight up or down: no intersection possible
t1 = double.MaxValue;
x1 = y1 = 0;
}
// Calculate the cut through upper and lower boundaries
if (dy < 0)
{
// Line going downwards: intersect with y = minY
t2 = (ymin - y) / dy;
x2 = x + t2 * dx;
y2 = ymin;
}
else if (dy > 0)
{
// Line going upwards: intersect with y = maxY
t2 = (ymax - y) / dy;
x2 = x + t2 * dx;
y2 = ymax;
}
else
{
// Horizontal line: no intersection possible
t2 = double.MaxValue;
x2 = y2 = 0;
}
if (t1 < t2)
{
c.x = x1;
c.y = y1;
}
else
{
c.x = x2;
c.y = y2;
}
return true;
}
}
}

View File

@ -0,0 +1,245 @@
// -----------------------------------------------------------------------
// <copyright file="PolygonValidator.cs">
// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/
// </copyright>
// -----------------------------------------------------------------------
namespace TriangleNet.Tools
{
using System;
using System.Collections.Generic;
using TriangleNet.Geometry;
public static class PolygonValidator
{
/// <summary>
/// Test the polygon for consistency.
/// </summary>
public static bool IsConsistent(IPolygon poly)
{
var logger = Log.Instance;
var points = poly.Points;
int horrors = 0;
int i = 0;
int count = points.Count;
if (count < 3)
{
logger.Warning("Polygon must have at least 3 vertices.", "PolygonValidator.IsConsistent()");
return false;
}
foreach (var p in points)
{
if (p == null)
{
horrors++;
logger.Warning(String.Format("Point {0} is null.", i), "PolygonValidator.IsConsistent()");
}
else if (double.IsNaN(p.x) || double.IsNaN(p.y))
{
horrors++;
logger.Warning(String.Format("Point {0} has invalid coordinates.", i), "PolygonValidator.IsConsistent()");
}
else if (double.IsInfinity(p.x) || double.IsInfinity(p.y))
{
horrors++;
logger.Warning(String.Format("Point {0} has invalid coordinates.", i), "PolygonValidator.IsConsistent()");
}
i++;
}
i = 0;
foreach (var seg in poly.Segments)
{
if (seg == null)
{
horrors++;
logger.Warning(String.Format("Segment {0} is null.", i), "PolygonValidator.IsConsistent()");
// Always abort if a NULL-segment is found.
return false;
}
var p = seg.GetVertex(0);
var q = seg.GetVertex(1);
if ((p.x == q.x) && (p.y == q.y))
{
horrors++;
logger.Warning(String.Format("Endpoints of segment {0} are coincident (IDs {1} / {2}).", i, p.id, q.id),
"PolygonValidator.IsConsistent()");
}
i++;
}
if (points[0].id == points[1].id)
{
horrors += CheckVertexIDs(poly, count);
}
else
{
horrors += CheckDuplicateIDs(poly);
}
return horrors == 0;
}
/// <summary>
/// Test the polygon for duplicate vertices.
/// </summary>
public static bool HasDuplicateVertices(IPolygon poly)
{
var logger = Log.Instance;
int horrors = 0;
var points = poly.Points.ToArray();
VertexSorter.Sort(points);
for (int i = 1; i < points.Length; i++)
{
if (points[i - 1] == points[i])
{
horrors++;
logger.Warning(String.Format("Found duplicate point {0}.", points[i]),
"PolygonValidator.HasDuplicateVertices()");
}
}
return horrors > 0;
}
/// <summary>
/// Test the polygon for 360 degree angles.
/// </summary>
/// <param name="poly">The polygon.</param>
/// <param name="threshold">The angle threshold.</param>
public static bool HasBadAngles(IPolygon poly, double threshold = 2e-12)
{
var logger = Log.Instance;
int horrors = 0;
int i = 0;
Point p0 = null, p1 = null;
Point q0, q1;
int count = poly.Points.Count;
foreach (var seg in poly.Segments)
{
q0 = p0; // Previous segment start point.
q1 = p1; // Previous segment end point.
p0 = seg.GetVertex(0); // Current segment start point.
p1 = seg.GetVertex(1); // Current segment end point.
if (p0 == p1 || q0 == q1)
{
// Ignore zero-length segments.
continue;
}
if (q0 != null && q1 != null)
{
// The two segments are connected.
if (p0 == q1 && p1 != null)
{
if (IsBadAngle(q0, p0, p1,threshold))
{
horrors++;
logger.Warning(String.Format("Bad segment angle found at index {0}.", i),
"PolygonValidator.HasBadAngles()");
}
}
}
i++;
}
return horrors > 0;
}
private static bool IsBadAngle(Point a, Point b, Point c, double threshold = 0.0)
{
double x = DotProduct(a, b, c);
double y = CrossProductLength(a, b, c);
return Math.Abs(Math.Atan2(y, x)) <= threshold;
}
// Returns the dot product <AB, BC>.
private static double DotProduct(Point a, Point b, Point c)
{
// Calculate the dot product.
return (a.x - b.x) * (c.x - b.x) + (a.y - b.y) * (c.y - b.y);
}
// Returns the length of cross product AB x BC.
private static double CrossProductLength(Point a, Point b, Point c)
{
// Calculate the Z coordinate of the cross product.
return (a.x - b.x) * (c.y - b.y) - (a.y - b.y) * (c.x - b.x);
}
private static int CheckVertexIDs(IPolygon poly, int count)
{
var logger = Log.Instance;
int horrors = 0;
int i = 0;
Vertex p, q;
foreach (var seg in poly.Segments)
{
p = seg.GetVertex(0);
q = seg.GetVertex(1);
if (p.id < 0 || p.id >= count)
{
horrors++;
logger.Warning(String.Format("Segment {0} has invalid startpoint.", i),
"PolygonValidator.IsConsistent()");
}
if (q.id < 0 || q.id >= count)
{
horrors++;
logger.Warning(String.Format("Segment {0} has invalid endpoint.", i),
"PolygonValidator.IsConsistent()");
}
i++;
}
return horrors;
}
private static int CheckDuplicateIDs(IPolygon poly)
{
var ids = new HashSet<int>();
// Check for duplicate ids.
foreach (var p in poly.Points)
{
if (!ids.Add(p.id))
{
Log.Instance.Warning("Found duplicate vertex ids.", "PolygonValidator.IsConsistent()");
return 1;
}
}
return 0;
}
}
}

View File

@ -0,0 +1,541 @@
// -----------------------------------------------------------------------
// <copyright file="QualityMeasure.cs" company="">
// Original Matlab code by John Burkardt, Florida State University
// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/
// </copyright>
// -----------------------------------------------------------------------
namespace TriangleNet.Tools
{
using System;
using TriangleNet.Geometry;
/// <summary>
/// Provides mesh quality information.
/// </summary>
/// <remarks>
/// Given a triangle abc with points A (ax, ay), B (bx, by), C (cx, cy).
///
/// The side lengths are given as
/// a = sqrt((cx - bx)^2 + (cy - by)^2) -- side BC opposite of A
/// b = sqrt((cx - ax)^2 + (cy - ay)^2) -- side CA opposite of B
/// c = sqrt((ax - bx)^2 + (ay - by)^2) -- side AB opposite of C
///
/// The angles are given as
/// ang_a = acos((b^2 + c^2 - a^2) / (2 * b * c)) -- angle at A
/// ang_b = acos((c^2 + a^2 - b^2) / (2 * c * a)) -- angle at B
/// ang_c = acos((a^2 + b^2 - c^2) / (2 * a * b)) -- angle at C
///
/// The semiperimeter is given as
/// s = (a + b + c) / 2
///
/// The area is given as
/// D = abs(ax * (by - cy) + bx * (cy - ay) + cx * (ay - by)) / 2
/// = sqrt(s * (s - a) * (s - b) * (s - c))
///
/// The inradius is given as
/// r = D / s
///
/// The circumradius is given as
/// R = a * b * c / (4 * D)
///
/// The altitudes are given as
/// alt_a = 2 * D / a -- altitude above side a
/// alt_b = 2 * D / b -- altitude above side b
/// alt_c = 2 * D / c -- altitude above side c
///
/// The aspect ratio may be given as the ratio of the longest to the
/// shortest edge or, more commonly as the ratio of the circumradius
/// to twice the inradius
/// ar = R / (2 * r)
/// = a * b * c / (8 * (s - a) * (s - b) * (s - c))
/// = a * b * c / ((b + c - a) * (c + a - b) * (a + b - c))
/// </remarks>
public class QualityMeasure
{
AreaMeasure areaMeasure;
AlphaMeasure alphaMeasure;
Q_Measure qMeasure;
Mesh mesh;
public QualityMeasure()
{
areaMeasure = new AreaMeasure();
alphaMeasure = new AlphaMeasure();
qMeasure = new Q_Measure();
}
#region Public properties
/// <summary>
/// Minimum triangle area.
/// </summary>
public double AreaMinimum
{
get { return areaMeasure.area_min; }
}
/// <summary>
/// Maximum triangle area.
/// </summary>
public double AreaMaximum
{
get { return areaMeasure.area_max; }
}
/// <summary>
/// Ratio of maximum and minimum triangle area.
/// </summary>
public double AreaRatio
{
get { return areaMeasure.area_max / areaMeasure.area_min; }
}
/// <summary>
/// Smallest angle.
/// </summary>
public double AlphaMinimum
{
get { return alphaMeasure.alpha_min; }
}
/// <summary>
/// Maximum smallest angle.
/// </summary>
public double AlphaMaximum
{
get { return alphaMeasure.alpha_max; }
}
/// <summary>
/// Average angle.
/// </summary>
public double AlphaAverage
{
get { return alphaMeasure.alpha_ave; }
}
/// <summary>
/// Average angle weighted by area.
/// </summary>
public double AlphaArea
{
get { return alphaMeasure.alpha_area; }
}
/// <summary>
/// Smallest aspect ratio.
/// </summary>
public double Q_Minimum
{
get { return qMeasure.q_min; }
}
/// <summary>
/// Largest aspect ratio.
/// </summary>
public double Q_Maximum
{
get { return qMeasure.q_max; }
}
/// <summary>
/// Average aspect ratio.
/// </summary>
public double Q_Average
{
get { return qMeasure.q_ave; }
}
/// <summary>
/// Average aspect ratio weighted by area.
/// </summary>
public double Q_Area
{
get { return qMeasure.q_area; }
}
#endregion
public void Update(Mesh mesh)
{
this.mesh = mesh;
// Reset all measures.
areaMeasure.Reset();
alphaMeasure.Reset();
qMeasure.Reset();
Compute();
}
private void Compute()
{
Point a, b, c;
double ab, bc, ca;
double lx, ly;
double area;
int n = 0;
foreach (var tri in mesh.triangles)
{
n++;
a = tri.vertices[0];
b = tri.vertices[1];
c = tri.vertices[2];
lx = a.x - b.x;
ly = a.y - b.y;
ab = Math.Sqrt(lx * lx + ly * ly);
lx = b.x - c.x;
ly = b.y - c.y;
bc = Math.Sqrt(lx * lx + ly * ly);
lx = c.x - a.x;
ly = c.y - a.y;
ca = Math.Sqrt(lx * lx + ly * ly);
area = areaMeasure.Measure(a, b, c);
alphaMeasure.Measure(ab, bc, ca, area);
qMeasure.Measure(ab, bc, ca, area);
}
// Normalize measures
alphaMeasure.Normalize(n, areaMeasure.area_total);
qMeasure.Normalize(n, areaMeasure.area_total);
}
/// <summary>
/// Determines the bandwidth of the coefficient matrix.
/// </summary>
/// <returns>Bandwidth of the coefficient matrix.</returns>
/// <remarks>
/// The quantity computed here is the "geometric" bandwidth determined
/// by the finite element mesh alone.
///
/// If a single finite element variable is associated with each node
/// of the mesh, and if the nodes and variables are numbered in the
/// same way, then the geometric bandwidth is the same as the bandwidth
/// of a typical finite element matrix.
///
/// The bandwidth M is defined in terms of the lower and upper bandwidths:
///
/// M = ML + 1 + MU
///
/// where
///
/// ML = maximum distance from any diagonal entry to a nonzero
/// entry in the same row, but earlier column,
///
/// MU = maximum distance from any diagonal entry to a nonzero
/// entry in the same row, but later column.
///
/// Because the finite element node adjacency relationship is symmetric,
/// we are guaranteed that ML = MU.
/// </remarks>
public int Bandwidth()
{
if (mesh == null) return 0;
// Lower and upper bandwidth of the matrix
int ml = 0, mu = 0;
int gi, gj;
foreach (var tri in mesh.triangles)
{
for (int j = 0; j < 3; j++)
{
gi = tri.GetVertex(j).id;
for (int k = 0; k < 3; k++)
{
gj = tri.GetVertex(k).id;
mu = Math.Max(mu, gj - gi);
ml = Math.Max(ml, gi - gj);
}
}
}
return ml + 1 + mu;
}
class AreaMeasure
{
// Minimum area
public double area_min = double.MaxValue;
// Maximum area
public double area_max = -double.MaxValue;
// Total area of geometry
public double area_total = 0;
// Nmber of triangles with zero area
public int area_zero = 0;
/// <summary>
/// Reset all values.
/// </summary>
public void Reset()
{
area_min = double.MaxValue;
area_max = -double.MaxValue;
area_total = 0;
area_zero = 0;
}
/// <summary>
/// Compute the area of given triangle.
/// </summary>
/// <param name="a">Triangle corner a.</param>
/// <param name="b">Triangle corner b.</param>
/// <param name="c">Triangle corner c.</param>
/// <returns>Triangle area.</returns>
public double Measure(Point a, Point b, Point c)
{
double area = 0.5 * Math.Abs(a.x * (b.y - c.y) + b.x * (c.y - a.y) + c.x * (a.y - b.y));
area_min = Math.Min(area_min, area);
area_max = Math.Max(area_max, area);
area_total += area;
if (area == 0.0)
{
area_zero = area_zero + 1;
}
return area;
}
}
/// <summary>
/// The alpha measure determines the triangulated pointset quality.
/// </summary>
/// <remarks>
/// The alpha measure evaluates the uniformity of the shapes of the triangles
/// defined by a triangulated pointset.
///
/// We compute the minimum angle among all the triangles in the triangulated
/// dataset and divide by the maximum possible value (which, in degrees,
/// is 60). The best possible value is 1, and the worst 0. A good
/// triangulation should have an alpha score close to 1.
/// </remarks>
class AlphaMeasure
{
// Minimum value over all triangles
public double alpha_min;
// Maximum value over all triangles
public double alpha_max;
// Value averaged over all triangles
public double alpha_ave;
// Value averaged over all triangles and weighted by area
public double alpha_area;
/// <summary>
/// Reset all values.
/// </summary>
public void Reset()
{
alpha_min = double.MaxValue;
alpha_max = -double.MaxValue;
alpha_ave = 0;
alpha_area = 0;
}
double acos(double c)
{
if (c <= -1.0)
{
return Math.PI;
}
else if (1.0 <= c)
{
return 0.0;
}
else
{
return Math.Acos(c);
}
}
/// <summary>
/// Compute q value of given triangle.
/// </summary>
/// <param name="ab">Side length ab.</param>
/// <param name="bc">Side length bc.</param>
/// <param name="ca">Side length ca.</param>
/// <param name="area">Triangle area.</param>
/// <returns></returns>
public double Measure(double ab, double bc, double ca, double area)
{
double alpha = double.MaxValue;
double ab2 = ab * ab;
double bc2 = bc * bc;
double ca2 = ca * ca;
double a_angle;
double b_angle;
double c_angle;
// Take care of a ridiculous special case.
if (ab == 0.0 && bc == 0.0 && ca == 0.0)
{
a_angle = 2.0 * Math.PI / 3.0;
b_angle = 2.0 * Math.PI / 3.0;
c_angle = 2.0 * Math.PI / 3.0;
}
else
{
if (ca == 0.0 || ab == 0.0)
{
a_angle = Math.PI;
}
else
{
a_angle = acos((ca2 + ab2 - bc2) / (2.0 * ca * ab));
}
if (ab == 0.0 || bc == 0.0)
{
b_angle = Math.PI;
}
else
{
b_angle = acos((ab2 + bc2 - ca2) / (2.0 * ab * bc));
}
if (bc == 0.0 || ca == 0.0)
{
c_angle = Math.PI;
}
else
{
c_angle = acos((bc2 + ca2 - ab2) / (2.0 * bc * ca));
}
}
alpha = Math.Min(alpha, a_angle);
alpha = Math.Min(alpha, b_angle);
alpha = Math.Min(alpha, c_angle);
// Normalize angle from [0,pi/3] radians into qualities in [0,1].
alpha = alpha * 3.0 / Math.PI;
alpha_ave += alpha;
alpha_area += area * alpha;
alpha_min = Math.Min(alpha, alpha_min);
alpha_max = Math.Max(alpha, alpha_max);
return alpha;
}
/// <summary>
/// Normalize values.
/// </summary>
public void Normalize(int n, double area_total)
{
if (n > 0)
{
alpha_ave /= n;
}
else
{
alpha_ave = 0.0;
}
if (0.0 < area_total)
{
alpha_area /= area_total;
}
else
{
alpha_area = 0.0;
}
}
}
/// <summary>
/// The Q measure determines the triangulated pointset quality.
/// </summary>
/// <remarks>
/// The Q measure evaluates the uniformity of the shapes of the triangles
/// defined by a triangulated pointset. It uses the aspect ratio
///
/// 2 * (incircle radius) / (circumcircle radius)
///
/// In an ideally regular mesh, all triangles would have the same
/// equilateral shape, for which Q = 1. A good mesh would have
/// 0.5 &lt; Q.
/// </remarks>
class Q_Measure
{
// Minimum value over all triangles
public double q_min;
// Maximum value over all triangles
public double q_max;
// Average value
public double q_ave;
// Average value weighted by the area of each triangle
public double q_area;
/// <summary>
/// Reset all values.
/// </summary>
public void Reset()
{
q_min = double.MaxValue;
q_max = -double.MaxValue;
q_ave = 0;
q_area = 0;
}
/// <summary>
/// Compute q value of given triangle.
/// </summary>
/// <param name="ab">Side length ab.</param>
/// <param name="bc">Side length bc.</param>
/// <param name="ca">Side length ca.</param>
/// <param name="area">Triangle area.</param>
/// <returns></returns>
public double Measure(double ab, double bc, double ca, double area)
{
double q = (bc + ca - ab) * (ca + ab - bc) * (ab + bc - ca) / (ab * bc * ca);
q_min = Math.Min(q_min, q);
q_max = Math.Max(q_max, q);
q_ave += q;
q_area += q * area;
return q;
}
/// <summary>
/// Normalize values.
/// </summary>
public void Normalize(int n, double area_total)
{
if (n > 0)
{
q_ave /= n;
}
else
{
q_ave = 0.0;
}
if (area_total > 0.0)
{
q_area /= area_total;
}
else
{
q_area = 0.0;
}
}
}
}
}

View File

@ -0,0 +1,528 @@
// -----------------------------------------------------------------------
// <copyright file="Statistic.cs">
// Original Triangle code by Jonathan Richard Shewchuk, http://www.cs.cmu.edu/~quake/triangle.html
// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/
// </copyright>
// -----------------------------------------------------------------------
namespace TriangleNet.Tools
{
using System;
using TriangleNet.Topology;
using TriangleNet.Geometry;
/// <summary>
/// Gather mesh statistics.
/// </summary>
public class Statistic
{
#region Static members
/// <summary>
/// Number of incircle tests performed.
/// </summary>
public static long InCircleCount = 0;
public static long InCircleAdaptCount = 0;
/// <summary>
/// Number of counterclockwise tests performed.
/// </summary>
public static long CounterClockwiseCount = 0;
public static long CounterClockwiseAdaptCount = 0;
/// <summary>
/// Number of 3D orientation tests performed.
/// </summary>
public static long Orient3dCount = 0;
/// <summary>
/// Number of right-of-hyperbola tests performed.
/// </summary>
public static long HyperbolaCount = 0;
/// <summary>
/// // Number of circumcenter calculations performed.
/// </summary>
public static long CircumcenterCount = 0;
/// <summary>
/// Number of circle top calculations performed.
/// </summary>
public static long CircleTopCount = 0;
/// <summary>
/// Number of vertex relocations.
/// </summary>
public static long RelocationCount = 0;
#endregion
#region Properties
double minEdge = 0;
/// <summary>
/// Gets the shortest edge.
/// </summary>
public double ShortestEdge { get { return minEdge; } }
double maxEdge = 0;
/// <summary>
/// Gets the longest edge.
/// </summary>
public double LongestEdge { get { return maxEdge; } }
//
double minAspect = 0;
/// <summary>
/// Gets the shortest altitude.
/// </summary>
public double ShortestAltitude { get { return minAspect; } }
double maxAspect = 0;
/// <summary>
/// Gets the largest aspect ratio.
/// </summary>
public double LargestAspectRatio { get { return maxAspect; } }
double minArea = 0;
/// <summary>
/// Gets the smallest area.
/// </summary>
public double SmallestArea { get { return minArea; } }
double maxArea = 0;
/// <summary>
/// Gets the largest area.
/// </summary>
public double LargestArea { get { return maxArea; } }
double minAngle = 0;
/// <summary>
/// Gets the smallest angle.
/// </summary>
public double SmallestAngle { get { return minAngle; } }
double maxAngle = 0;
/// <summary>
/// Gets the largest angle.
/// </summary>
public double LargestAngle { get { return maxAngle; } }
int[] angleTable;
/// <summary>
/// Gets the angle histogram.
/// </summary>
public int[] AngleHistogram { get { return angleTable; } }
int[] minAngles;
/// <summary>
/// Gets the min angles histogram.
/// </summary>
public int[] MinAngleHistogram { get { return minAngles; } }
int[] maxAngles;
/// <summary>
/// Gets the max angles histogram.
/// </summary>
public int[] MaxAngleHistogram { get { return maxAngles; } }
#endregion
#region Private methods
private void GetAspectHistogram(Mesh mesh)
{
int[] aspecttable;
double[] ratiotable;
aspecttable = new int[16];
ratiotable = new double[] {
1.5, 2.0, 2.5, 3.0, 4.0, 6.0, 10.0, 15.0, 25.0, 50.0,
100.0, 300.0, 1000.0, 10000.0, 100000.0, 0.0 };
Otri tri = default(Otri);
Vertex[] p = new Vertex[3];
double[] dx = new double[3], dy = new double[3];
double[] edgelength = new double[3];
double triarea;
double trilongest2;
double triminaltitude2;
double triaspect2;
int aspectindex;
int i, j, k;
tri.orient = 0;
foreach (var t in mesh.triangles)
{
tri.tri = t;
p[0] = tri.Org();
p[1] = tri.Dest();
p[2] = tri.Apex();
trilongest2 = 0.0;
for (i = 0; i < 3; i++)
{
j = plus1Mod3[i];
k = minus1Mod3[i];
dx[i] = p[j].x - p[k].x;
dy[i] = p[j].y - p[k].y;
edgelength[i] = dx[i] * dx[i] + dy[i] * dy[i];
if (edgelength[i] > trilongest2)
{
trilongest2 = edgelength[i];
}
}
//triarea = Primitives.CounterClockwise(p[0], p[1], p[2]);
triarea = Math.Abs((p[2].x - p[0].x) * (p[1].y - p[0].y) -
(p[1].x - p[0].x) * (p[2].y - p[0].y)) / 2.0;
triminaltitude2 = triarea * triarea / trilongest2;
triaspect2 = trilongest2 / triminaltitude2;
aspectindex = 0;
while ((triaspect2 > ratiotable[aspectindex] * ratiotable[aspectindex]) && (aspectindex < 15))
{
aspectindex++;
}
aspecttable[aspectindex]++;
}
}
#endregion
static readonly int[] plus1Mod3 = { 1, 2, 0 };
static readonly int[] minus1Mod3 = { 2, 0, 1 };
/// <summary>
/// Update statistics about the quality of the mesh.
/// </summary>
/// <param name="mesh"></param>
public void Update(Mesh mesh, int sampleDegrees)
{
Point[] p = new Point[3];
int k1, k2;
int degreeStep;
//sampleDegrees = 36; // sample every 5 degrees
//sampleDegrees = 45; // sample every 4 degrees
sampleDegrees = 60; // sample every 3 degrees
double[] cosSquareTable = new double[sampleDegrees / 2 - 1];
double[] dx = new double[3];
double[] dy = new double[3];
double[] edgeLength = new double[3];
double dotProduct;
double cosSquare;
double triArea;
double triLongest2;
double triMinAltitude2;
double triAspect2;
double radconst = Math.PI / sampleDegrees;
double degconst = 180.0 / Math.PI;
// New angle table
angleTable = new int[sampleDegrees];
minAngles = new int[sampleDegrees];
maxAngles = new int[sampleDegrees];
for (int i = 0; i < sampleDegrees / 2 - 1; i++)
{
cosSquareTable[i] = Math.Cos(radconst * (i + 1));
cosSquareTable[i] = cosSquareTable[i] * cosSquareTable[i];
}
for (int i = 0; i < sampleDegrees; i++)
{
angleTable[i] = 0;
}
minAspect = mesh.bounds.Width + mesh.bounds.Height;
minAspect = minAspect * minAspect;
maxAspect = 0.0;
minEdge = minAspect;
maxEdge = 0.0;
minArea = minAspect;
maxArea = 0.0;
minAngle = 0.0;
maxAngle = 2.0;
bool acuteBiggest = true;
bool acuteBiggestTri = true;
double triMinAngle, triMaxAngle = 1;
foreach (var tri in mesh.triangles)
{
triMinAngle = 0; // Min angle: 0 < a < 60 degress
triMaxAngle = 1; // Max angle: 60 < a < 180 degress
p[0] = tri.vertices[0];
p[1] = tri.vertices[1];
p[2] = tri.vertices[2];
triLongest2 = 0.0;
for (int i = 0; i < 3; i++)
{
k1 = plus1Mod3[i];
k2 = minus1Mod3[i];
dx[i] = p[k1].x - p[k2].x;
dy[i] = p[k1].y - p[k2].y;
edgeLength[i] = dx[i] * dx[i] + dy[i] * dy[i];
if (edgeLength[i] > triLongest2)
{
triLongest2 = edgeLength[i];
}
if (edgeLength[i] > maxEdge)
{
maxEdge = edgeLength[i];
}
if (edgeLength[i] < minEdge)
{
minEdge = edgeLength[i];
}
}
//triarea = Primitives.CounterClockwise(p[0], p[1], p[2]);
triArea = Math.Abs((p[2].x - p[0].x) * (p[1].y - p[0].y) -
(p[1].x - p[0].x) * (p[2].y - p[0].y));
if (triArea < minArea)
{
minArea = triArea;
}
if (triArea > maxArea)
{
maxArea = triArea;
}
triMinAltitude2 = triArea * triArea / triLongest2;
if (triMinAltitude2 < minAspect)
{
minAspect = triMinAltitude2;
}
triAspect2 = triLongest2 / triMinAltitude2;
if (triAspect2 > maxAspect)
{
maxAspect = triAspect2;
}
for (int i = 0; i < 3; i++)
{
k1 = plus1Mod3[i];
k2 = minus1Mod3[i];
dotProduct = dx[k1] * dx[k2] + dy[k1] * dy[k2];
cosSquare = dotProduct * dotProduct / (edgeLength[k1] * edgeLength[k2]);
degreeStep = sampleDegrees / 2 - 1;
for (int j = degreeStep - 1; j >= 0; j--)
{
if (cosSquare > cosSquareTable[j])
{
degreeStep = j;
}
}
if (dotProduct <= 0.0)
{
angleTable[degreeStep]++;
if (cosSquare > minAngle)
{
minAngle = cosSquare;
}
if (acuteBiggest && (cosSquare < maxAngle))
{
maxAngle = cosSquare;
}
// Update min/max angle per triangle
if (cosSquare > triMinAngle)
{
triMinAngle = cosSquare;
}
if (acuteBiggestTri && (cosSquare < triMaxAngle))
{
triMaxAngle = cosSquare;
}
}
else
{
angleTable[sampleDegrees - degreeStep - 1]++;
if (acuteBiggest || (cosSquare > maxAngle))
{
maxAngle = cosSquare;
acuteBiggest = false;
}
// Update max angle for (possibly non-acute) triangle
if (acuteBiggestTri || (cosSquare > triMaxAngle))
{
triMaxAngle = cosSquare;
acuteBiggestTri = false;
}
}
}
// Update min angle histogram
degreeStep = sampleDegrees / 2 - 1;
for (int j = degreeStep - 1; j >= 0; j--)
{
if (triMinAngle > cosSquareTable[j])
{
degreeStep = j;
}
}
minAngles[degreeStep]++;
// Update max angle histogram
degreeStep = sampleDegrees / 2 - 1;
for (int j = degreeStep - 1; j >= 0; j--)
{
if (triMaxAngle > cosSquareTable[j])
{
degreeStep = j;
}
}
if (acuteBiggestTri)
{
maxAngles[degreeStep]++;
}
else
{
maxAngles[sampleDegrees - degreeStep - 1]++;
}
acuteBiggestTri = true;
}
minEdge = Math.Sqrt(minEdge);
maxEdge = Math.Sqrt(maxEdge);
minAspect = Math.Sqrt(minAspect);
maxAspect = Math.Sqrt(maxAspect);
minArea *= 0.5;
maxArea *= 0.5;
if (minAngle >= 1.0)
{
minAngle = 0.0;
}
else
{
minAngle = degconst * Math.Acos(Math.Sqrt(minAngle));
}
if (maxAngle >= 1.0)
{
maxAngle = 180.0;
}
else
{
if (acuteBiggest)
{
maxAngle = degconst * Math.Acos(Math.Sqrt(maxAngle));
}
else
{
maxAngle = 180.0 - degconst * Math.Acos(Math.Sqrt(maxAngle));
}
}
}
/// <summary>
/// Compute angle information for given triangle.
/// </summary>
/// <param name="triangle">The triangle to check.</param>
/// <param name="data">Array of doubles (length 6).</param>
/// <remarks>
/// On return, the squared cosines of the minimum and maximum angle will
/// be stored at position data[0] and data[1] respectively.
/// If the triangle was obtuse, data[2] will be set to -1 and maximum angle
/// is computed as (pi - acos(sqrt(data[1]))).
/// </remarks>
public static void ComputeAngles(ITriangle triangle, double[] data)
{
double min = 0.0;
double max = 1.0;
var va = triangle.GetVertex(0);
var vb = triangle.GetVertex(1);
var vc = triangle.GetVertex(2);
double dxa = vb.x - vc.x;
double dya = vb.y - vc.y;
double lena = dxa * dxa + dya * dya;
double dxb = vc.x - va.x;
double dyb = vc.y - va.y;
double lenb = dxb * dxb + dyb * dyb;
double dxc = va.x - vb.x;
double dyc = va.y - vb.y;
double lenc = dxc * dxc + dyc * dyc;
// Dot products.
double dota = data[0] = dxb * dxc + dyb * dyc;
double dotb = data[1] = dxc * dxa + dyc * dya;
double dotc = data[2] = dxa * dxb + dya * dyb;
// Squared cosines.
data[3] = (dota * dota) / (lenb * lenc);
data[4] = (dotb * dotb) / (lenc * lena);
data[5] = (dotc * dotc) / (lena * lenb);
// The sign of the dot product will tell us, if the angle is
// acute (value < 0) or obtuse (value > 0).
bool acute = true;
double cos, dot;
for (int i = 0; i < 3; i++)
{
dot = data[i];
cos = data[3 + i];
if (dot <= 0.0)
{
if (cos > min)
{
min = cos;
}
if (acute && (cos < max))
{
max = cos;
}
}
else
{
// Update max angle for (possibly non-acute) triangle
if (acute || (cos > max))
{
max = cos;
acute = false;
}
}
}
data[0] = min;
data[1] = max;
data[2] = acute ? 1.0 : -1.0;
}
}
}

View File

@ -0,0 +1,426 @@
// -----------------------------------------------------------------------
// <copyright file="TriangleQuadTree.cs" company="">
// Original code by Frank Dockhorn, [not available anymore: http://sourceforge.net/projects/quadtreesim/]
// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/
// </copyright>
// -----------------------------------------------------------------------
namespace TriangleNet.Tools
{
using System.Collections.Generic;
using System.Linq;
using TriangleNet.Geometry;
/// <summary>
/// A Quadtree implementation optimized for triangles.
/// </summary>
public class TriangleQuadTree
{
QuadNode root;
internal ITriangle[] triangles;
internal int sizeBound;
internal int maxDepth;
/// <summary>
/// Initializes a new instance of the <see cref="TriangleQuadTree" /> class.
/// </summary>
/// <param name="mesh">Mesh containing triangles.</param>
/// <param name="maxDepth">The maximum depth of the tree.</param>
/// <param name="sizeBound">The maximum number of triangles contained in a leaf.</param>
/// <remarks>
/// The quadtree does not track changes of the mesh. If a mesh is refined or
/// changed in any other way, a new quadtree has to be built to make the point
/// location work.
///
/// A node of the tree will be split, if its level if less than the max depth parameter
/// AND the number of triangles in the node is greater than the size bound.
/// </remarks>
public TriangleQuadTree(Mesh mesh, int maxDepth = 10, int sizeBound = 10)
{
this.maxDepth = maxDepth;
this.sizeBound = sizeBound;
triangles = mesh.Triangles.ToArray();
int currentDepth = 0;
root = new QuadNode(mesh.Bounds, this, true);
root.CreateSubRegion(++currentDepth);
}
public ITriangle Query(double x, double y)
{
var point = new Point(x, y);
var indices = root.FindTriangles(point);
foreach (var i in indices)
{
var tri = this.triangles[i];
if (IsPointInTriangle(point, tri.GetVertex(0), tri.GetVertex(1), tri.GetVertex(2)))
{
return tri;
}
}
return null;
}
/// <summary>
/// Test, if a given point lies inside a triangle.
/// </summary>
/// <param name="p">Point to locate.</param>
/// <param name="t0">Corner point of triangle.</param>
/// <param name="t1">Corner point of triangle.</param>
/// <param name="t2">Corner point of triangle.</param>
/// <returns>True, if point is inside or on the edge of this triangle.</returns>
internal static bool IsPointInTriangle(Point p, Point t0, Point t1, Point t2)
{
// 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(p.x - t0.x, p.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 double DotProduct(Point p, Point q)
{
return p.x * q.x + p.y * q.y;
}
/// <summary>
/// A node of the quadtree.
/// </summary>
class QuadNode
{
const int SW = 0;
const int SE = 1;
const int NW = 2;
const int NE = 3;
const double EPS = 1e-6;
static readonly byte[] BITVECTOR = { 0x1, 0x2, 0x4, 0x8 };
Rectangle bounds;
Point pivot;
TriangleQuadTree tree;
QuadNode[] regions;
List<int> triangles;
byte bitRegions;
public QuadNode(Rectangle box, TriangleQuadTree tree)
: this(box, tree, false)
{
}
public QuadNode(Rectangle box, TriangleQuadTree tree, bool init)
{
this.tree = tree;
this.bounds = new Rectangle(box.Left, box.Bottom, box.Width, box.Height);
this.pivot = new Point((box.Left + box.Right) / 2, (box.Bottom + box.Top) / 2);
this.bitRegions = 0;
this.regions = new QuadNode[4];
this.triangles = new List<int>();
if (init)
{
int count = tree.triangles.Length;
// Allocate memory upfront
triangles.Capacity = count;
for (int i = 0; i < count; i++)
{
triangles.Add(i);
}
}
}
public List<int> FindTriangles(Point searchPoint)
{
int region = FindRegion(searchPoint);
if (regions[region] == null)
{
return triangles;
}
return regions[region].FindTriangles(searchPoint);
}
public void CreateSubRegion(int currentDepth)
{
// The four sub regions of the quad tree
// +--------------+
// | nw 2 | ne 3 |
// |------+pivot--|
// | sw 0 | se 1 |
// +--------------+
Rectangle box;
var width = bounds.Right - pivot.x;
var height = bounds.Top - pivot.y;
// 1. region south west
box = new Rectangle(bounds.Left, bounds.Bottom, width, height);
regions[0] = new QuadNode(box, tree);
// 2. region south east
box = new Rectangle(pivot.x, bounds.Bottom, width, height);
regions[1] = new QuadNode(box, tree);
// 3. region north west
box = new Rectangle(bounds.Left, pivot.y, width, height);
regions[2] = new QuadNode(box, tree);
// 4. region north east
box = new Rectangle(pivot.x, pivot.y, width, height);
regions[3] = new QuadNode(box, tree);
Point[] triangle = new Point[3];
// Find region for every triangle vertex
foreach (var index in triangles)
{
ITriangle tri = tree.triangles[index];
triangle[0] = tri.GetVertex(0);
triangle[1] = tri.GetVertex(1);
triangle[2] = tri.GetVertex(2);
AddTriangleToRegion(triangle, index);
}
for (int i = 0; i < 4; i++)
{
if (regions[i].triangles.Count > tree.sizeBound && currentDepth < tree.maxDepth)
{
regions[i].CreateSubRegion(currentDepth + 1);
}
}
}
void AddTriangleToRegion(Point[] triangle, int index)
{
bitRegions = 0;
if (TriangleQuadTree.IsPointInTriangle(pivot, triangle[0], triangle[1], triangle[2]))
{
AddToRegion(index, SW);
AddToRegion(index, SE);
AddToRegion(index, NW);
AddToRegion(index, NE);
return;
}
FindTriangleIntersections(triangle, index);
if (bitRegions == 0)
{
// we didn't find any intersection so we add this triangle to a point's region
int region = FindRegion(triangle[0]);
regions[region].triangles.Add(index);
}
}
void FindTriangleIntersections(Point[] triangle, int index)
{
// PLEASE NOTE:
// Handling of component comparison is tightly associated with the implementation
// of the findRegion() function. That means when the point to be compared equals
// the pivot point the triangle must be put at least into region 2.
//
// Linear equations are in parametric form.
// pivot.x = triangle[0].x + t * (triangle[1].x - triangle[0].x)
// pivot.y = triangle[0].y + t * (triangle[1].y - triangle[0].y)
int k = 2;
double dx, dy;
// Iterate through all triangle laterals and find bounding box intersections
for (int i = 0; i < 3; k = i++)
{
dx = triangle[i].x - triangle[k].x;
dy = triangle[i].y - triangle[k].y;
if (dx != 0.0)
{
FindIntersectionsWithX(dx, dy, triangle, index, k);
}
if (dy != 0.0)
{
FindIntersectionsWithY(dx, dy, triangle, index, k);
}
}
}
void FindIntersectionsWithX(double dx, double dy, Point[] triangle, int index, int k)
{
double t;
// find intersection with plane x = m_pivot.dX
t = (pivot.x - triangle[k].x) / dx;
if (t < (1 + EPS) && t > -EPS)
{
// we have an intersection
double yComponent = triangle[k].y + t * dy;
if (yComponent < pivot.y && yComponent >= bounds.Bottom)
{
AddToRegion(index, SW);
AddToRegion(index, SE);
}
else if (yComponent <= bounds.Top)
{
AddToRegion(index, NW);
AddToRegion(index, NE);
}
}
// find intersection with plane x = m_boundingBox[0].dX
t = (bounds.Left - triangle[k].x) / dx;
if (t < (1 + EPS) && t > -EPS)
{
// we have an intersection
double yComponent = triangle[k].y + t * dy;
if (yComponent < pivot.y && yComponent >= bounds.Bottom)
{
AddToRegion(index, SW);
}
else if (yComponent <= bounds.Top) // TODO: check && yComponent >= pivot.Y
{
AddToRegion(index, NW);
}
}
// find intersection with plane x = m_boundingBox[1].dX
t = (bounds.Right - triangle[k].x) / dx;
if (t < (1 + EPS) && t > -EPS)
{
// we have an intersection
double yComponent = triangle[k].y + t * dy;
if (yComponent < pivot.y && yComponent >= bounds.Bottom)
{
AddToRegion(index, SE);
}
else if (yComponent <= bounds.Top)
{
AddToRegion(index, NE);
}
}
}
void FindIntersectionsWithY(double dx, double dy, Point[] triangle, int index, int k)
{
double t, xComponent;
// find intersection with plane y = m_pivot.dY
t = (pivot.y - triangle[k].y) / dy;
if (t < (1 + EPS) && t > -EPS)
{
// we have an intersection
xComponent = triangle[k].x + t * dx;
if (xComponent > pivot.x && xComponent <= bounds.Right)
{
AddToRegion(index, SE);
AddToRegion(index, NE);
}
else if (xComponent >= bounds.Left)
{
AddToRegion(index, SW);
AddToRegion(index, NW);
}
}
// find intersection with plane y = m_boundingBox[0].dY
t = (bounds.Bottom - triangle[k].y) / dy;
if (t < (1 + EPS) && t > -EPS)
{
// we have an intersection
xComponent = triangle[k].x + t * dx;
if (xComponent > pivot.x && xComponent <= bounds.Right)
{
AddToRegion(index, SE);
}
else if (xComponent >= bounds.Left)
{
AddToRegion(index, SW);
}
}
// find intersection with plane y = m_boundingBox[1].dY
t = (bounds.Top - triangle[k].y) / dy;
if (t < (1 + EPS) && t > -EPS)
{
// we have an intersection
xComponent = triangle[k].x + t * dx;
if (xComponent > pivot.x && xComponent <= bounds.Right)
{
AddToRegion(index, NE);
}
else if (xComponent >= bounds.Left)
{
AddToRegion(index, NW);
}
}
}
int FindRegion(Point point)
{
int b = 2;
if (point.y < pivot.y)
{
b = 0;
}
if (point.x > pivot.x)
{
b++;
}
return b;
}
void AddToRegion(int index, int region)
{
//if (!(m_bitRegions & BITVECTOR[region]))
if ((bitRegions & BITVECTOR[region]) == 0)
{
regions[region].triangles.Add(index);
bitRegions |= BITVECTOR[region];
}
}
}
}
}

View File

@ -0,0 +1,371 @@
// -----------------------------------------------------------------------
// <copyright file="VertexSorter.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 TriangleNet.Tools
{
using System;
using TriangleNet.Geometry;
/// <summary>
/// Sort an array of points using quicksort.
/// </summary>
public class VertexSorter
{
private const int RANDOM_SEED = 57113;
Random rand;
Vertex[] points;
VertexSorter(Vertex[] points, int seed)
{
this.points = points;
this.rand = new Random(seed);
}
/// <summary>
/// Sorts the given vertex array by x-coordinate.
/// </summary>
/// <param name="array">The vertex array.</param>
/// <param name="seed">Random seed used for pivoting.</param>
public static void Sort(Vertex[] array, int seed = RANDOM_SEED)
{
var qs = new VertexSorter(array, seed);
qs.QuickSort(0, array.Length - 1);
}
/// <summary>
/// Impose alternating cuts on given vertex array.
/// </summary>
/// <param name="array">The vertex array.</param>
/// <param name="length">The number of vertices to sort.</param>
/// <param name="seed">Random seed used for pivoting.</param>
public static void Alternate(Vertex[] array, int length, int seed = RANDOM_SEED)
{
var qs = new VertexSorter(array, seed);
int divider = length >> 1;
// Re-sort the array of vertices to accommodate alternating cuts.
if (length - divider >= 2)
{
if (divider >= 2)
{
qs.AlternateAxes(0, divider - 1, 1);
}
qs.AlternateAxes(divider, length - 1, 1);
}
}
#region Quicksort
/// <summary>
/// Sort an array of vertices by x-coordinate, using the y-coordinate as a secondary key.
/// </summary>
/// <param name="left"></param>
/// <param name="right"></param>
/// <remarks>
/// Uses quicksort. Randomized O(n log n) time. No, I did not make any of
/// the usual quicksort mistakes.
/// </remarks>
private void QuickSort(int left, int right)
{
int oleft = left;
int oright = right;
int arraysize = right - left + 1;
int pivot;
double pivotx, pivoty;
Vertex temp;
var array = this.points;
if (arraysize < 32)
{
// Insertion sort
for (int i = left + 1; i <= right; i++)
{
var a = array[i];
int j = i - 1;
while (j >= left && (array[j].x > a.x || (array[j].x == a.x && array[j].y > a.y)))
{
array[j + 1] = array[j];
j--;
}
array[j + 1] = a;
}
return;
}
// Choose a random pivot to split the array.
pivot = rand.Next(left, right);
pivotx = array[pivot].x;
pivoty = array[pivot].y;
// Split the array.
left--;
right++;
while (left < right)
{
// Search for a vertex whose x-coordinate is too large for the left.
do
{
left++;
}
while ((left <= right) && ((array[left].x < pivotx) ||
((array[left].x == pivotx) && (array[left].y < pivoty))));
// Search for a vertex whose x-coordinate is too small for the right.
do
{
right--;
}
while ((left <= right) && ((array[right].x > pivotx) ||
((array[right].x == pivotx) && (array[right].y > pivoty))));
if (left < right)
{
// Swap the left and right vertices.
temp = array[left];
array[left] = array[right];
array[right] = temp;
}
}
if (left > oleft)
{
// Recursively sort the left subset.
QuickSort(oleft, left);
}
if (oright > right + 1)
{
// Recursively sort the right subset.
QuickSort(right + 1, oright);
}
}
#endregion
#region Alternate axes
/// <summary>
/// Sorts the vertices as appropriate for the divide-and-conquer algorithm with
/// alternating cuts.
/// </summary>
/// <param name="left"></param>
/// <param name="right"></param>
/// <param name="axis"></param>
/// <remarks>
/// Partitions by x-coordinate if axis == 0; by y-coordinate if axis == 1.
/// For the base case, subsets containing only two or three vertices are
/// always sorted by x-coordinate.
/// </remarks>
private void AlternateAxes(int left, int right, int axis)
{
int size = right - left + 1;
int divider = size >> 1;
if (size <= 3)
{
// Recursive base case: subsets of two or three vertices will be
// handled specially, and should always be sorted by x-coordinate.
axis = 0;
}
// Partition with a horizontal or vertical cut.
if (axis == 0)
{
VertexMedianX(left, right, left + divider);
}
else
{
VertexMedianY(left, right, left + divider);
}
// Recursively partition the subsets with a cross cut.
if (size - divider >= 2)
{
if (divider >= 2)
{
AlternateAxes(left, left + divider - 1, 1 - axis);
}
AlternateAxes(left + divider, right, 1 - axis);
}
}
/// <summary>
/// An order statistic algorithm, almost. Shuffles an array of vertices so that the
/// first 'median' vertices occur lexicographically before the remaining vertices.
/// </summary>
/// <param name="left"></param>
/// <param name="right"></param>
/// <param name="median"></param>
/// <remarks>
/// Uses the x-coordinate as the primary key. Very similar to the QuickSort()
/// procedure, but runs in randomized linear time.
/// </remarks>
private void VertexMedianX(int left, int right, int median)
{
int arraysize = right - left + 1;
int oleft = left, oright = right;
int pivot;
double pivot1, pivot2;
Vertex temp;
var array = this.points;
if (arraysize == 2)
{
// Recursive base case.
if ((array[left].x > array[right].x) ||
((array[left].x == array[right].x) &&
(array[left].y > array[right].y)))
{
temp = array[right];
array[right] = array[left];
array[left] = temp;
}
return;
}
// Choose a random pivot to split the array.
pivot = rand.Next(left, right);
pivot1 = array[pivot].x;
pivot2 = array[pivot].y;
left--;
right++;
while (left < right)
{
// Search for a vertex whose x-coordinate is too large for the left.
do
{
left++;
}
while ((left <= right) && ((array[left].x < pivot1) ||
((array[left].x == pivot1) && (array[left].y < pivot2))));
// Search for a vertex whose x-coordinate is too small for the right.
do
{
right--;
}
while ((left <= right) && ((array[right].x > pivot1) ||
((array[right].x == pivot1) && (array[right].y > pivot2))));
if (left < right)
{
// Swap the left and right vertices.
temp = array[left];
array[left] = array[right];
array[right] = temp;
}
}
// Unlike in vertexsort(), at most one of the following conditionals is true.
if (left > median)
{
// Recursively shuffle the left subset.
VertexMedianX(oleft, left - 1, median);
}
if (right < median - 1)
{
// Recursively shuffle the right subset.
VertexMedianX(right + 1, oright, median);
}
}
/// <summary>
/// An order statistic algorithm, almost. Shuffles an array of vertices so that
/// the first 'median' vertices occur lexicographically before the remaining vertices.
/// </summary>
/// <param name="left"></param>
/// <param name="right"></param>
/// <param name="median"></param>
/// <remarks>
/// Uses the y-coordinate as the primary key. Very similar to the QuickSort()
/// procedure, but runs in randomized linear time.
/// </remarks>
private void VertexMedianY(int left, int right, int median)
{
int arraysize = right - left + 1;
int oleft = left, oright = right;
int pivot;
double pivot1, pivot2;
Vertex temp;
var array = this.points;
if (arraysize == 2)
{
// Recursive base case.
if ((array[left].y > array[right].y) ||
((array[left].y == array[right].y) &&
(array[left].x > array[right].x)))
{
temp = array[right];
array[right] = array[left];
array[left] = temp;
}
return;
}
// Choose a random pivot to split the array.
pivot = rand.Next(left, right);
pivot1 = array[pivot].y;
pivot2 = array[pivot].x;
left--;
right++;
while (left < right)
{
// Search for a vertex whose x-coordinate is too large for the left.
do
{
left++;
}
while ((left <= right) && ((array[left].y < pivot1) ||
((array[left].y == pivot1) && (array[left].x < pivot2))));
// Search for a vertex whose x-coordinate is too small for the right.
do
{
right--;
}
while ((left <= right) && ((array[right].y > pivot1) ||
((array[right].y == pivot1) && (array[right].x > pivot2))));
if (left < right)
{
// Swap the left and right vertices.
temp = array[left];
array[left] = array[right];
array[right] = temp;
}
}
// Unlike in QuickSort(), at most one of the following conditionals is true.
if (left > median)
{
// Recursively shuffle the left subset.
VertexMedianY(oleft, left - 1, median);
}
if (right < median - 1)
{
// Recursively shuffle the right subset.
VertexMedianY(right + 1, oright, median);
}
}
#endregion
}
}

View File

@ -0,0 +1,269 @@
// -----------------------------------------------------------------------
// <copyright file="DcelMesh.cs">
// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/
// </copyright>
// -----------------------------------------------------------------------
namespace TriangleNet.Topology.DCEL
{
using System.Collections.Generic;
using TriangleNet.Geometry;
public class DcelMesh
{
protected List<Vertex> vertices;
protected List<HalfEdge> edges;
protected List<Face> faces;
/// <summary>
/// Initializes a new instance of the <see cref="DcelMesh" /> class.
/// </summary>
public DcelMesh()
: this(true)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="" /> class.
/// </summary>
/// <param name="initialize">If false, lists will not be initialized.</param>
protected DcelMesh(bool initialize)
{
if (initialize)
{
vertices = new List<Vertex>();
edges = new List<HalfEdge>();
faces = new List<Face>();
}
}
/// <summary>
/// Gets the vertices of the Voronoi diagram.
/// </summary>
public List<Vertex> Vertices
{
get { return vertices; }
}
/// <summary>
/// Gets the list of half-edges specify the Voronoi diagram topology.
/// </summary>
public List<HalfEdge> HalfEdges
{
get { return edges; }
}
/// <summary>
/// Gets the faces of the Voronoi diagram.
/// </summary>
public List<Face> Faces
{
get { return faces; }
}
/// <summary>
/// Gets the collection of edges of the Voronoi diagram.
/// </summary>
public IEnumerable<IEdge> Edges
{
get { return EnumerateEdges(); }
}
/// <summary>
/// Check if the DCEL is consistend.
/// </summary>
/// <param name="closed">If true, faces are assumed to be closed (i.e. all edges must have
/// a valid next pointer).</param>
/// <param name="depth">Maximum edge count of faces (default = 0 means skip check).</param>
/// <returns></returns>
public virtual bool IsConsistent(bool closed = true, int depth = 0)
{
// Check vertices for null pointers.
foreach (var vertex in vertices)
{
if (vertex.id < 0)
{
continue;
}
if (vertex.leaving == null)
{
return false;
}
if (vertex.Leaving.Origin.id != vertex.id)
{
return false;
}
}
// Check faces for null pointers.
foreach (var face in faces)
{
if (face.ID < 0)
{
continue;
}
if (face.edge == null)
{
return false;
}
if (face.id != face.edge.face.id)
{
return false;
}
}
// Check half-edges for null pointers.
foreach (var edge in edges)
{
if (edge.id < 0)
{
continue;
}
if (edge.twin == null)
{
return false;
}
if (edge.origin == null)
{
return false;
}
if (edge.face == null)
{
return false;
}
if (closed && edge.next == null)
{
return false;
}
}
// Check half-edges (topology).
foreach (var edge in edges)
{
if (edge.id < 0)
{
continue;
}
var twin = edge.twin;
var next = edge.next;
if (edge.id != twin.twin.id)
{
return false;
}
if (closed)
{
if (next.origin.id != twin.origin.id)
{
return false;
}
if (next.twin.next.origin.id != edge.twin.origin.id)
{
return false;
}
}
}
if (closed && depth > 0)
{
// Check if faces are closed.
foreach (var face in faces)
{
if (face.id < 0)
{
continue;
}
var edge = face.edge;
var next = edge.next;
int id = edge.id;
int k = 0;
while (next.id != id && k < depth)
{
next = next.next;
k++;
}
if (next.id != id)
{
return false;
}
}
}
return true;
}
/// <summary>
/// Search for half-edge without twin and add a twin. Connect twins to form connected
/// boundary contours.
/// </summary>
/// <remarks>
/// This method assumes that all faces are closed (i.e. no edge.next pointers are null).
/// </remarks>
public void ResolveBoundaryEdges()
{
// Maps vertices to leaving boundary edge.
var map = new Dictionary<int, HalfEdge>();
// TODO: parallel?
foreach (var edge in this.edges)
{
if (edge.twin == null)
{
var twin = edge.twin = new HalfEdge(edge.next.origin, Face.Empty);
twin.twin = edge;
map.Add(twin.origin.id, twin);
}
}
int j = edges.Count;
foreach (var edge in map.Values)
{
edge.id = j++;
edge.next = map[edge.twin.origin.id];
this.edges.Add(edge);
}
}
/// <summary>
/// Enumerates all edges of the DCEL.
/// </summary>
/// <remarks>
/// This method assumes that each half-edge has a twin (i.e. NOT null).
/// </remarks>
protected virtual IEnumerable<IEdge> EnumerateEdges()
{
var edges = new List<IEdge>(this.edges.Count / 2);
foreach (var edge in this.edges)
{
var twin = edge.twin;
// Report edge only once.
if (edge.id < twin.id)
{
edges.Add(new Edge(edge.origin.id, twin.origin.id));
}
}
return edges;
}
}
}

View File

@ -0,0 +1,112 @@
// -----------------------------------------------------------------------
// <copyright file="Face.cs">
// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/
// </copyright>
// -----------------------------------------------------------------------
namespace TriangleNet.Topology.DCEL
{
using System.Collections.Generic;
using TriangleNet.Geometry;
/// <summary>
/// A face of DCEL mesh.
/// </summary>
public class Face
{
#region Static initialization of "Outer Space" face
public static readonly Face Empty;
static Face()
{
Empty = new Face(null);
Empty.id = -1;
}
#endregion
internal int id;
internal int mark;
internal Point generator;
internal HalfEdge edge;
internal bool bounded;
/// <summary>
/// Gets or sets the face id.
/// </summary>
public int ID
{
get { return id; }
set { id = value; }
}
/// <summary>
/// Gets or sets a half-edge connected to the face.
/// </summary>
public HalfEdge Edge
{
get { return edge; }
set { edge = value; }
}
/// <summary>
/// Gets or sets a value, indicating if the face is bounded (for Voronoi diagram).
/// </summary>
public bool Bounded
{
get { return bounded; }
set { bounded = value; }
}
/// <summary>
/// Initializes a new instance of the <see cref="Face" /> class.
/// </summary>
/// <param name="generator">The generator of this face (for Voronoi diagram)</param>
public Face(Point generator)
: this(generator, null)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="Face" /> class.
/// </summary>
/// <param name="generator">The generator of this face (for Voronoi diagram)</param>
/// <param name="edge">The half-edge connected to this face.</param>
public Face(Point generator, HalfEdge edge)
{
this.generator = generator;
this.edge = edge;
this.bounded = true;
if (generator != null)
{
this.id = generator.ID;
}
}
/// <summary>
/// Enumerates all half-edges of the face boundary.
/// </summary>
/// <returns></returns>
public IEnumerable<HalfEdge> EnumerateEdges()
{
var edge = this.Edge;
int first = edge.ID;
do
{
yield return edge;
edge = edge.Next;
} while (edge.ID != first);
}
public override string ToString()
{
return string.Format("F-ID {0}", id);
}
}
}

View File

@ -0,0 +1,101 @@
// -----------------------------------------------------------------------
// <copyright file="HalfEdge.cs">
// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/
// </copyright>
// -----------------------------------------------------------------------
namespace TriangleNet.Topology.DCEL
{
public class HalfEdge
{
internal int id;
internal int mark;
internal Vertex origin;
internal Face face;
internal HalfEdge twin;
internal HalfEdge next;
/// <summary>
/// Gets or sets the half-edge id.
/// </summary>
public int ID
{
get { return id; }
set { id = value; }
}
public int Boundary
{
get { return mark; }
set { mark = value; }
}
/// <summary>
/// Gets or sets the origin of the half-edge.
/// </summary>
public Vertex Origin
{
get { return origin; }
set { origin = value; }
}
/// <summary>
/// Gets or sets the face connected to the half-edge.
/// </summary>
public Face Face
{
get { return face; }
set { face = value; }
}
/// <summary>
/// Gets or sets the twin of the half-edge.
/// </summary>
public HalfEdge Twin
{
get { return twin; }
set { twin = value; }
}
/// <summary>
/// Gets or sets the next pointer of the half-edge.
/// </summary>
public HalfEdge Next
{
get { return next; }
set { next = value; }
}
/// <summary>
/// Initializes a new instance of the <see cref="HalfEdge" /> class.
/// </summary>
/// <param name="origin">The origin of this half-edge.</param>
public HalfEdge(Vertex origin)
{
this.origin = origin;
}
/// <summary>
/// Initializes a new instance of the <see cref="HalfEdge" /> class.
/// </summary>
/// <param name="origin">The origin of this half-edge.</param>
/// <param name="face">The face connected to this half-edge.</param>
public HalfEdge(Vertex origin, Face face)
{
this.origin = origin;
this.face = face;
// IMPORTANT: do not remove the (face.edge == null) check!
if (face != null && face.edge == null)
{
face.edge = this;
}
}
public override string ToString()
{
return string.Format("HE-ID {0} (Origin = VID-{1})", id, origin.id);
}
}
}

View File

@ -0,0 +1,68 @@
// -----------------------------------------------------------------------
// <copyright file="Vertex.cs">
// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/
// </copyright>
// -----------------------------------------------------------------------
namespace TriangleNet.Topology.DCEL
{
using System.Collections.Generic;
public class Vertex : TriangleNet.Geometry.Point
{
internal HalfEdge leaving;
/// <summary>
/// Gets or sets a half-edge leaving the vertex.
/// </summary>
public HalfEdge Leaving
{
get { return leaving; }
set { leaving = value; }
}
/// <summary>
/// Initializes a new instance of the <see cref="Vertex" /> class.
/// </summary>
/// <param name="x">The x coordinate.</param>
/// <param name="y">The y coordinate.</param>
public Vertex(double x, double y)
: base(x, y)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="Vertex" /> class.
/// </summary>
/// <param name="x">The x coordinate.</param>
/// <param name="y">The y coordinate.</param>
/// <param name="leaving">A half-edge leaving this vertex.</param>
public Vertex(double x, double y, HalfEdge leaving)
: base(x, y)
{
this.leaving = leaving;
}
/// <summary>
/// Enumerates all half-edges leaving this vertex.
/// </summary>
/// <returns></returns>
public IEnumerable<HalfEdge> EnumerateEdges()
{
var edge = this.Leaving;
int first = edge.ID;
do
{
yield return edge;
edge = edge.Twin.Next;
} while (edge.ID != first);
}
public override string ToString()
{
return string.Format("V-ID {0}", base.id);
}
}
}

View File

@ -0,0 +1,256 @@
// -----------------------------------------------------------------------
// <copyright file="Osub.cs">
// Original Triangle code by Jonathan Richard Shewchuk, http://www.cs.cmu.edu/~quake/triangle.html
// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/
// </copyright>
// -----------------------------------------------------------------------
namespace TriangleNet.Topology
{
using System;
using TriangleNet.Geometry;
/// <summary>
/// An oriented subsegment.
/// </summary>
/// <remarks>
/// Includes a pointer to a subsegment and an orientation. The orientation denotes a
/// side of the edge. Hence, there are two possible orientations. By convention, the
/// edge is always directed so that the "side" denoted is the right side of the edge.
/// </remarks>
public struct Osub
{
internal SubSegment seg;
internal int orient; // Ranges from 0 to 1.
public SubSegment Segment
{
get { return seg; }
}
public override string ToString()
{
if (seg == null)
{
return "O-TID [null]";
}
return String.Format("O-SID {0}", seg.hash);
}
#region Osub primitives
/// <summary>
/// Reverse the orientation of a subsegment. [sym(ab) -> ba]
/// </summary>
public void Sym(ref Osub os)
{
os.seg = seg;
os.orient = 1 - orient;
}
/// <summary>
/// Reverse the orientation of a subsegment. [sym(ab) -> ba]
/// </summary>
public void Sym()
{
orient = 1 - orient;
}
/// <summary>
/// Find adjoining subsegment with the same origin. [pivot(ab) -> a*]
/// </summary>
/// <remarks>spivot() finds the other subsegment (from the same segment)
/// that shares the same origin.
/// </remarks>
public void Pivot(ref Osub os)
{
os = seg.subsegs[orient];
}
/// <summary>
/// Finds a triangle abutting a subsegment.
/// </summary>
internal void Pivot(ref Otri ot)
{
ot = seg.triangles[orient];
}
/// <summary>
/// Find next subsegment in sequence. [next(ab) -> b*]
/// </summary>
public void Next(ref Osub ot)
{
ot = seg.subsegs[1 - orient];
}
/// <summary>
/// Find next subsegment in sequence. [next(ab) -> b*]
/// </summary>
public void Next()
{
this = seg.subsegs[1 - orient];
}
/// <summary>
/// Get the origin of a subsegment
/// </summary>
public Vertex Org()
{
return seg.vertices[orient];
}
/// <summary>
/// Get the destination of a subsegment
/// </summary>
public Vertex Dest()
{
return seg.vertices[1 - orient];
}
#endregion
#region Osub primitives (internal)
/// <summary>
/// Set the origin or destination of a subsegment.
/// </summary>
internal void SetOrg(Vertex vertex)
{
seg.vertices[orient] = vertex;
}
/// <summary>
/// Set destination of a subsegment.
/// </summary>
internal void SetDest(Vertex vertex)
{
seg.vertices[1 - orient] = vertex;
}
/// <summary>
/// Get the origin of the segment that includes the subsegment.
/// </summary>
internal Vertex SegOrg()
{
return seg.vertices[2 + orient];
}
/// <summary>
/// Get the destination of the segment that includes the subsegment.
/// </summary>
internal Vertex SegDest()
{
return seg.vertices[3 - orient];
}
/// <summary>
/// Set the origin of the segment that includes the subsegment.
/// </summary>
internal void SetSegOrg(Vertex vertex)
{
seg.vertices[2 + orient] = vertex;
}
/// <summary>
/// Set the destination of the segment that includes the subsegment.
/// </summary>
internal void SetSegDest(Vertex vertex)
{
seg.vertices[3 - orient] = vertex;
}
/* Unused primitives.
/// <summary>
/// Find adjoining subsegment with the same origin. [pivot(ab) -> a*]
/// </summary>
public void PivotSelf()
{
this = seg.subsegs[orient];
}
/// <summary>
/// Read a boundary marker.
/// </summary>
/// <remarks>Boundary markers are used to hold user-defined tags for
/// setting boundary conditions in finite element solvers.</remarks>
public int Mark()
{
return seg.boundary;
}
/// <summary>
/// Set a boundary marker.
/// </summary>
public void SetMark(int value)
{
seg.boundary = value;
}
/// <summary>
/// Copy a subsegment.
/// </summary>
public void Copy(ref Osub o2)
{
o2.seg = seg;
o2.orient = orient;
}
//*/
/// <summary>
/// Bond two subsegments together. [bond(abc, ba)]
/// </summary>
internal void Bond(ref Osub os)
{
seg.subsegs[orient] = os;
os.seg.subsegs[os.orient] = this;
}
/// <summary>
/// Dissolve a subsegment bond (from one side).
/// </summary>
/// <remarks>Note that the other subsegment will still think it's
/// connected to this subsegment.</remarks>
internal void Dissolve(SubSegment dummy)
{
seg.subsegs[orient].seg = dummy;
}
/// <summary>
/// Test for equality of subsegments.
/// </summary>
internal bool Equal(Osub os)
{
return ((seg == os.seg) && (orient == os.orient));
}
/// <summary>
/// Dissolve a bond (from the subsegment side).
/// </summary>
internal void TriDissolve(Triangle dummy)
{
seg.triangles[orient].tri = dummy;
}
/// <summary>
/// Check a subsegment's deallocation.
/// </summary>
internal static bool IsDead(SubSegment sub)
{
return sub.subsegs[0].seg == null;
}
/// <summary>
/// Set a subsegment's deallocation.
/// </summary>
internal static void Kill(SubSegment sub)
{
sub.subsegs[0].seg = null;
sub.subsegs[1].seg = null;
}
#endregion
}
}

View File

@ -0,0 +1,481 @@
// -----------------------------------------------------------------------
// <copyright file="Otri.cs">
// Original Triangle code by Jonathan Richard Shewchuk, http://www.cs.cmu.edu/~quake/triangle.html
// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/
// </copyright>
// -----------------------------------------------------------------------
namespace TriangleNet.Topology
{
using System;
using TriangleNet.Geometry;
/// <summary>
/// An oriented triangle.
/// </summary>
/// <remarks>
/// Includes a pointer to a triangle and orientation. The orientation denotes an edge
/// of the triangle. Hence, there are three possible orientations. By convention, each
/// edge always points counterclockwise about the corresponding triangle.
/// </remarks>
public struct Otri
{
internal Triangle tri;
internal int orient; // Ranges from 0 to 2.
public Triangle Triangle
{
get { return tri; }
set { tri = value; }
}
public override string ToString()
{
if (tri == null)
{
return "O-TID [null]";
}
return String.Format("O-TID {0}", tri.hash);
}
#region Otri primitives (public)
// For fast access
static readonly int[] plus1Mod3 = { 1, 2, 0 };
static readonly int[] minus1Mod3 = { 2, 0, 1 };
// The following primitives are all described by Guibas and Stolfi.
// However, Guibas and Stolfi use an edge-based data structure,
// whereas I use a triangle-based data structure.
//
// lnext: finds the next edge (counterclockwise) of a triangle.
//
// onext: spins counterclockwise around a vertex; that is, it finds
// the next edge with the same origin in the counterclockwise direction. This
// edge is part of a different triangle.
//
// oprev: spins clockwise around a vertex; that is, it finds the
// next edge with the same origin in the clockwise direction. This edge is
// part of a different triangle.
//
// dnext: spins counterclockwise around a vertex; that is, it finds
// the next edge with the same destination in the counterclockwise direction.
// This edge is part of a different triangle.
//
// dprev: spins clockwise around a vertex; that is, it finds the
// next edge with the same destination in the clockwise direction. This edge
// is part of a different triangle.
//
// rnext: moves one edge counterclockwise about the adjacent
// triangle. (It's best understood by reading Guibas and Stolfi. It
// involves changing triangles twice.)
//
// rprev: moves one edge clockwise about the adjacent triangle.
// (It's best understood by reading Guibas and Stolfi. It involves
// changing triangles twice.)
/// <summary>
/// Find the abutting triangle; same edge. [sym(abc) -> ba*]
/// </summary>
/// Note that the edge direction is necessarily reversed, because the handle specified
/// by an oriented triangle is directed counterclockwise around the triangle.
/// </remarks>
public void Sym(ref Otri ot)
{
ot.tri = tri.neighbors[orient].tri;
ot.orient = tri.neighbors[orient].orient;
}
/// <summary>
/// Find the abutting triangle; same edge. [sym(abc) -> ba*]
/// </summary>
public void Sym()
{
int tmp = orient;
orient = tri.neighbors[tmp].orient;
tri = tri.neighbors[tmp].tri;
}
/// <summary>
/// Find the next edge (counterclockwise) of a triangle. [lnext(abc) -> bca]
/// </summary>
public void Lnext(ref Otri ot)
{
ot.tri = tri;
ot.orient = plus1Mod3[orient];
}
/// <summary>
/// Find the next edge (counterclockwise) of a triangle. [lnext(abc) -> bca]
/// </summary>
public void Lnext()
{
orient = plus1Mod3[orient];
}
/// <summary>
/// Find the previous edge (clockwise) of a triangle. [lprev(abc) -> cab]
/// </summary>
public void Lprev(ref Otri ot)
{
ot.tri = tri;
ot.orient = minus1Mod3[orient];
}
/// <summary>
/// Find the previous edge (clockwise) of a triangle. [lprev(abc) -> cab]
/// </summary>
public void Lprev()
{
orient = minus1Mod3[orient];
}
/// <summary>
/// Find the next edge counterclockwise with the same origin. [onext(abc) -> ac*]
/// </summary>
public void Onext(ref Otri ot)
{
//Lprev(ref ot);
ot.tri = tri;
ot.orient = minus1Mod3[orient];
//ot.SymSelf();
int tmp = ot.orient;
ot.orient = ot.tri.neighbors[tmp].orient;
ot.tri = ot.tri.neighbors[tmp].tri;
}
/// <summary>
/// Find the next edge counterclockwise with the same origin. [onext(abc) -> ac*]
/// </summary>
public void Onext()
{
//LprevSelf();
orient = minus1Mod3[orient];
//SymSelf();
int tmp = orient;
orient = tri.neighbors[tmp].orient;
tri = tri.neighbors[tmp].tri;
}
/// <summary>
/// Find the next edge clockwise with the same origin. [oprev(abc) -> a*b]
/// </summary>
public void Oprev(ref Otri ot)
{
//Sym(ref ot);
ot.tri = tri.neighbors[orient].tri;
ot.orient = tri.neighbors[orient].orient;
//ot.LnextSelf();
ot.orient = plus1Mod3[ot.orient];
}
/// <summary>
/// Find the next edge clockwise with the same origin. [oprev(abc) -> a*b]
/// </summary>
public void Oprev()
{
//SymSelf();
int tmp = orient;
orient = tri.neighbors[tmp].orient;
tri = tri.neighbors[tmp].tri;
//LnextSelf();
orient = plus1Mod3[orient];
}
/// <summary>
/// Find the next edge counterclockwise with the same destination. [dnext(abc) -> *ba]
/// </summary>
public void Dnext(ref Otri ot)
{
//Sym(ref ot);
ot.tri = tri.neighbors[orient].tri;
ot.orient = tri.neighbors[orient].orient;
//ot.LprevSelf();
ot.orient = minus1Mod3[ot.orient];
}
/// <summary>
/// Find the next edge counterclockwise with the same destination. [dnext(abc) -> *ba]
/// </summary>
public void Dnext()
{
//SymSelf();
int tmp = orient;
orient = tri.neighbors[tmp].orient;
tri = tri.neighbors[tmp].tri;
//LprevSelf();
orient = minus1Mod3[orient];
}
/// <summary>
/// Find the next edge clockwise with the same destination. [dprev(abc) -> cb*]
/// </summary>
public void Dprev(ref Otri ot)
{
//Lnext(ref ot);
ot.tri = tri;
ot.orient = plus1Mod3[orient];
//ot.SymSelf();
int tmp = ot.orient;
ot.orient = ot.tri.neighbors[tmp].orient;
ot.tri = ot.tri.neighbors[tmp].tri;
}
/// <summary>
/// Find the next edge clockwise with the same destination. [dprev(abc) -> cb*]
/// </summary>
public void Dprev()
{
//LnextSelf();
orient = plus1Mod3[orient];
//SymSelf();
int tmp = orient;
orient = tri.neighbors[tmp].orient;
tri = tri.neighbors[tmp].tri;
}
/// <summary>
/// Find the next edge (counterclockwise) of the adjacent triangle. [rnext(abc) -> *a*]
/// </summary>
public void Rnext(ref Otri ot)
{
//Sym(ref ot);
ot.tri = tri.neighbors[orient].tri;
ot.orient = tri.neighbors[orient].orient;
//ot.LnextSelf();
ot.orient = plus1Mod3[ot.orient];
//ot.SymSelf();
int tmp = ot.orient;
ot.orient = ot.tri.neighbors[tmp].orient;
ot.tri = ot.tri.neighbors[tmp].tri;
}
/// <summary>
/// Find the next edge (counterclockwise) of the adjacent triangle. [rnext(abc) -> *a*]
/// </summary>
public void Rnext()
{
//SymSelf();
int tmp = orient;
orient = tri.neighbors[tmp].orient;
tri = tri.neighbors[tmp].tri;
//LnextSelf();
orient = plus1Mod3[orient];
//SymSelf();
tmp = orient;
orient = tri.neighbors[tmp].orient;
tri = tri.neighbors[tmp].tri;
}
/// <summary>
/// Find the previous edge (clockwise) of the adjacent triangle. [rprev(abc) -> b**]
/// </summary>
public void Rprev(ref Otri ot)
{
//Sym(ref ot);
ot.tri = tri.neighbors[orient].tri;
ot.orient = tri.neighbors[orient].orient;
//ot.LprevSelf();
ot.orient = minus1Mod3[ot.orient];
//ot.SymSelf();
int tmp = ot.orient;
ot.orient = ot.tri.neighbors[tmp].orient;
ot.tri = ot.tri.neighbors[tmp].tri;
}
/// <summary>
/// Find the previous edge (clockwise) of the adjacent triangle. [rprev(abc) -> b**]
/// </summary>
public void Rprev()
{
//SymSelf();
int tmp = orient;
orient = tri.neighbors[tmp].orient;
tri = tri.neighbors[tmp].tri;
//LprevSelf();
orient = minus1Mod3[orient];
//SymSelf();
tmp = orient;
orient = tri.neighbors[tmp].orient;
tri = tri.neighbors[tmp].tri;
}
/// <summary>
/// Origin [org(abc) -> a]
/// </summary>
public Vertex Org()
{
return tri.vertices[plus1Mod3[orient]];
}
/// <summary>
/// Destination [dest(abc) -> b]
/// </summary>
public Vertex Dest()
{
return tri.vertices[minus1Mod3[orient]];
}
/// <summary>
/// Apex [apex(abc) -> c]
/// </summary>
public Vertex Apex()
{
return tri.vertices[orient];
}
/// <summary>
/// Copy an oriented triangle.
/// </summary>
public void Copy(ref Otri ot)
{
ot.tri = tri;
ot.orient = orient;
}
/// <summary>
/// Test for equality of oriented triangles.
/// </summary>
public bool Equals(Otri ot)
{
return ((tri == ot.tri) && (orient == ot.orient));
}
#endregion
#region Otri primitives (internal)
/// <summary>
/// Set Origin
/// </summary>
internal void SetOrg(Vertex v)
{
tri.vertices[plus1Mod3[orient]] = v;
}
/// <summary>
/// Set Destination
/// </summary>
internal void SetDest(Vertex v)
{
tri.vertices[minus1Mod3[orient]] = v;
}
/// <summary>
/// Set Apex
/// </summary>
internal void SetApex(Vertex v)
{
tri.vertices[orient] = v;
}
/// <summary>
/// Bond two triangles together at the resepective handles. [bond(abc, bad)]
/// </summary>
internal void Bond(ref Otri ot)
{
tri.neighbors[orient].tri = ot.tri;
tri.neighbors[orient].orient = ot.orient;
ot.tri.neighbors[ot.orient].tri = this.tri;
ot.tri.neighbors[ot.orient].orient = this.orient;
}
/// <summary>
/// Dissolve a bond (from one side).
/// </summary>
/// <remarks>Note that the other triangle will still think it's connected to
/// this triangle. Usually, however, the other triangle is being deleted
/// entirely, or bonded to another triangle, so it doesn't matter.
/// </remarks>
internal void Dissolve(Triangle dummy)
{
tri.neighbors[orient].tri = dummy;
tri.neighbors[orient].orient = 0;
}
/// <summary>
/// Infect a triangle with the virus.
/// </summary>
internal void Infect()
{
tri.infected = true;
}
/// <summary>
/// Cure a triangle from the virus.
/// </summary>
internal void Uninfect()
{
tri.infected = false;
}
/// <summary>
/// Test a triangle for viral infection.
/// </summary>
internal bool IsInfected()
{
return tri.infected;
}
/// <summary>
/// Finds a subsegment abutting a triangle.
/// </summary>
internal void Pivot(ref Osub os)
{
os = tri.subsegs[orient];
}
/// <summary>
/// Bond a triangle to a subsegment.
/// </summary>
internal void SegBond(ref Osub os)
{
tri.subsegs[orient] = os;
os.seg.triangles[os.orient] = this;
}
/// <summary>
/// Dissolve a bond (from the triangle side).
/// </summary>
internal void SegDissolve(SubSegment dummy)
{
tri.subsegs[orient].seg = dummy;
}
/// <summary>
/// Check a triangle's deallocation.
/// </summary>
internal static bool IsDead(Triangle tria)
{
return tria.neighbors[0].tri == null;
}
/// <summary>
/// Set a triangle's deallocation.
/// </summary>
internal static void Kill(Triangle tri)
{
tri.neighbors[0].tri = null;
tri.neighbors[2].tri = null;
}
#endregion
}
}

View File

@ -0,0 +1,96 @@
// -----------------------------------------------------------------------
// <copyright file="Segment.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 TriangleNet.Topology
{
using System;
using TriangleNet.Geometry;
/// <summary>
/// The subsegment data structure.
/// </summary>
public class SubSegment : ISegment
{
// Hash for dictionary. Will be set by mesh instance.
internal int hash;
internal Osub[] subsegs;
internal Vertex[] vertices;
internal Otri[] triangles;
internal int boundary;
public SubSegment()
{
// Four NULL vertices.
vertices = new Vertex[4];
// Set the boundary marker to zero.
boundary = 0;
// Initialize the two adjoining subsegments to be the omnipresent
// subsegment.
subsegs = new Osub[2];
// Initialize the two adjoining triangles to be "outer space."
triangles = new Otri[2];
}
#region Public properties
/// <summary>
/// Gets the first endpoints vertex id.
/// </summary>
public int P0
{
get { return this.vertices[0].id; }
}
/// <summary>
/// Gets the seconds endpoints vertex id.
/// </summary>
public int P1
{
get { return this.vertices[1].id; }
}
/// <summary>
/// Gets the segment boundary mark.
/// </summary>
public int Label
{
get { return this.boundary; }
}
#endregion
/// <summary>
/// Gets the segments endpoint.
/// </summary>
public Vertex GetVertex(int index)
{
return this.vertices[index]; // TODO: Check range?
}
/// <summary>
/// Gets an adjoining triangle.
/// </summary>
public ITriangle GetTriangle(int index)
{
return triangles[index].tri.hash == Mesh.DUMMY ? null : triangles[index].tri;
}
public override int GetHashCode()
{
return this.hash;
}
public override string ToString()
{
return String.Format("SID {0}", hash);
}
}
}

View File

@ -0,0 +1,128 @@
// -----------------------------------------------------------------------
// <copyright file="Triangle.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 TriangleNet.Topology
{
using System;
using TriangleNet.Geometry;
/// <summary>
/// The triangle data structure.
/// </summary>
public class Triangle : ITriangle
{
// Hash for dictionary. Will be set by mesh instance.
internal int hash;
// The ID is only used for mesh output.
internal int id;
internal Otri[] neighbors;
internal Vertex[] vertices;
internal Osub[] subsegs;
internal int label;
internal double area;
internal bool infected;
/// <summary>
/// Initializes a new instance of the <see cref="Triangle" /> class.
/// </summary>
public Triangle()
{
// Three NULL vertices.
vertices = new Vertex[3];
// Initialize the three adjoining subsegments to be the omnipresent subsegment.
subsegs = new Osub[3];
// Initialize the three adjoining triangles to be "outer space".
neighbors = new Otri[3];
// area = -1.0;
}
#region Public properties
/// <summary>
/// Gets or sets the triangle id.
/// </summary>
public int ID
{
get { return this.id; }
set { this.id = value; }
}
/// <summary>
/// Region ID the triangle belongs to.
/// </summary>
public int Label
{
get { return this.label; }
set { this.label = value; }
}
/// <summary>
/// Gets the triangle area constraint.
/// </summary>
public double Area
{
get { return this.area; }
set { this.area = value; }
}
/// <summary>
/// Gets the specified corners vertex.
/// </summary>
public Vertex GetVertex(int index)
{
return this.vertices[index]; // TODO: Check range?
}
public int GetVertexID(int index)
{
return this.vertices[index].id;
}
/// <summary>
/// Gets a triangles' neighbor.
/// </summary>
/// <param name="index">The neighbor index (0, 1 or 2).</param>
/// <returns>The neigbbor opposite of vertex with given index.</returns>
public ITriangle GetNeighbor(int index)
{
return neighbors[index].tri.hash == Mesh.DUMMY ? null : neighbors[index].tri;
}
/// <inheritdoc />
public int GetNeighborID(int index)
{
return neighbors[index].tri.hash == Mesh.DUMMY ? -1 : neighbors[index].tri.id;
}
/// <summary>
/// Gets a triangles segment.
/// </summary>
/// <param name="index">The vertex index (0, 1 or 2).</param>
/// <returns>The segment opposite of vertex with given index.</returns>
public ISegment GetSegment(int index)
{
return subsegs[index].seg.hash == Mesh.DUMMY ? null : subsegs[index].seg;
}
#endregion
public override int GetHashCode()
{
return this.hash;
}
public override string ToString()
{
return String.Format("TID {0}", hash);
}
}
}

View File

@ -0,0 +1,363 @@
// -----------------------------------------------------------------------
// <copyright file="TriangleLocator.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 TriangleNet
{
using TriangleNet.Geometry;
using TriangleNet.Topology;
/// <summary>
/// Locate triangles in a mesh.
/// </summary>
/// <remarks>
/// WARNING: This routine is designed for convex triangulations, and will
/// not generally work after the holes and concavities have been carved.
///
/// Based on a paper by Ernst P. Mucke, Isaac Saias, and Binhai Zhu, "Fast
/// Randomized Point Location Without Preprocessing in Two- and Three-Dimensional
/// Delaunay Triangulations," Proceedings of the Twelfth Annual Symposium on
/// Computational Geometry, ACM, May 1996.
/// </remarks>
public class TriangleLocator
{
TriangleSampler sampler;
Mesh mesh;
IPredicates predicates;
// Pointer to a recently visited triangle. Improves point location if
// proximate vertices are inserted sequentially.
internal Otri recenttri;
public TriangleLocator(Mesh mesh)
: this(mesh, RobustPredicates.Default)
{
}
public TriangleLocator(Mesh mesh, IPredicates predicates)
{
this.mesh = mesh;
this.predicates = predicates;
sampler = new TriangleSampler(mesh);
}
/// <summary>
/// Suggest the given triangle as a starting triangle for point location.
/// </summary>
/// <param name="otri"></param>
public void Update(ref Otri otri)
{
otri.Copy(ref recenttri);
}
public void Reset()
{
sampler.Reset();
recenttri.tri = null; // No triangle has been visited yet.
}
/// <summary>
/// Find a triangle or edge containing a given point.
/// </summary>
/// <param name="searchpoint">The point to locate.</param>
/// <param name="searchtri">The triangle to start the search at.</param>
/// <param name="stopatsubsegment"> If 'stopatsubsegment' is set, the search
/// will stop if it tries to walk through a subsegment, and will return OUTSIDE.</param>
/// <returns>Location information.</returns>
/// <remarks>
/// Begins its search from 'searchtri'. It is important that 'searchtri'
/// be a handle with the property that 'searchpoint' is strictly to the left
/// of the edge denoted by 'searchtri', or is collinear with that edge and
/// does not intersect that edge. (In particular, 'searchpoint' should not
/// be the origin or destination of that edge.)
///
/// These conditions are imposed because preciselocate() is normally used in
/// one of two situations:
///
/// (1) To try to find the location to insert a new point. Normally, we
/// know an edge that the point is strictly to the left of. In the
/// incremental Delaunay algorithm, that edge is a bounding box edge.
/// In Ruppert's Delaunay refinement algorithm for quality meshing,
/// that edge is the shortest edge of the triangle whose circumcenter
/// is being inserted.
///
/// (2) To try to find an existing point. In this case, any edge on the
/// convex hull is a good starting edge. You must screen out the
/// possibility that the vertex sought is an endpoint of the starting
/// edge before you call preciselocate().
///
/// On completion, 'searchtri' is a triangle that contains 'searchpoint'.
///
/// This implementation differs from that given by Guibas and Stolfi. It
/// walks from triangle to triangle, crossing an edge only if 'searchpoint'
/// is on the other side of the line containing that edge. After entering
/// a triangle, there are two edges by which one can leave that triangle.
/// If both edges are valid ('searchpoint' is on the other side of both
/// edges), one of the two is chosen by drawing a line perpendicular to
/// the entry edge (whose endpoints are 'forg' and 'fdest') passing through
/// 'fapex'. Depending on which side of this perpendicular 'searchpoint'
/// falls on, an exit edge is chosen.
///
/// This implementation is empirically faster than the Guibas and Stolfi
/// point location routine (which I originally used), which tends to spiral
/// in toward its target.
///
/// Returns ONVERTEX if the point lies on an existing vertex. 'searchtri'
/// is a handle whose origin is the existing vertex.
///
/// Returns ONEDGE if the point lies on a mesh edge. 'searchtri' is a
/// handle whose primary edge is the edge on which the point lies.
///
/// Returns INTRIANGLE if the point lies strictly within a triangle.
/// 'searchtri' is a handle on the triangle that contains the point.
///
/// Returns OUTSIDE if the point lies outside the mesh. 'searchtri' is a
/// handle whose primary edge the point is to the right of. This might
/// occur when the circumcenter of a triangle falls just slightly outside
/// the mesh due to floating-point roundoff error. It also occurs when
/// seeking a hole or region point that a foolish user has placed outside
/// the mesh.
///
/// WARNING: This routine is designed for convex triangulations, and will
/// not generally work after the holes and concavities have been carved.
/// However, it can still be used to find the circumcenter of a triangle, as
/// long as the search is begun from the triangle in question.</remarks>
public LocateResult PreciseLocate(Point searchpoint, ref Otri searchtri,
bool stopatsubsegment)
{
Otri backtracktri = default(Otri);
Osub checkedge = default(Osub);
Vertex forg, fdest, fapex;
double orgorient, destorient;
bool moveleft;
// Where are we?
forg = searchtri.Org();
fdest = searchtri.Dest();
fapex = searchtri.Apex();
while (true)
{
// Check whether the apex is the point we seek.
if ((fapex.x == searchpoint.x) && (fapex.y == searchpoint.y))
{
searchtri.Lprev();
return LocateResult.OnVertex;
}
// Does the point lie on the other side of the line defined by the
// triangle edge opposite the triangle's destination?
destorient = predicates.CounterClockwise(forg, fapex, searchpoint);
// Does the point lie on the other side of the line defined by the
// triangle edge opposite the triangle's origin?
orgorient = predicates.CounterClockwise(fapex, fdest, searchpoint);
if (destorient > 0.0)
{
if (orgorient > 0.0)
{
// Move left if the inner product of (fapex - searchpoint) and
// (fdest - forg) is positive. This is equivalent to drawing
// a line perpendicular to the line (forg, fdest) and passing
// through 'fapex', and determining which side of this line
// 'searchpoint' falls on.
moveleft = (fapex.x - searchpoint.x) * (fdest.x - forg.x) +
(fapex.y - searchpoint.y) * (fdest.y - forg.y) > 0.0;
}
else
{
moveleft = true;
}
}
else
{
if (orgorient > 0.0)
{
moveleft = false;
}
else
{
// The point we seek must be on the boundary of or inside this
// triangle.
if (destorient == 0.0)
{
searchtri.Lprev();
return LocateResult.OnEdge;
}
if (orgorient == 0.0)
{
searchtri.Lnext();
return LocateResult.OnEdge;
}
return LocateResult.InTriangle;
}
}
// Move to another triangle. Leave a trace 'backtracktri' in case
// floating-point roundoff or some such bogey causes us to walk
// off a boundary of the triangulation.
if (moveleft)
{
searchtri.Lprev(ref backtracktri);
fdest = fapex;
}
else
{
searchtri.Lnext(ref backtracktri);
forg = fapex;
}
backtracktri.Sym(ref searchtri);
if (mesh.checksegments && stopatsubsegment)
{
// Check for walking through a subsegment.
backtracktri.Pivot(ref checkedge);
if (checkedge.seg.hash != Mesh.DUMMY)
{
// Go back to the last triangle.
backtracktri.Copy(ref searchtri);
return LocateResult.Outside;
}
}
// Check for walking right out of the triangulation.
if (searchtri.tri.id == Mesh.DUMMY)
{
// Go back to the last triangle.
backtracktri.Copy(ref searchtri);
return LocateResult.Outside;
}
fapex = searchtri.Apex();
}
}
/// <summary>
/// Find a triangle or edge containing a given point.
/// </summary>
/// <param name="searchpoint">The point to locate.</param>
/// <param name="searchtri">The triangle to start the search at.</param>
/// <returns>Location information.</returns>
/// <remarks>
/// Searching begins from one of: the input 'searchtri', a recently
/// encountered triangle 'recenttri', or from a triangle chosen from a
/// random sample. The choice is made by determining which triangle's
/// origin is closest to the point we are searching for. Normally,
/// 'searchtri' should be a handle on the convex hull of the triangulation.
///
/// Details on the random sampling method can be found in the Mucke, Saias,
/// and Zhu paper cited in the header of this code.
///
/// On completion, 'searchtri' is a triangle that contains 'searchpoint'.
///
/// Returns ONVERTEX if the point lies on an existing vertex. 'searchtri'
/// is a handle whose origin is the existing vertex.
///
/// Returns ONEDGE if the point lies on a mesh edge. 'searchtri' is a
/// handle whose primary edge is the edge on which the point lies.
///
/// Returns INTRIANGLE if the point lies strictly within a triangle.
/// 'searchtri' is a handle on the triangle that contains the point.
///
/// Returns OUTSIDE if the point lies outside the mesh. 'searchtri' is a
/// handle whose primary edge the point is to the right of. This might
/// occur when the circumcenter of a triangle falls just slightly outside
/// the mesh due to floating-point roundoff error. It also occurs when
/// seeking a hole or region point that a foolish user has placed outside
/// the mesh.
///
/// WARNING: This routine is designed for convex triangulations, and will
/// not generally work after the holes and concavities have been carved.
/// </remarks>
public LocateResult Locate(Point searchpoint, ref Otri searchtri)
{
Otri sampletri = default(Otri);
Vertex torg, tdest;
double searchdist, dist;
double ahead;
// Record the distance from the suggested starting triangle to the
// point we seek.
torg = searchtri.Org();
searchdist = (searchpoint.x - torg.x) * (searchpoint.x - torg.x) +
(searchpoint.y - torg.y) * (searchpoint.y - torg.y);
// If a recently encountered triangle has been recorded and has not been
// deallocated, test it as a good starting point.
if (recenttri.tri != null)
{
if (!Otri.IsDead(recenttri.tri))
{
torg = recenttri.Org();
if ((torg.x == searchpoint.x) && (torg.y == searchpoint.y))
{
recenttri.Copy(ref searchtri);
return LocateResult.OnVertex;
}
dist = (searchpoint.x - torg.x) * (searchpoint.x - torg.x) +
(searchpoint.y - torg.y) * (searchpoint.y - torg.y);
if (dist < searchdist)
{
recenttri.Copy(ref searchtri);
searchdist = dist;
}
}
}
// TODO: Improve sampling.
sampler.Update();
foreach (var t in sampler)
{
sampletri.tri = t;
if (!Otri.IsDead(sampletri.tri))
{
torg = sampletri.Org();
dist = (searchpoint.x - torg.x) * (searchpoint.x - torg.x) +
(searchpoint.y - torg.y) * (searchpoint.y - torg.y);
if (dist < searchdist)
{
sampletri.Copy(ref searchtri);
searchdist = dist;
}
}
}
// Where are we?
torg = searchtri.Org();
tdest = searchtri.Dest();
// Check the starting triangle's vertices.
if ((torg.x == searchpoint.x) && (torg.y == searchpoint.y))
{
return LocateResult.OnVertex;
}
if ((tdest.x == searchpoint.x) && (tdest.y == searchpoint.y))
{
searchtri.Lnext();
return LocateResult.OnVertex;
}
// Orient 'searchtri' to fit the preconditions of calling preciselocate().
ahead = predicates.CounterClockwise(torg, tdest, searchpoint);
if (ahead < 0.0)
{
// Turn around so that 'searchpoint' is to the left of the
// edge specified by 'searchtri'.
searchtri.Sym();
}
else if (ahead == 0.0)
{
// Check if 'searchpoint' is between 'torg' and 'tdest'.
if (((torg.x < searchpoint.x) == (searchpoint.x < tdest.x)) &&
((torg.y < searchpoint.y) == (searchpoint.y < tdest.y)))
{
return LocateResult.OnEdge;
}
}
return PreciseLocate(searchpoint, ref searchtri, false);
}
}
}

View File

@ -0,0 +1,305 @@
// -----------------------------------------------------------------------
// <copyright file="TrianglePool.cs" company="">
// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/
// </copyright>
// -----------------------------------------------------------------------
namespace TriangleNet
{
using System;
using System.Collections.Generic;
using TriangleNet.Geometry;
using TriangleNet.Topology;
public class TrianglePool : ICollection<Triangle>
{
// Determines the size of each block in the pool.
private const int BLOCKSIZE = 1024;
// The total number of currently allocated triangles.
int size;
// The number of triangles currently used.
int count;
// The pool.
Triangle[][] pool;
// A stack of free triangles.
Stack<Triangle> stack;
public TrianglePool()
{
size = 0;
// On startup, the pool should be able to hold 2^16 triangles.
int n = Math.Max(1, 65536 / BLOCKSIZE);
pool = new Triangle[n][];
pool[0] = new Triangle[BLOCKSIZE];
stack = new Stack<Triangle>(BLOCKSIZE);
}
/// <summary>
/// Gets a triangle from the pool.
/// </summary>
/// <returns></returns>
public Triangle Get()
{
Triangle triangle;
if (stack.Count > 0)
{
triangle = stack.Pop();
triangle.hash = -triangle.hash - 1;
Cleanup(triangle);
}
else if (count < size)
{
triangle = pool[count / BLOCKSIZE][count % BLOCKSIZE];
triangle.id = triangle.hash;
Cleanup(triangle);
count++;
}
else
{
triangle = new Triangle();
triangle.hash = size;
triangle.id = triangle.hash;
int block = size / BLOCKSIZE;
if (pool[block] == null)
{
pool[block] = new Triangle[BLOCKSIZE];
// Check if the pool has to be resized.
if (block + 1 == pool.Length)
{
Array.Resize(ref pool, 2 * pool.Length);
}
}
// Add triangle to pool.
pool[block][size % BLOCKSIZE] = triangle;
count = ++size;
}
return triangle;
}
public void Release(Triangle triangle)
{
stack.Push(triangle);
// Mark the triangle as free (used by enumerator).
triangle.hash = -triangle.hash - 1;
}
/// <summary>
/// Restart the triangle pool.
/// </summary>
public TrianglePool Restart()
{
foreach (var triangle in stack)
{
// Reset hash to original value.
triangle.hash = -triangle.hash - 1;
}
stack.Clear();
count = 0;
return this;
}
/// <summary>
/// Samples a number of triangles from the pool.
/// </summary>
/// <param name="k">The number of triangles to sample.</param>
/// <param name="random"></param>
/// <returns></returns>
internal IEnumerable<Triangle> Sample(int k, Random random)
{
int i, count = this.Count;
if (k > count)
{
// TODO: handle Sample special case.
k = count;
}
Triangle t;
// TODO: improve sampling code (to ensure no duplicates).
while (k > 0)
{
i = random.Next(0, count);
t = pool[i / BLOCKSIZE][i % BLOCKSIZE];
if (t.hash >= 0)
{
k--;
yield return t;
}
}
}
private void Cleanup(Triangle triangle)
{
triangle.label = 0;
triangle.area = 0.0;
triangle.infected = false;
for (int i = 0; i < 3; i++)
{
triangle.vertices[i] = null;
triangle.subsegs[i] = default(Osub);
triangle.neighbors[i] = default(Otri);
}
}
public void Add(Triangle item)
{
throw new NotImplementedException();
}
public void Clear()
{
stack.Clear();
int blocks = (size / BLOCKSIZE) + 1;
for (int i = 0; i < blocks; i++)
{
var block = pool[i];
// Number of triangles in current block:
int length = (size - i * BLOCKSIZE) % BLOCKSIZE;
for (int j = 0; j < length; j++)
{
block[j] = null;
}
}
size = count = 0;
}
public bool Contains(Triangle item)
{
int i = item.hash;
if (i < 0 || i > size)
{
return false;
}
return pool[i / BLOCKSIZE][i % BLOCKSIZE].hash >= 0;
}
public void CopyTo(Triangle[] array, int index)
{
var enumerator = GetEnumerator();
while (enumerator.MoveNext())
{
array[index] = enumerator.Current;
index++;
}
}
public int Count
{
get { return count - stack.Count; }
}
public bool IsReadOnly
{
get { return true; }
}
public bool Remove(Triangle item)
{
throw new NotImplementedException();
}
public IEnumerator<Triangle> GetEnumerator()
{
return new Enumerator(this);
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
class Enumerator : IEnumerator<Triangle>
{
// TODO: enumerator should be able to tell if collection changed.
int count;
Triangle[][] pool;
Triangle current;
int index, offset;
public Enumerator(TrianglePool pool)
{
this.count = pool.Count;
this.pool = pool.pool;
index = 0;
offset = 0;
}
public Triangle Current
{
get { return current; }
}
public void Dispose()
{
}
object System.Collections.IEnumerator.Current
{
get { return current; }
}
public bool MoveNext()
{
while (index < count)
{
current = pool[offset / BLOCKSIZE][offset % BLOCKSIZE];
offset++;
if (current.hash >= 0)
{
index++;
return true;
}
}
return false;
}
public void Reset()
{
index = offset = 0;
}
}
}
}

View File

@ -0,0 +1,85 @@
// -----------------------------------------------------------------------
// <copyright file="TriangleSampler.cs">
// Original Triangle code by Jonathan Richard Shewchuk, http://www.cs.cmu.edu/~quake/triangle.html
// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/
// </copyright>
// -----------------------------------------------------------------------
namespace TriangleNet
{
using System;
using System.Collections.Generic;
using TriangleNet.Topology;
/// <summary>
/// Used for triangle sampling in the <see cref="TriangleLocator"/> class.
/// </summary>
class TriangleSampler : IEnumerable<Triangle>
{
private const int RANDOM_SEED = 110503;
// Empirically chosen factor.
private const int samplefactor = 11;
private Random random;
private Mesh mesh;
// Number of random samples for point location (at least 1).
private int samples = 1;
// Number of triangles in mesh.
private int triangleCount = 0;
public TriangleSampler(Mesh mesh)
: this(mesh, new Random(RANDOM_SEED))
{
}
public TriangleSampler(Mesh mesh, Random random)
{
this.mesh = mesh;
this.random = random;
}
/// <summary>
/// Reset the sampler.
/// </summary>
public void Reset()
{
this.samples = 1;
this.triangleCount = 0;
}
/// <summary>
/// Update sampling parameters if mesh changed.
/// </summary>
public void Update()
{
int count = mesh.triangles.Count;
if (triangleCount != count)
{
triangleCount = count;
// The number of random samples taken is proportional to the cube root
// of the number of triangles in the mesh. The next bit of code assumes
// that the number of triangles increases monotonically (or at least
// doesn't decrease enough to matter).
while (samplefactor * samples * samples * samples < count)
{
samples++;
}
}
}
public IEnumerator<Triangle> GetEnumerator()
{
return mesh.triangles.Sample(samples, random).GetEnumerator();
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
}

View File

@ -0,0 +1,182 @@
// -----------------------------------------------------------------------
// <copyright file="BoundedVoronoi.cs">
// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/
// </copyright>
// -----------------------------------------------------------------------
namespace TriangleNet.Voronoi
{
using System.Collections.Generic;
using TriangleNet.Geometry;
using TriangleNet.Tools;
using TriangleNet.Topology.DCEL;
using HVertex = TriangleNet.Topology.DCEL.Vertex;
using TVertex = TriangleNet.Geometry.Vertex;
public class BoundedVoronoi : VoronoiBase
{
int offset;
public BoundedVoronoi(Mesh mesh)
: this(mesh, new DefaultVoronoiFactory(), RobustPredicates.Default)
{
}
public BoundedVoronoi(Mesh mesh, IVoronoiFactory factory, IPredicates predicates)
: base(mesh, factory, predicates, true)
{
// We explicitly told the base constructor to call the Generate method, so
// at this point the basic Voronoi diagram is already created.
offset = base.vertices.Count;
// Each vertex of the hull will be part of a Voronoi cell.
base.vertices.Capacity = offset + mesh.hullsize;
// Create bounded Voronoi diagram.
PostProcess();
ResolveBoundaryEdges();
}
/// <summary>
/// Computes edge intersections with mesh boundary edges.
/// </summary>
private void PostProcess()
{
foreach (var edge in rays)
{
var twin = edge.twin;
var v1 = (TVertex)edge.face.generator;
var v2 = (TVertex)twin.face.generator;
double dir = predicates.CounterClockwise(v1, v2, edge.origin);
if (dir <= 0)
{
HandleCase1(edge, v1, v2);
}
else
{
HandleCase2(edge, v1, v2);
}
}
}
/// <summary>
/// Case 1: edge origin lies inside the domain.
/// </summary>
private void HandleCase1(HalfEdge edge, TVertex v1, TVertex v2)
{
//int mark = GetBoundaryMark(v1);
// The infinite vertex.
var v = (Point)edge.twin.origin;
// The half-edge is the bisector of v1 and v2, so the projection onto the
// boundary segment is actually its midpoint.
v.x = (v1.x + v2.x) / 2.0;
v.y = (v1.y + v2.y) / 2.0;
// Close the cell connected to edge.
var gen = factory.CreateVertex(v1.x, v1.y);
var h1 = factory.CreateHalfEdge(edge.twin.origin, edge.face);
var h2 = factory.CreateHalfEdge(gen, edge.face);
edge.next = h1;
h1.next = h2;
h2.next = edge.face.edge;
gen.leaving = h2;
// Let the face edge point to the edge leaving at generator.
edge.face.edge = h2;
base.edges.Add(h1);
base.edges.Add(h2);
int count = base.edges.Count;
h1.id = count;
h2.id = count + 1;
gen.id = offset++;
base.vertices.Add(gen);
}
/// <summary>
/// Case 2: edge origin lies outside the domain.
/// </summary>
private void HandleCase2(HalfEdge edge, TVertex v1, TVertex v2)
{
// The vertices of the infinite edge.
var p1 = (Point)edge.origin;
var p2 = (Point)edge.twin.origin;
// The two edges leaving p1, pointing into the mesh.
var e1 = edge.twin.next;
var e2 = e1.twin.next;
// Find the two intersections with boundary edge.
IntersectionHelper.IntersectSegments(v1, v2, e1.origin, e1.twin.origin, ref p2);
IntersectionHelper.IntersectSegments(v1, v2, e2.origin, e2.twin.origin, ref p1);
// The infinite edge will now lie on the boundary. Update pointers:
e1.twin.next = edge.twin;
edge.twin.next = e2;
edge.twin.face = e2.face;
e1.origin = edge.twin.origin;
edge.twin.twin = null;
edge.twin = null;
// Close the cell.
var gen = factory.CreateVertex(v1.x, v1.y);
var he = factory.CreateHalfEdge(gen, edge.face);
edge.next = he;
he.next = edge.face.edge;
// Let the face edge point to the edge leaving at generator.
edge.face.edge = he;
base.edges.Add(he);
he.id = base.edges.Count;
gen.id = offset++;
base.vertices.Add(gen);
}
/*
private int GetBoundaryMark(Vertex v)
{
Otri tri = default(Otri);
Otri next = default(Otri);
Osub seg = default(Osub);
// Get triangle connected to generator.
v.tri.Copy(ref tri);
v.tri.Copy(ref next);
// Find boundary triangle.
while (next.triangle.id != -1)
{
next.Copy(ref tri);
next.OnextSelf();
}
// Find edge dual to current half-edge.
tri.LnextSelf();
tri.LnextSelf();
tri.SegPivot(ref seg);
return seg.seg.boundary;
}
//*/
}
}

View File

@ -0,0 +1,35 @@

namespace TriangleNet.Voronoi
{
using System;
using TriangleNet.Topology.DCEL;
/// <summary>
/// Default factory for Voronoi / DCEL mesh objects.
/// </summary>
public class DefaultVoronoiFactory : IVoronoiFactory
{
public void Initialize(int vertexCount, int edgeCount, int faceCount)
{
}
public void Reset()
{
}
public Vertex CreateVertex(double x, double y)
{
return new Vertex(x, y);
}
public HalfEdge CreateHalfEdge(Vertex origin, Face face)
{
return new HalfEdge(origin, face);
}
public Face CreateFace(Geometry.Vertex vertex)
{
return new Face(vertex);
}
}
}

View File

@ -0,0 +1,18 @@

namespace TriangleNet.Voronoi
{
using TriangleNet.Topology.DCEL;
public interface IVoronoiFactory
{
void Initialize(int vertexCount, int edgeCount, int faceCount);
void Reset();
Vertex CreateVertex(double x, double y);
HalfEdge CreateHalfEdge(Vertex origin, Face face);
Face CreateFace(Geometry.Vertex vertex);
}
}

View File

@ -0,0 +1,692 @@
// -----------------------------------------------------------------------
// <copyright file="BoundedVoronoi.cs" company="">
// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/
// </copyright>
// -----------------------------------------------------------------------
namespace TriangleNet.Voronoi.Legacy
{
using System;
using System.Collections.Generic;
using TriangleNet.Topology;
using TriangleNet.Geometry;
/// <summary>
/// The Bounded Voronoi Diagram is the dual of a PSLG triangulation.
/// </summary>
/// <remarks>
/// 2D Centroidal Voronoi Tessellations with Constraints, 2010,
/// Jane Tournois, Pierre Alliez and Olivier Devillers
/// </remarks>
[Obsolete("Use TriangleNet.Voronoi.BoundedVoronoi class instead.")]
public class BoundedVoronoiLegacy : IVoronoi
{
IPredicates predicates = RobustPredicates.Default;
Mesh mesh;
Point[] points;
List<VoronoiRegion> regions;
// Used for new points on segments.
List<Point> segPoints;
int segIndex;
Dictionary<int, SubSegment> subsegMap;
bool includeBoundary = true;
/// <summary>
/// Initializes a new instance of the <see cref="BoundedVoronoiLegacy" /> class.
/// </summary>
/// <param name="mesh">Mesh instance.</param>
public BoundedVoronoiLegacy(Mesh mesh)
: this(mesh, true)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="BoundedVoronoiLegacy" /> class.
/// </summary>
/// <param name="mesh">Mesh instance.</param>
public BoundedVoronoiLegacy(Mesh mesh, bool includeBoundary)
{
this.mesh = mesh;
this.includeBoundary = includeBoundary;
Generate();
}
/// <summary>
/// Gets the list of Voronoi vertices.
/// </summary>
public Point[] Points
{
get { return points; }
}
/// <summary>
/// Gets the list of Voronoi regions.
/// </summary>
public ICollection<VoronoiRegion> Regions
{
get { return regions; }
}
public IEnumerable<IEdge> Edges
{
get { return EnumerateEdges(); }
}
/// <summary>
/// Computes the bounded voronoi diagram.
/// </summary>
private void Generate()
{
mesh.Renumber();
mesh.MakeVertexMap();
// Allocate space for voronoi diagram
this.regions = new List<VoronoiRegion>(mesh.vertices.Count);
this.points = new Point[mesh.triangles.Count];
this.segPoints = new List<Point>(mesh.subsegs.Count * 4);
ComputeCircumCenters();
TagBlindTriangles();
foreach (var v in mesh.vertices.Values)
{
// TODO: Need a reliable way to check if a vertex is on a segment
if (v.type == VertexType.FreeVertex || v.label == 0)
{
ConstructCell(v);
}
else if (includeBoundary)
{
ConstructBoundaryCell(v);
}
}
// Add the new points on segments to the point array.
int length = points.Length;
Array.Resize<Point>(ref points, length + segPoints.Count);
for (int i = 0; i < segPoints.Count; i++)
{
points[length + i] = segPoints[i];
}
this.segPoints.Clear();
this.segPoints = null;
}
private void ComputeCircumCenters()
{
Otri tri = default(Otri);
double xi = 0, eta = 0;
Point pt;
// Compue triangle circumcenters
foreach (var item in mesh.triangles)
{
tri.tri = item;
pt = predicates.FindCircumcenter(tri.Org(), tri.Dest(), tri.Apex(), ref xi, ref eta);
pt.id = item.id;
points[item.id] = pt;
}
}
/// <summary>
/// Tag all blind triangles.
/// </summary>
/// <remarks>
/// A triangle is said to be blind if the triangle and its circumcenter
/// lie on two different sides of a constrained edge.
/// </remarks>
private void TagBlindTriangles()
{
int blinded = 0;
Stack<Triangle> triangles;
subsegMap = new Dictionary<int, SubSegment>();
Otri f = default(Otri);
Otri f0 = default(Otri);
Osub e = default(Osub);
Osub sub1 = default(Osub);
// Tag all triangles non-blind
foreach (var t in mesh.triangles)
{
// Use the infected flag for 'blinded' attribute.
t.infected = false;
}
// for each constrained edge e of cdt do
foreach (var ss in mesh.subsegs.Values)
{
// Create a stack: triangles
triangles = new Stack<Triangle>();
// for both adjacent triangles fe to e tagged non-blind do
// Push fe into triangles
e.seg = ss;
e.orient = 0;
e.Pivot(ref f);
if (f.tri.id != Mesh.DUMMY && !f.tri.infected)
{
triangles.Push(f.tri);
}
e.Sym();
e.Pivot(ref f);
if (f.tri.id != Mesh.DUMMY && !f.tri.infected)
{
triangles.Push(f.tri);
}
// while triangles is non-empty
while (triangles.Count > 0)
{
// Pop f from stack triangles
f.tri = triangles.Pop();
f.orient = 0;
// if f is blinded by e (use P) then
if (TriangleIsBlinded(ref f, ref e))
{
// Tag f as blinded by e
f.tri.infected = true;
blinded++;
// Store association triangle -> subseg
subsegMap.Add(f.tri.hash, e.seg);
// for each adjacent triangle f0 to f do
for (f.orient = 0; f.orient < 3; f.orient++)
{
f.Sym(ref f0);
f0.Pivot(ref sub1);
// if f0 is finite and tagged non-blind & the common edge
// between f and f0 is unconstrained then
if (f0.tri.id != Mesh.DUMMY && !f0.tri.infected && sub1.seg.hash == Mesh.DUMMY)
{
// Push f0 into triangles.
triangles.Push(f0.tri);
}
}
}
}
}
blinded = 0;
}
/// <summary>
/// Check if given triangle is blinded by given segment.
/// </summary>
/// <param name="tri">Triangle.</param>
/// <param name="seg">Segments</param>
/// <returns>Returns true, if the triangle is blinded.</returns>
private bool TriangleIsBlinded(ref Otri tri, ref Osub seg)
{
Point c, pt;
Vertex torg = tri.Org();
Vertex tdest = tri.Dest();
Vertex tapex = tri.Apex();
Vertex sorg = seg.Org();
Vertex sdest = seg.Dest();
c = this.points[tri.tri.id];
if (SegmentsIntersect(sorg, sdest, c, torg, out pt, true))
{
return true;
}
if (SegmentsIntersect(sorg, sdest, c, tdest, out pt, true))
{
return true;
}
if (SegmentsIntersect(sorg, sdest, c, tapex, out pt, true))
{
return true;
}
return false;
}
private void ConstructCell(Vertex vertex)
{
VoronoiRegion region = new VoronoiRegion(vertex);
regions.Add(region);
Otri f = default(Otri);
Otri f_init = default(Otri);
Otri f_next = default(Otri);
Osub sf = default(Osub);
Osub sfn = default(Osub);
Point cc_f, cc_f_next, p;
int n = mesh.triangles.Count;
// Call P the polygon (cell) in construction
List<Point> vpoints = new List<Point>();
// Call f_init a triangle incident to x
vertex.tri.Copy(ref f_init);
if (f_init.Org() != vertex)
{
throw new Exception("ConstructCell: inconsistent topology.");
}
// Let f be initialized to f_init
f_init.Copy(ref f);
// Call f_next the next triangle counterclockwise around x
f_init.Onext(ref f_next);
// repeat ... until f = f_init
do
{
// Call Lffnext the line going through the circumcenters of f and f_next
cc_f = this.points[f.tri.id];
cc_f_next = this.points[f_next.tri.id];
// if f is tagged non-blind then
if (!f.tri.infected)
{
// Insert the circumcenter of f into P
vpoints.Add(cc_f);
if (f_next.tri.infected)
{
// Call S_fnext the constrained edge blinding f_next
sfn.seg = subsegMap[f_next.tri.hash];
// Insert point Lf,f_next /\ Sf_next into P
if (SegmentsIntersect(sfn.Org(), sfn.Dest(), cc_f, cc_f_next, out p, true))
{
p.id = n + segIndex++;
segPoints.Add(p);
vpoints.Add(p);
}
}
}
else
{
// Call Sf the constrained edge blinding f
sf.seg = subsegMap[f.tri.hash];
// if f_next is tagged non-blind then
if (!f_next.tri.infected)
{
// Insert point Lf,f_next /\ Sf into P
if (SegmentsIntersect(sf.Org(), sf.Dest(), cc_f, cc_f_next, out p, true))
{
p.id = n + segIndex++;
segPoints.Add(p);
vpoints.Add(p);
}
}
else
{
// Call Sf_next the constrained edge blinding f_next
sfn.seg = subsegMap[f_next.tri.hash];
// if Sf != Sf_next then
if (!sf.Equal(sfn))
{
// Insert Lf,fnext /\ Sf and Lf,fnext /\ Sfnext into P
if (SegmentsIntersect(sf.Org(), sf.Dest(), cc_f, cc_f_next, out p, true))
{
p.id = n + segIndex++;
segPoints.Add(p);
vpoints.Add(p);
}
if (SegmentsIntersect(sfn.Org(), sfn.Dest(), cc_f, cc_f_next, out p, true))
{
p.id = n + segIndex++;
segPoints.Add(p);
vpoints.Add(p);
}
}
}
}
// f <- f_next
f_next.Copy(ref f);
// Call f_next the next triangle counterclockwise around x
f_next.Onext();
}
while (!f.Equals(f_init));
// Output: Bounded Voronoi cell of x in counterclockwise order.
region.Add(vpoints);
}
private void ConstructBoundaryCell(Vertex vertex)
{
VoronoiRegion region = new VoronoiRegion(vertex);
regions.Add(region);
Otri f = default(Otri);
Otri f_init = default(Otri);
Otri f_next = default(Otri);
Otri f_prev = default(Otri);
Osub sf = default(Osub);
Osub sfn = default(Osub);
Vertex torg, tdest, tapex, sorg, sdest;
Point cc_f, cc_f_next, p;
int n = mesh.triangles.Count;
// Call P the polygon (cell) in construction
List<Point> vpoints = new List<Point>();
// Call f_init a triangle incident to x
vertex.tri.Copy(ref f_init);
if (f_init.Org() != vertex)
{
throw new Exception("ConstructBoundaryCell: inconsistent topology.");
}
// Let f be initialized to f_init
f_init.Copy(ref f);
// Call f_next the next triangle counterclockwise around x
f_init.Onext(ref f_next);
f_init.Oprev(ref f_prev);
// Is the border to the left?
if (f_prev.tri.id != Mesh.DUMMY)
{
// Go clockwise until we reach the border (or the initial triangle)
while (f_prev.tri.id != Mesh.DUMMY && !f_prev.Equals(f_init))
{
f_prev.Copy(ref f);
f_prev.Oprev();
}
f.Copy(ref f_init);
f.Onext(ref f_next);
}
if (f_prev.tri.id == Mesh.DUMMY)
{
// For vertices on the domain boundaray, add the vertex. For
// internal boundaries don't add it.
p = new Point(vertex.x, vertex.y);
p.id = n + segIndex++;
segPoints.Add(p);
vpoints.Add(p);
}
// Add midpoint of start triangles' edge.
torg = f.Org();
tdest = f.Dest();
p = new Point((torg.x + tdest.x) / 2, (torg.y + tdest.y) / 2);
p.id = n + segIndex++;
segPoints.Add(p);
vpoints.Add(p);
// repeat ... until f = f_init
do
{
// Call Lffnext the line going through the circumcenters of f and f_next
cc_f = this.points[f.tri.id];
if (f_next.tri.id == Mesh.DUMMY)
{
if (!f.tri.infected)
{
// Add last circumcenter
vpoints.Add(cc_f);
}
// Add midpoint of last triangles' edge (chances are it has already
// been added, so post process cell to remove duplicates???)
torg = f.Org();
tapex = f.Apex();
p = new Point((torg.x + tapex.x) / 2, (torg.y + tapex.y) / 2);
p.id = n + segIndex++;
segPoints.Add(p);
vpoints.Add(p);
break;
}
cc_f_next = this.points[f_next.tri.id];
// if f is tagged non-blind then
if (!f.tri.infected)
{
// Insert the circumcenter of f into P
vpoints.Add(cc_f);
if (f_next.tri.infected)
{
// Call S_fnext the constrained edge blinding f_next
sfn.seg = subsegMap[f_next.tri.hash];
// Insert point Lf,f_next /\ Sf_next into P
if (SegmentsIntersect(sfn.Org(), sfn.Dest(), cc_f, cc_f_next, out p, true))
{
p.id = n + segIndex++;
segPoints.Add(p);
vpoints.Add(p);
}
}
}
else
{
// Call Sf the constrained edge blinding f
sf.seg = subsegMap[f.tri.hash];
sorg = sf.Org();
sdest = sf.Dest();
// if f_next is tagged non-blind then
if (!f_next.tri.infected)
{
tdest = f.Dest();
tapex = f.Apex();
// Both circumcenters lie on the blinded side, but we
// have to add the intersection with the segment.
// Center of f edge dest->apex
Point bisec = new Point((tdest.x + tapex.x) / 2, (tdest.y + tapex.y) / 2);
// Find intersection of seg with line through f's bisector and circumcenter
if (SegmentsIntersect(sorg, sdest, bisec, cc_f, out p, false))
{
p.id = n + segIndex++;
segPoints.Add(p);
vpoints.Add(p);
}
// Insert point Lf,f_next /\ Sf into P
if (SegmentsIntersect(sorg, sdest, cc_f, cc_f_next, out p, true))
{
p.id = n + segIndex++;
segPoints.Add(p);
vpoints.Add(p);
}
}
else
{
// Call Sf_next the constrained edge blinding f_next
sfn.seg = subsegMap[f_next.tri.hash];
// if Sf != Sf_next then
if (!sf.Equal(sfn))
{
// Insert Lf,fnext /\ Sf and Lf,fnext /\ Sfnext into P
if (SegmentsIntersect(sorg, sdest, cc_f, cc_f_next, out p, true))
{
p.id = n + segIndex++;
segPoints.Add(p);
vpoints.Add(p);
}
if (SegmentsIntersect(sfn.Org(), sfn.Dest(), cc_f, cc_f_next, out p, true))
{
p.id = n + segIndex++;
segPoints.Add(p);
vpoints.Add(p);
}
}
else
{
// Both circumcenters lie on the blinded side, but we
// have to add the intersection with the segment.
// Center of f_next edge org->dest
Point bisec = new Point((torg.x + tdest.x) / 2, (torg.y + tdest.y) / 2);
// Find intersection of seg with line through f_next's bisector and circumcenter
if (SegmentsIntersect(sorg, sdest, bisec, cc_f_next, out p, false))
{
p.id = n + segIndex++;
segPoints.Add(p);
vpoints.Add(p);
}
}
}
}
// f <- f_next
f_next.Copy(ref f);
// Call f_next the next triangle counterclockwise around x
f_next.Onext();
}
while (!f.Equals(f_init));
// Output: Bounded Voronoi cell of x in counterclockwise order.
region.Add(vpoints);
}
/// <summary>
/// Determines the intersection point of the line segment defined by points A and B with the
/// line segment defined by points C and D.
/// </summary>
/// <param name="seg">The first segment AB.</param>
/// <param name="pc">Endpoint C of second segment.</param>
/// <param name="pd">Endpoint D of second segment.</param>
/// <param name="p">Reference to the intersection point.</param>
/// <param name="strictIntersect">If false, pa and pb represent a line.</param>
/// <returns>Returns true if the intersection point was found, and stores that point in X,Y.
/// Returns false if there is no determinable intersection point, in which case X,Y will
/// be unmodified.
/// </returns>
private bool SegmentsIntersect(Point p1, Point p2, Point p3, Point p4, out Point p, bool strictIntersect)
{
p = null; // TODO: Voronoi SegmentsIntersect
double Ax = p1.x, Ay = p1.y;
double Bx = p2.x, By = p2.y;
double Cx = p3.x, Cy = p3.y;
double Dx = p4.x, Dy = p4.y;
double distAB, theCos, theSin, newX, ABpos;
// Fail if either line segment is zero-length.
if (Ax == Bx && Ay == By || Cx == Dx && Cy == Dy) return false;
// Fail if the segments share an end-point.
if (Ax == Cx && Ay == Cy || Bx == Cx && By == Cy
|| Ax == Dx && Ay == Dy || Bx == Dx && By == Dy)
{
return false;
}
// (1) Translate the system so that point A is on the origin.
Bx -= Ax; By -= Ay;
Cx -= Ax; Cy -= Ay;
Dx -= Ax; Dy -= Ay;
// Discover the length of segment A-B.
distAB = Math.Sqrt(Bx * Bx + By * By);
// (2) Rotate the system so that point B is on the positive X axis.
theCos = Bx / distAB;
theSin = By / distAB;
newX = Cx * theCos + Cy * theSin;
Cy = Cy * theCos - Cx * theSin; Cx = newX;
newX = Dx * theCos + Dy * theSin;
Dy = Dy * theCos - Dx * theSin; Dx = newX;
// Fail if segment C-D doesn't cross line A-B.
if (Cy < 0 && Dy < 0 || Cy >= 0 && Dy >= 0 && strictIntersect) return false;
// (3) Discover the position of the intersection point along line A-B.
ABpos = Dx + (Cx - Dx) * Dy / (Dy - Cy);
// Fail if segment C-D crosses line A-B outside of segment A-B.
if (ABpos < 0 || ABpos > distAB && strictIntersect) return false;
// (4) Apply the discovered position to line A-B in the original coordinate system.
p = new Point(Ax + ABpos * theCos, Ay + ABpos * theSin);
// Success.
return true;
}
// TODO: Voronoi enumerate edges
private IEnumerable<IEdge> EnumerateEdges()
{
// Copy edges
Point first, last;
var edges = new List<IEdge>(this.Regions.Count * 2);
foreach (var region in this.Regions)
{
first = null;
last = null;
foreach (var pt in region.Vertices)
{
if (first == null)
{
first = pt;
last = pt;
}
else
{
edges.Add(new Edge(last.id, pt.id));
last = pt;
}
}
if (region.Bounded && first != null)
{
edges.Add(new Edge(last.id, first.id));
}
}
return edges;
}
}
}

View File

@ -0,0 +1,32 @@
// -----------------------------------------------------------------------
// <copyright file="IVoronoi.cs" company="">
// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/
// </copyright>
// -----------------------------------------------------------------------
namespace TriangleNet.Voronoi.Legacy
{
using System.Collections.Generic;
using TriangleNet.Geometry;
/// <summary>
/// Voronoi diagram interface.
/// </summary>
public interface IVoronoi
{
/// <summary>
/// Gets the list of Voronoi vertices.
/// </summary>
Point[] Points { get; }
/// <summary>
/// Gets the list of Voronoi regions.
/// </summary>
ICollection<VoronoiRegion> Regions { get; }
/// <summary>
/// Gets the list of edges.
/// </summary>
IEnumerable<IEdge> Edges { get; }
}
}

View File

@ -0,0 +1,306 @@
// -----------------------------------------------------------------------
// <copyright file="Voronoi.cs">
// Original Triangle code by Jonathan Richard Shewchuk, http://www.cs.cmu.edu/~quake/triangle.html
// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/
// </copyright>
// -----------------------------------------------------------------------
namespace TriangleNet.Voronoi.Legacy
{
using System;
using System.Collections.Generic;
using TriangleNet.Topology;
using TriangleNet.Geometry;
using TriangleNet.Tools;
/// <summary>
/// The Voronoi Diagram is the dual of a pointset triangulation.
/// </summary>
[Obsolete("Use TriangleNet.Voronoi.StandardVoronoi class instead.")]
public class SimpleVoronoi : IVoronoi
{
IPredicates predicates = RobustPredicates.Default;
Mesh mesh;
Point[] points;
Dictionary<int, VoronoiRegion> regions;
// Stores the endpoints of rays of unbounded Voronoi cells
Dictionary<int, Point> rayPoints;
int rayIndex;
// Bounding box of the triangles circumcenters.
Rectangle bounds;
/// <summary>
/// Initializes a new instance of the <see cref="SimpleVoronoi" /> class.
/// </summary>
/// <param name="mesh"></param>
/// <remarks>
/// Be sure MakeVertexMap has been called (should always be the case).
/// </remarks>
public SimpleVoronoi(Mesh mesh)
{
this.mesh = mesh;
Generate();
}
/// <summary>
/// Gets the list of Voronoi vertices.
/// </summary>
public Point[] Points
{
get { return points; }
}
/// <summary>
/// Gets the list of Voronoi regions.
/// </summary>
public ICollection<VoronoiRegion> Regions
{
get { return regions.Values; }
}
public IEnumerable<IEdge> Edges
{
get { return EnumerateEdges(); }
}
/// <summary>
/// Gets the Voronoi diagram as raw output data.
/// </summary>
/// <param name="mesh"></param>
/// <returns></returns>
/// <remarks>
/// The Voronoi diagram is the geometric dual of the Delaunay triangulation.
/// Hence, the Voronoi vertices are listed by traversing the Delaunay
/// triangles, and the Voronoi edges are listed by traversing the Delaunay
/// edges.
///</remarks>
private void Generate()
{
mesh.Renumber();
mesh.MakeVertexMap();
// Allocate space for voronoi diagram
this.points = new Point[mesh.triangles.Count + mesh.hullsize];
this.regions = new Dictionary<int, VoronoiRegion>(mesh.vertices.Count);
rayPoints = new Dictionary<int, Point>();
rayIndex = 0;
bounds = new Rectangle();
// Compute triangles circumcenters and setup bounding box
ComputeCircumCenters();
// Add all Voronoi regions to the map.
foreach (var vertex in mesh.vertices.Values)
{
regions.Add(vertex.id, new VoronoiRegion(vertex));
}
// Loop over the mesh vertices (Voronoi generators).
foreach (var region in regions.Values)
{
//if (item.Boundary == 0)
{
ConstructCell(region);
}
}
}
private void ComputeCircumCenters()
{
Otri tri = default(Otri);
double xi = 0, eta = 0;
Point pt;
// Compue triangle circumcenters
foreach (var item in mesh.triangles)
{
tri.tri = item;
pt = predicates.FindCircumcenter(tri.Org(), tri.Dest(), tri.Apex(), ref xi, ref eta);
pt.id = item.id;
points[item.id] = pt;
bounds.Expand(pt);
}
double ds = Math.Max(bounds.Width, bounds.Height);
bounds.Resize(ds / 10, ds / 10);
}
/// <summary>
/// Construct Voronoi region for given vertex.
/// </summary>
/// <param name="region"></param>
private void ConstructCell(VoronoiRegion region)
{
var vertex = region.Generator as Vertex;
var vpoints = new List<Point>();
Otri f = default(Otri);
Otri f_init = default(Otri);
Otri f_next = default(Otri);
Otri f_prev = default(Otri);
Osub sub = default(Osub);
// Call f_init a triangle incident to x
vertex.tri.Copy(ref f_init);
f_init.Copy(ref f);
f_init.Onext(ref f_next);
// Check if f_init lies on the boundary of the triangulation.
if (f_next.tri.id == Mesh.DUMMY)
{
f_init.Oprev(ref f_prev);
if (f_prev.tri.id != Mesh.DUMMY)
{
f_init.Copy(ref f_next);
// Move one triangle clockwise
f_init.Oprev();
f_init.Copy(ref f);
}
}
// Go counterclockwise until we reach the border or the initial triangle.
while (f_next.tri.id != Mesh.DUMMY)
{
// Add circumcenter of current triangle
vpoints.Add(points[f.tri.id]);
region.AddNeighbor(f.tri.id, regions[f.Apex().id]);
if (f_next.Equals(f_init))
{
// Voronoi cell is complete (bounded case).
region.Add(vpoints);
return;
}
f_next.Copy(ref f);
f_next.Onext();
}
// Voronoi cell is unbounded
region.Bounded = false;
Vertex torg, tdest, tapex;
Point intersection;
int sid, n = mesh.triangles.Count;
// Find the boundary segment id (we use this id to number the endpoints of infinit rays).
f.Lprev(ref f_next);
f_next.Pivot(ref sub);
sid = sub.seg.hash;
// Last valid f lies at the boundary. Add the circumcenter.
vpoints.Add(points[f.tri.id]);
region.AddNeighbor(f.tri.id, regions[f.Apex().id]);
// Check if the intersection with the bounding box has already been computed.
if (!rayPoints.TryGetValue(sid, out intersection))
{
torg = f.Org();
tapex = f.Apex();
intersection = IntersectionHelper.BoxRayIntersection(bounds, points[f.tri.id], torg.y - tapex.y, tapex.x - torg.x);
// Set the correct id for the vertex
intersection.id = n + rayIndex;
points[n + rayIndex] = intersection;
rayIndex++;
rayPoints.Add(sid, intersection);
}
vpoints.Add(intersection);
// Now walk from f_init clockwise till we reach the boundary.
vpoints.Reverse();
f_init.Copy(ref f);
f.Oprev(ref f_prev);
while (f_prev.tri.id != Mesh.DUMMY)
{
vpoints.Add(points[f_prev.tri.id]);
region.AddNeighbor(f_prev.tri.id, regions[f_prev.Apex().id]);
f_prev.Copy(ref f);
f_prev.Oprev();
}
// Find the boundary segment id.
f.Pivot(ref sub);
sid = sub.seg.hash;
if (!rayPoints.TryGetValue(sid, out intersection))
{
// Intersection has not been computed yet.
torg = f.Org();
tdest = f.Dest();
intersection = IntersectionHelper.BoxRayIntersection(bounds, points[f.tri.id], tdest.y - torg.y, torg.x - tdest.x);
// Set the correct id for the vertex
intersection.id = n + rayIndex;
rayPoints.Add(sid, intersection);
points[n + rayIndex] = intersection;
rayIndex++;
}
vpoints.Add(intersection);
region.AddNeighbor(intersection.id, regions[f.Dest().id]);
// Add the new points to the region (in counter-clockwise order)
vpoints.Reverse();
region.Add(vpoints);
}
// TODO: Voronoi enumerate edges
private IEnumerable<IEdge> EnumerateEdges()
{
// Copy edges
Point first, last;
var edges = new List<IEdge>(this.Regions.Count * 2);
foreach (var region in this.Regions)
{
var ve = region.Vertices.GetEnumerator();
ve.MoveNext();
first = last = ve.Current;
while (ve.MoveNext())
{
if (region.ID < region.GetNeighbor(last).ID)
{
edges.Add(new Edge(last.id, ve.Current.id));
}
last = ve.Current;
}
if (region.Bounded && region.ID < region.GetNeighbor(last).ID)
{
edges.Add(new Edge(last.id, first.id));
}
}
return edges;
}
}
}

View File

@ -0,0 +1,111 @@
// -----------------------------------------------------------------------
// <copyright file="VoronoiRegion.cs" company="">
// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/
// </copyright>
// -----------------------------------------------------------------------
namespace TriangleNet.Voronoi.Legacy
{
using System;
using System.Collections.Generic;
using TriangleNet.Topology;
using TriangleNet.Geometry;
/// <summary>
/// Represents a region in the Voronoi diagram.
/// </summary>
public class VoronoiRegion
{
int id;
Point generator;
List<Point> vertices;
bool bounded;
// A map (vertex id) -> (neighbor across adjacent edge)
Dictionary<int, VoronoiRegion> neighbors;
/// <summary>
/// Gets the Voronoi region id (which is the same as the generators vertex id).
/// </summary>
public int ID
{
get { return id; }
}
/// <summary>
/// Gets the Voronoi regions generator.
/// </summary>
public Point Generator
{
get { return generator; }
}
/// <summary>
/// Gets the Voronoi vertices on the regions boundary.
/// </summary>
public ICollection<Point> Vertices
{
get { return vertices; }
}
/// <summary>
/// Gets or sets whether the Voronoi region is bounded.
/// </summary>
public bool Bounded
{
get { return bounded; }
set { bounded = value; }
}
public VoronoiRegion(Vertex generator)
{
this.id = generator.id;
this.generator = generator;
this.vertices = new List<Point>();
this.bounded = true;
this.neighbors = new Dictionary<int, VoronoiRegion>();
}
public void Add(Point point)
{
this.vertices.Add(point);
}
public void Add(List<Point> points)
{
this.vertices.AddRange(points);
}
/// <summary>
/// Returns the neighbouring Voronoi region, that lies across the edge starting at
/// given vertex.
/// </summary>
/// <param name="p">Vertex defining an edge of the region.</param>
/// <returns>Neighbouring Voronoi region</returns>
/// <remarks>
/// The edge starting at p is well defined (vertices are ordered counterclockwise).
/// </remarks>
public VoronoiRegion GetNeighbor(Point p)
{
VoronoiRegion neighbor;
if (neighbors.TryGetValue(p.id, out neighbor))
{
return neighbor;
}
return null;
}
internal void AddNeighbor(int id, VoronoiRegion neighbor)
{
this.neighbors.Add(id, neighbor);
}
public override string ToString()
{
return String.Format("R-ID {0}", id);
}
}
}

View File

@ -0,0 +1,66 @@
// -----------------------------------------------------------------------
// <copyright file="StandardVoronoi.cs">
// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/
// </copyright>
// -----------------------------------------------------------------------
namespace TriangleNet.Voronoi
{
using System.Collections.Generic;
using TriangleNet.Geometry;
using TriangleNet.Tools;
using TriangleNet.Topology.DCEL;
public class StandardVoronoi : VoronoiBase
{
public StandardVoronoi(Mesh mesh)
: this(mesh, mesh.bounds, new DefaultVoronoiFactory(), RobustPredicates.Default)
{
}
public StandardVoronoi(Mesh mesh, Rectangle box)
: this(mesh, box, new DefaultVoronoiFactory(), RobustPredicates.Default)
{
}
public StandardVoronoi(Mesh mesh, Rectangle box, IVoronoiFactory factory, IPredicates predicates)
: base(mesh, factory, predicates, true)
{
// We assume the box to be at least as large as the mesh.
box.Expand(mesh.bounds);
// We explicitly told the base constructor to call the Generate method, so
// at this point the basic Voronoi diagram is already created.
PostProcess(box);
}
/// <summary>
/// Compute edge intersections with bounding box.
/// </summary>
private void PostProcess(Rectangle box)
{
foreach (var edge in rays)
{
// The vertices of the infinite edge.
var v1 = (Point)edge.origin;
var v2 = (Point)edge.twin.origin;
if (box.Contains(v1) || box.Contains(v2))
{
// Move infinite vertex v2 onto the box boundary.
IntersectionHelper.BoxRayIntersection(box, v1, v2, ref v2);
}
else
{
// There is actually no easy way to handle the second case. The two edges
// leaving v1, pointing towards the mesh, don't have to intersect the box
// (the could join with edges of other cells outside the box).
// A general intersection algorithm (DCEL <-> Rectangle) is needed, which
// computes intersections with all edges and discards objects outside the
// box.
}
}
}
}
}

View File

@ -0,0 +1,291 @@
// -----------------------------------------------------------------------
// <copyright file="VoronoiBase.cs">
// Original Triangle code by Jonathan Richard Shewchuk, http://www.cs.cmu.edu/~quake/triangle.html
// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/
// </copyright>
// -----------------------------------------------------------------------
namespace TriangleNet.Voronoi
{
using System.Collections.Generic;
using TriangleNet.Topology;
using TriangleNet.Geometry;
using TriangleNet.Topology.DCEL;
using Vertex = TriangleNet.Topology.DCEL.Vertex;
/// <summary>
/// The Voronoi diagram is the dual of a pointset triangulation.
/// </summary>
public abstract class VoronoiBase : DcelMesh
{
protected IPredicates predicates;
protected IVoronoiFactory factory;
// List of infinite half-edges, i.e. half-edges that start at circumcenters of triangles
// which lie on the domain boundary.
protected List<HalfEdge> rays;
/// <summary>
/// Initializes a new instance of the <see cref="VoronoiBase" /> class.
/// </summary>
/// <param name="mesh">Triangle mesh.</param>
/// <param name="factory">Voronoi object factory.</param>
/// <param name="predicates">Geometric predicates implementation.</param>
/// <param name="generate">If set to true, the constuctor will call the Generate
/// method, which builds the Voronoi diagram.</param>
protected VoronoiBase(Mesh mesh, IVoronoiFactory factory, IPredicates predicates,
bool generate)
: base(false)
{
this.factory = factory;
this.predicates = predicates;
if (generate)
{
Generate(mesh);
}
}
/// <summary>
/// Generate the Voronoi diagram from given triangle mesh..
/// </summary>
/// <param name="mesh"></param>
/// <param name="bounded"></param>
protected void Generate(Mesh mesh)
{
mesh.Renumber();
base.edges = new List<HalfEdge>();
this.rays = new List<HalfEdge>();
// Allocate space for Voronoi diagram.
var vertices = new Vertex[mesh.triangles.Count + mesh.hullsize];
var faces = new Face[mesh.vertices.Count];
if (factory == null)
{
factory = new DefaultVoronoiFactory();
}
factory.Initialize(vertices.Length, 2 * mesh.NumberOfEdges, faces.Length);
// Compute triangles circumcenters.
var map = ComputeVertices(mesh, vertices);
// Create all Voronoi faces.
foreach (var vertex in mesh.vertices.Values)
{
faces[vertex.id] = factory.CreateFace(vertex);
}
ComputeEdges(mesh, vertices, faces, map);
// At this point all edges are computed, but the (edge.next) pointers aren't set.
ConnectEdges(map);
base.vertices = new List<Vertex>(vertices);
base.faces = new List<Face>(faces);
}
/// <summary>
/// Compute the Voronoi vertices (the circumcenters of the triangles).
/// </summary>
/// <returns>An empty map, which will map all vertices to a list of leaving edges.</returns>
protected List<HalfEdge>[] ComputeVertices(Mesh mesh, Vertex[] vertices)
{
Otri tri = default(Otri);
double xi = 0, eta = 0;
Vertex vertex;
Point pt;
int id;
// Maps all vertices to a list of leaving edges.
var map = new List<HalfEdge>[mesh.triangles.Count];
// Compue triangle circumcenters
foreach (var t in mesh.triangles)
{
id = t.id;
tri.tri = t;
pt = predicates.FindCircumcenter(tri.Org(), tri.Dest(), tri.Apex(), ref xi, ref eta);
vertex = factory.CreateVertex(pt.x, pt.y);
vertex.id = id;
vertices[id] = vertex;
map[id] = new List<HalfEdge>();
}
return map;
}
/// <summary>
/// Compute the edges of the Voronoi diagram.
/// </summary>
/// <param name="mesh"></param>
/// <param name="vertices"></param>
/// <param name="faces"></param>
/// <param name="map">Empty vertex map.</param>
protected void ComputeEdges(Mesh mesh, Vertex[] vertices, Face[] faces, List<HalfEdge>[] map)
{
Otri tri, neighbor = default(Otri);
TriangleNet.Geometry.Vertex org, dest;
double px, py;
int id, nid, count = mesh.triangles.Count;
Face face, neighborFace;
HalfEdge edge, twin;
Vertex vertex, end;
// Count infinte edges (vertex id for their endpoints).
int j = 0;
// Count half-edges (edge ids).
int k = 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 id than
// its neighbor. This way, each edge is considered only once.
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)
{
// Get the endpoints of the current triangle edge.
org = tri.Org();
dest = tri.Dest();
face = faces[org.id];
neighborFace = faces[dest.id];
vertex = vertices[id];
// For each edge in the triangle mesh, there's a corresponding edge
// in the Voronoi diagram, i.e. two half-edges will be created.
if (nid < 0)
{
// Unbounded edge, direction perpendicular to the boundary edge,
// pointing outwards.
px = dest.y - org.y;
py = org.x - dest.x;
end = factory.CreateVertex(vertex.x + px, vertex.y + py);
end.id = count + j++;
vertices[end.id] = end;
edge = factory.CreateHalfEdge(end, face);
twin = factory.CreateHalfEdge(vertex, neighborFace);
// Make (face.edge) always point to an edge that starts at an infinite
// vertex. This will allow traversing of unbounded faces.
face.edge = edge;
face.bounded = false;
map[id].Add(twin);
rays.Add(twin);
}
else
{
end = vertices[nid];
// Create half-edges.
edge = factory.CreateHalfEdge(end, face);
twin = factory.CreateHalfEdge(vertex, neighborFace);
// Add to vertex map.
map[nid].Add(edge);
map[id].Add(twin);
}
vertex.leaving = twin;
end.leaving = edge;
edge.twin = twin;
twin.twin = edge;
edge.id = k++;
twin.id = k++;
this.edges.Add(edge);
this.edges.Add(twin);
}
}
}
}
/// <summary>
/// Connect all edges of the Voronoi diagram.
/// </summary>
/// <param name="map">Maps all vertices to a list of leaving edges.</param>
protected virtual void ConnectEdges(List<HalfEdge>[] map)
{
int length = map.Length;
// For each half-edge, find its successor in the connected face.
foreach (var edge in this.edges)
{
var face = edge.face.generator.id;
// The id of the dest vertex of current edge.
int id = edge.twin.origin.id;
// The edge origin can also be an infinite vertex. Sort them out
// by checking the id.
if (id < length)
{
// Look for the edge that is connected to the current face. Each
// Voronoi vertex has degree 3, so this loop is actually O(1).
foreach (var next in map[id])
{
if (next.face.generator.id == face)
{
edge.next = next;
break;
}
}
}
}
}
protected override IEnumerable<IEdge> EnumerateEdges()
{
var edges = new List<IEdge>(this.edges.Count / 2);
foreach (var edge in this.edges)
{
var twin = edge.twin;
// Report edge only once.
if (twin == null)
{
edges.Add(new Edge(edge.origin.id, edge.next.origin.id));
}
else if (edge.id < twin.id)
{
edges.Add(new Edge(edge.origin.id, twin.origin.id));
}
}
return edges;
}
}
}

View File

@ -0,0 +1,51 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="1024"
height="1024"
viewBox="0 0 1024 1024"
version="1.1"
id="svg5"
inkscape:version="1.2.2 (732a01da63, 2022-12-09)"
sodipodi:docname="Rokojori-Action-Library-Logo.svg"
xml:space="preserve"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"><sodipodi:namedview
id="namedview7"
pagecolor="#333333"
bordercolor="#404040"
borderopacity="1"
inkscape:showpageshadow="0"
inkscape:pageopacity="0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#333333"
inkscape:document-units="px"
showgrid="false"
inkscape:zoom="0.41781005"
inkscape:cx="543.30909"
inkscape:cy="522.96492"
inkscape:window-width="1920"
inkscape:window-height="1017"
inkscape:window-x="-8"
inkscape:window-y="-8"
inkscape:window-maximized="1"
inkscape:current-layer="svg5" /><defs
id="defs2"><clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath7940"><rect
style="fill:#333333;fill-opacity:1;stroke:none;stroke-width:10;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke fill markers"
id="rect7942"
width="1440"
height="810"
x="0"
y="0" /></clipPath></defs><g
inkscape:label="Content"
inkscape:groupmode="layer"
id="layer1"
style="fill:#ffffff;fill-opacity:1" /><path
id="path20242"
style="display:inline;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:23.9101;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke fill markers"
d="m 604.25829,89.770812 c -16.5542,27.824228 -31.69521,57.952198 -44.88043,87.409488 -15.63609,-2.61337 -31.34151,-3.58174 -47.06832,-3.76944 v -0.024 c -0.11322,0 -0.21371,0.024 -0.3057,0.024 -0.0991,0 -0.20379,-0.024 -0.30145,-0.024 v 0.024 c -15.75809,0.18654 -31.45389,1.15601 -47.09224,3.76944 -13.17688,-29.45371 -28.30601,-59.58408 -44.8841,-87.408302 -41.44468,9.213764 -82.44099,22.036782 -120.87776,41.383672 0.8789,33.94045 3.07117,66.46183 7.5197,99.49489 -14.92702,9.56286 -30.61448,17.77121 -44.55887,28.96594 -14.16561,10.89825 -28.63718,21.32784 -41.46378,34.07435 -25.62574,-16.94991 -52.74588,-32.87884 -80.68717,-46.94159 -30.11728,32.41498 -58.283461,67.40269 -81.288517,106.55077 17.308529,27.99996 35.378621,54.24374 54.889327,79.14854 h 0.54206 v 240.27659 c 0.43874,0 0.88172,0.024 1.31905,0.0594 l 147.30199,14.19904 c 7.71699,0.74488 13.76267,6.95785 14.29949,14.69039 l 4.54292,65.02244 128.49313,9.17193 8.84908,-60.01685 c 1.1478,-7.78156 7.82456,-13.5439 15.69467,-13.5439 h 155.40989 c 7.86757,0 14.54221,5.76234 15.69099,13.5439 l 8.84922,60.01685 128.49667,-9.17193 4.53811,-65.02244 c 0.54206,-7.73254 6.5825,-13.94199 14.29949,-14.69039 l 147.2434,-14.19904 c 0.43592,-0.0354 0.87465,-0.0594 1.31339,-0.0594 v -19.17472 l 0.0566,-0.024 V 432.44503 h 0.54206 c 19.51297,-24.9048 37.57231,-51.14977 54.88921,-79.14855 -22.99786,-39.14927 -51.17479,-74.13819 -81.29321,-106.55077 -27.93422,14.06036 -55.06865,29.98931 -80.69438,46.9416 -12.82306,-12.74531 -27.26592,-23.17491 -41.45303,-34.07315 -13.93718,-11.19594 -29.64742,-19.40428 -44.54699,-28.96715 4.4365,-33.03304 6.63274,-65.55323 7.51503,-99.49249 C 686.71736,111.80644 645.72232,98.979844 604.25614,89.767256 Z M 303.18303,441.26304 c 49.03019,0 88.77115,39.71236 88.77115,88.72695 0,49.04568 -39.74096,88.77598 -88.77115,88.77598 -49.00739,0 -88.75924,-39.7303 -88.75924,-88.77598 0,-49.01459 39.75425,-88.72695 88.75924,-88.72695 z m 417.62888,0 c 49.00259,0 88.74722,39.71236 88.74722,88.72695 0,49.04568 -39.74463,88.77598 -88.74722,88.77598 -49.03726,0 -88.77594,-39.7303 -88.77594,-88.77598 0,-49.01459 39.73868,-88.72695 88.77594,-88.72695 z m -208.83354,51.91968 c 15.78313,0 28.60491,11.64425 28.60491,25.97 v 81.72007 c 0,14.33654 -12.82306,25.96763 -28.60491,25.96763 -15.78313,0 -28.57266,-11.63109 -28.57266,-25.96763 v -81.72007 c 0,-14.32575 12.78953,-25.97 28.57266,-25.97 z m 166.73132,313.94495 c -32.53573,0 -58.89416,26.39322 -58.89416,58.92536 0,32.53213 26.36084,58.89904 58.89416,58.89904 32.56575,0 58.92063,-26.36691 58.92063,-58.89904 0,-32.53214 -26.35616,-58.92536 -58.92063,-58.92536 z m -336.54953,9.28192 c -32.54055,0 -58.91936,26.39321 -58.91936,58.92535 0,32.53333 26.37881,58.89785 58.91936,58.89785 32.55837,0 58.9253,-26.36452 58.9253,-58.89785 0,-32.53095 -26.36806,-58.92535 -58.9253,-58.92535 z" /></svg>

After

Width:  |  Height:  |  Size: 4.6 KiB

View File

@ -0,0 +1,37 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://bc3554irquknv"
path="res://.godot/imported/Rokojori-Action-Library-Logo.svg-084dc0777a3bf5e12ed64558d69c8366.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://Scripts/Rokojori/Rokojori-Action-Library/Icons/Rokojori-Action-Library-Logo.svg"
dest_files=["res://.godot/imported/Rokojori-Action-Library-Logo.svg-084dc0777a3bf5e12ed64558d69c8366.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1
svg/scale=1.0
editor/scale_with_editor_scale=false
editor/convert_colors_with_editor_theme=false

View File

@ -9,8 +9,8 @@ namespace Rokojori
{ {
public enum FilePathType public enum FilePathType
{ {
ABSOLUTE, Absolute,
RELATIVE Relative
} }
public class FilePath public class FilePath
@ -75,7 +75,7 @@ namespace Rokojori
} }
} }
public static FilePath Create( string path, FilePathType type = FilePathType.RELATIVE, FilePath parent = null ) public static FilePath Create( string path, FilePathType type = FilePathType.Relative, FilePath parent = null )
{ {
var rp = new FilePath(); var rp = new FilePath();
@ -88,12 +88,12 @@ namespace Rokojori
public static FilePath Absolute( string path ) public static FilePath Absolute( string path )
{ {
return FilePath.Create( path, FilePathType.ABSOLUTE ); return FilePath.Create( path, FilePathType.Absolute );
} }
public FilePath MakeRelative( string path ) public FilePath MakeRelative( string path )
{ {
return FilePath.Create( path, FilePathType.RELATIVE, this ); return FilePath.Create( path, FilePathType.Relative, this );
} }
public FilePath MakeAbsolutePathRelative( string absolutePath ) public FilePath MakeAbsolutePathRelative( string absolutePath )
@ -115,7 +115,7 @@ namespace Rokojori
{ {
var rp = new FilePath(); var rp = new FilePath();
rp.type = FilePathType.RELATIVE; rp.type = FilePathType.Relative;
rp.parent = absoluteParent; rp.parent = absoluteParent;
rp.path = Path.GetFileName( absolutePath ); rp.path = Path.GetFileName( absolutePath );
rp._fullPath = absolutePath; rp._fullPath = absolutePath;

View File

@ -52,12 +52,12 @@ namespace Rokojori
var nodes = parser.sceneFile.nodes; var nodes = parser.sceneFile.nodes;
RJLog.Log( "Nodes:", nodes.Count ); RJLog.Log( "Nodes:", nodes.Count );
var doc = new HtmlDocument(); var doc = new XMLDocument();
var html = doc.documentElement; var html = doc.documentElement;
var body = html.querySelector( HtmlElementNodeName.body ); var body = html.querySelector( HTMLElementName.body );
var head = html.querySelector( HtmlElementNodeName.head ); var head = html.querySelector( HTMLElementName.head );
head.AddScript( head.AddHTMLScript(
@" @"
function main() function main()
@ -112,7 +112,7 @@ namespace Rokojori
" "
); );
head.AddStyle( head.AddHTMLStyle(
@" @"
body body
{ {
@ -163,12 +163,12 @@ namespace Rokojori
var elementMap = new Dictionary<string,HtmlElementNode>(); var elementMap = new Dictionary<string,XMLElementNode>();
var GD_Node = HtmlElementNodeName.CreateNodeName( "gd-node" ); var GD_Node = XMLElementNodeName.Create( "gd-node" );
var GD_Name = HtmlElementNodeName.CreateNodeName( "gd-name" ); var GD_Name = XMLElementNodeName.Create( "gd-name" );
var GD_Type = HtmlElementNodeName.CreateNodeName( "gd-type" ); var GD_Type = XMLElementNodeName.Create( "gd-type" );
var GD_Children = HtmlElementNodeName.CreateNodeName( "gd-children" ); var GD_Children = XMLElementNodeName.Create( "gd-children" );
for ( int i = 0; i < nodes.Count; i++ ) for ( int i = 0; i < nodes.Count; i++ )
{ {
@ -211,7 +211,7 @@ namespace Rokojori
} }
FilesSync.SaveUTF8( path + ".html", new HtmlSerializer().Serialize( doc.documentElement ) ); FilesSync.SaveUTF8( path + ".html", new XMLSerializer().SerializeHtml( doc.documentElement ) );
} }
} }

View File

@ -1,25 +0,0 @@
using System.Collections;
using System.Collections.Generic;
using System.Text.RegularExpressions;
namespace Rokojori
{
public class HtmlAttributeNode:HtmlNode
{
public HtmlAttributeNode( HtmlDocument document, HtmlElementNode parent, string name, string value ):base( document, HtmlNode.NodeType.Attribute )
{
_name = name;
_value = value;
_parent = parent;
}
HtmlElementNode _parent;
string _name;
string _value;
public string name => _name;
public string value => _value;
}
}

View File

@ -1,41 +0,0 @@
using System.Collections;
using System.Collections.Generic;
using System.Text.RegularExpressions;
namespace Rokojori
{
public class HtmlDocument
{
HtmlElementNode _documentElement;
public HtmlElementNode documentElement => _documentElement;
public HtmlDocument( bool addHeadAndBody = true )
{
_documentElement = HtmlElementNodeName.html.Create( this );
if ( addHeadAndBody )
{
_documentElement.AppendChild( HtmlElementNodeName.head.Create( this ) );
_documentElement.AppendChild( HtmlElementNodeName.body.Create( this ) );
}
}
public HtmlElementNode Create( HtmlElementNodeName nodeName, string text = null )
{
var element = nodeName.Create( this );
if ( text != null )
{
element.AddText( text );
}
return element;
}
public HtmlTextNode CreateText( string text )
{
return new HtmlTextNode( this, text );
}
}
}

View File

@ -1,140 +0,0 @@
using System.Collections;
using System.Collections.Generic;
using System.Text.RegularExpressions;
namespace Rokojori
{
public class HtmlElementNode:HtmlNode
{
public HtmlElementNode( HtmlDocument document, string nodeName ):base( document, HtmlNode.NodeType.Element )
{
_nodeName = nodeName;
}
string _nodeName;
public string nodeName => _nodeName;
List<HtmlNode> _children = new List<HtmlNode>();
public int numChildren => _children.Count;
public HtmlNode GetChildAt( int index )
{
return _children[ index ];
}
public bool HasOnlyTextNodes()
{
for ( int i = 0; i < _children.Count; i++ )
{
if ( _children[ i ].nodeType != NodeType.Text )
{
return false;
}
}
return _children.Count > 0;
}
public void RemoveChild( HtmlNode node )
{
var childIndex = _children.IndexOf( node );
if ( childIndex == -1 )
{
return;
}
node._SetParent( null );
_children.RemoveAt( childIndex );
}
public void AppendChild( HtmlNode node )
{
if ( node.parentNode == this )
{
return;
}
if ( node.parentNode != null )
{
var element = node.parentNode as HtmlElementNode;
element.RemoveChild( node );
}
_children.Add( node );
node._SetParent( this );
}
public HtmlTextNode AddText( string text )
{
var textNode = document.CreateText( text );
AppendChild( textNode );
return textNode;
}
public HtmlElementNode AddScript( string script )
{
return AddElement( HtmlElementNodeName.script, script );
}
public HtmlElementNode AddStyle( string style )
{
return AddElement( HtmlElementNodeName.style, style );
}
public HtmlElementNode AddElement( HtmlElementNodeName name, string text = null )
{
var en = document.Create( name, text );
AppendChild( en );
return en;
}
public void SetAttribute( string name, string value )
{
var att = new HtmlAttributeNode( document, this, name, value );
_attributes.Add( att );
}
List<HtmlAttributeNode> _attributes = new List<HtmlAttributeNode>();
public int numAttributes => _attributes.Count;
public HtmlAttributeNode GetAttributeAt( int index )
{
return _attributes[ index ];
}
public HtmlElementNode querySelector( string selector )
{
var nodeName = HtmlElementNodeName.CreateNodeName( selector );
return querySelector( nodeName );
}
public HtmlElementNode querySelector( HtmlElementSelector selector )
{
var element = HtmlWalker.instance.Find( this,
n =>
{
var element = n as HtmlElementNode;
if ( element == null )
{
return false;
}
return selector.Selects( element );
},
false
);
return element == null ? null : ( element as HtmlElementNode );
}
}
}

View File

@ -1,41 +0,0 @@
using System.Collections;
using System.Collections.Generic;
using System.Text.RegularExpressions;
namespace Rokojori
{
public class HtmlElementNodeName:HtmlElementSelector
{
public static readonly HtmlElementNodeName html = CreateNodeName( "html" );
public static readonly HtmlElementNodeName head = CreateNodeName( "head" );
public static readonly HtmlElementNodeName body = CreateNodeName( "body" );
public static readonly HtmlElementNodeName br = CreateNodeName( "br" );
public static readonly HtmlElementNodeName a = CreateNodeName( "a" );
public static readonly HtmlElementNodeName style = CreateNodeName( "style" );
public static readonly HtmlElementNodeName script = CreateNodeName( "script" );
string _nodeName;
public string selector => _nodeName;
public static HtmlElementNodeName CreateNodeName( string type )
{
var elementNodeType = new HtmlElementNodeName();
elementNodeType._nodeName = type;
return elementNodeType;
}
public HtmlElementNode Create( HtmlDocument document )
{
return new HtmlElementNode( document, _nodeName );
}
public bool Selects( HtmlElementNode elementNode )
{
return elementNode.nodeName == _nodeName;
}
}
}

View File

@ -1,14 +0,0 @@
using System.Collections;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using System.Text;
namespace Rokojori
{
public interface HtmlElementSelector
{
bool Selects( HtmlElementNode elementNode );
string selector { get; }
}
}

Some files were not shown because too many files have changed in this diff Show More