XML/SVG, Vector-Boolean Operations, Triangulations
This commit is contained in:
parent
fed16cb75c
commit
521d609dd1
|
@ -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
|
File diff suppressed because it is too large
Load Diff
|
@ -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.
|
|
@ -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).
|
|
@ -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).
|
|
@ -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).
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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; }
|
||||
}
|
||||
}
|
|
@ -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 };
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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; }
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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; }
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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; }
|
||||
}
|
||||
}
|
|
@ -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; }
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
@ -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; }
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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 < 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
//*/
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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; }
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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.
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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 |
|
@ -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
|
|
@ -9,8 +9,8 @@ namespace Rokojori
|
|||
{
|
||||
public enum FilePathType
|
||||
{
|
||||
ABSOLUTE,
|
||||
RELATIVE
|
||||
Absolute,
|
||||
Relative
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
|
@ -88,12 +88,12 @@ namespace Rokojori
|
|||
|
||||
public static FilePath Absolute( string path )
|
||||
{
|
||||
return FilePath.Create( path, FilePathType.ABSOLUTE );
|
||||
return FilePath.Create( path, FilePathType.Absolute );
|
||||
}
|
||||
|
||||
public FilePath MakeRelative( string path )
|
||||
{
|
||||
return FilePath.Create( path, FilePathType.RELATIVE, this );
|
||||
return FilePath.Create( path, FilePathType.Relative, this );
|
||||
}
|
||||
|
||||
public FilePath MakeAbsolutePathRelative( string absolutePath )
|
||||
|
@ -115,7 +115,7 @@ namespace Rokojori
|
|||
{
|
||||
var rp = new FilePath();
|
||||
|
||||
rp.type = FilePathType.RELATIVE;
|
||||
rp.type = FilePathType.Relative;
|
||||
rp.parent = absoluteParent;
|
||||
rp.path = Path.GetFileName( absolutePath );
|
||||
rp._fullPath = absolutePath;
|
||||
|
|
|
@ -52,12 +52,12 @@ namespace Rokojori
|
|||
var nodes = parser.sceneFile.nodes;
|
||||
RJLog.Log( "Nodes:", nodes.Count );
|
||||
|
||||
var doc = new HtmlDocument();
|
||||
var doc = new XMLDocument();
|
||||
var html = doc.documentElement;
|
||||
var body = html.querySelector( HtmlElementNodeName.body );
|
||||
var head = html.querySelector( HtmlElementNodeName.head );
|
||||
var body = html.querySelector( HTMLElementName.body );
|
||||
var head = html.querySelector( HTMLElementName.head );
|
||||
|
||||
head.AddScript(
|
||||
head.AddHTMLScript(
|
||||
@"
|
||||
|
||||
function main()
|
||||
|
@ -112,7 +112,7 @@ namespace Rokojori
|
|||
"
|
||||
);
|
||||
|
||||
head.AddStyle(
|
||||
head.AddHTMLStyle(
|
||||
@"
|
||||
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_Name = HtmlElementNodeName.CreateNodeName( "gd-name" );
|
||||
var GD_Type = HtmlElementNodeName.CreateNodeName( "gd-type" );
|
||||
var GD_Children = HtmlElementNodeName.CreateNodeName( "gd-children" );
|
||||
var GD_Node = XMLElementNodeName.Create( "gd-node" );
|
||||
var GD_Name = XMLElementNodeName.Create( "gd-name" );
|
||||
var GD_Type = XMLElementNodeName.Create( "gd-type" );
|
||||
var GD_Children = XMLElementNodeName.Create( "gd-children" );
|
||||
|
||||
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 ) );
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
||||
}
|
||||
}
|
|
@ -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 );
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -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 );
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
|
@ -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
Loading…
Reference in New Issue