From 521d609dd1479cece249d767269666d19476c218 Mon Sep 17 00:00:00 2001 From: Josef Date: Fri, 25 Oct 2024 08:28:58 +0200 Subject: [PATCH] XML/SVG, Vector-Boolean Operations, Triangulations --- External/Clipper/CHANGELOG.md | 413 ++ External/Clipper/Clipper.cs | 4913 +++++++++++++++++ External/Clipper/LICENSE.md | 24 + External/Clipper/README.md | 11 + External/Triangle.NET/CHANGELOG.md | 26 + External/Triangle.NET/README.md | 11 + External/Triangle.NET/Triangle/Behavior.cs | 248 + .../Triangle.NET/Triangle/Configuration.cs | 44 + External/Triangle.NET/Triangle/Enums.cs | 46 + .../Triangle.NET/Triangle/Geometry/Contour.cs | 247 + .../Triangle.NET/Triangle/Geometry/Edge.cs | 58 + .../Triangle/Geometry/ExtensionMethods.cs | 143 + .../Triangle.NET/Triangle/Geometry/IEdge.cs | 29 + .../Triangle/Geometry/IPolygon.cs | 93 + .../Triangle/Geometry/ISegment.cs | 26 + .../Triangle/Geometry/ITriangle.cs | 69 + .../Triangle.NET/Triangle/Geometry/Point.cs | 180 + .../Triangle.NET/Triangle/Geometry/Polygon.cs | 184 + .../Triangle/Geometry/Rectangle.cs | 189 + .../Triangle/Geometry/RegionPointer.cs | 63 + .../Triangle.NET/Triangle/Geometry/Segment.cs | 92 + .../Triangle.NET/Triangle/Geometry/Vertex.cs | 125 + .../Triangle.NET/Triangle/IO/DebugWriter.cs | 263 + .../Triangle.NET/Triangle/IO/FileProcessor.cs | 126 + .../Triangle.NET/Triangle/IO/IFileFormat.cs | 13 + .../Triangle.NET/Triangle/IO/IMeshFormat.cs | 38 + .../Triangle/IO/IPolygonFormat.cs | 38 + .../Triangle.NET/Triangle/IO/InputTriangle.cs | 85 + .../Triangle/IO/TriangleFormat.cs | 92 + .../Triangle/IO/TriangleReader.cs | 756 +++ .../Triangle/IO/TriangleWriter.cs | 459 ++ External/Triangle.NET/Triangle/IPredicates.cs | 22 + External/Triangle.NET/Triangle/Log.cs | 84 + .../Triangle.NET/Triangle/Logging/ILog.cs | 34 + .../Triangle.NET/Triangle/Logging/ILogItem.cs | 21 + .../Triangle.NET/Triangle/Logging/LogItem.cs | 53 + External/Triangle.NET/Triangle/Mesh.cs | 1768 ++++++ .../Triangle.NET/Triangle/MeshValidator.cs | 215 + .../Triangle/Meshing/Algorithm/Dwyer.cs | 694 +++ .../Triangle/Meshing/Algorithm/Incremental.cs | 191 + .../Triangle/Meshing/Algorithm/SweepLine.cs | 808 +++ .../Triangle/Meshing/ConstraintMesher.cs | 1228 ++++ .../Triangle/Meshing/ConstraintOptions.cs | 40 + .../Triangle/Meshing/Converter.cs | 487 ++ .../Triangle/Meshing/Data/BadSubseg.cs | 36 + .../Triangle/Meshing/Data/BadTriQueue.cs | 194 + .../Triangle/Meshing/Data/BadTriangle.cs | 34 + .../Triangle/Meshing/GenericMesher.cs | 233 + .../Triangle/Meshing/IConstraintMesher.cs | 26 + .../Triangle.NET/Triangle/Meshing/IMesh.cs | 57 + .../Triangle/Meshing/IQualityMesher.cs | 28 + .../Triangle/Meshing/ITriangulator.cs | 25 + .../Meshing/Iterators/EdgeIterator.cs | 101 + .../Meshing/Iterators/RegionIterator.cs | 135 + .../Meshing/Iterators/VertexCirculator.cs | 100 + .../Triangle/Meshing/QualityMesher.cs | 898 +++ .../Triangle/Meshing/QualityOptions.cs | 55 + External/Triangle.NET/Triangle/NewLocation.cs | 4115 ++++++++++++++ .../Triangle.NET/Triangle/RobustPredicates.cs | 1346 +++++ .../Triangle/Smoothing/ISmoother.cs | 19 + .../Triangle/Smoothing/SimpleSmoother.cs | 171 + .../Triangle/Smoothing/VoronoiFactory.cs | 201 + .../Triangle/Tools/AdjacencyMatrix.cs | 285 + .../Triangle/Tools/CuthillMcKee.cs | 685 +++ .../Triangle/Tools/Interpolation.cs | 105 + .../Triangle/Tools/IntersectionHelper.cs | 225 + .../Triangle/Tools/PolygonValidator.cs | 245 + .../Triangle/Tools/QualityMeasure.cs | 541 ++ .../Triangle.NET/Triangle/Tools/Statistic.cs | 528 ++ .../Triangle/Tools/TriangleQuadTree.cs | 426 ++ .../Triangle/Tools/VertexSorter.cs | 371 ++ .../Triangle/Topology/DCEL/DcelMesh.cs | 269 + .../Triangle/Topology/DCEL/Face.cs | 112 + .../Triangle/Topology/DCEL/HalfEdge.cs | 101 + .../Triangle/Topology/DCEL/Vertex.cs | 68 + .../Triangle.NET/Triangle/Topology/Osub.cs | 256 + .../Triangle.NET/Triangle/Topology/Otri.cs | 481 ++ .../Triangle/Topology/SubSegment.cs | 96 + .../Triangle/Topology/Triangle.cs | 128 + .../Triangle.NET/Triangle/TriangleLocator.cs | 363 ++ .../Triangle.NET/Triangle/TrianglePool.cs | 305 + .../Triangle.NET/Triangle/TriangleSampler.cs | 85 + .../Triangle/Voronoi/BoundedVoronoi.cs | 182 + .../Triangle/Voronoi/DefaultVoronoiFactory.cs | 35 + .../Triangle/Voronoi/IVoronoiFactory.cs | 18 + .../Voronoi/Legacy/BoundedVoronoiLegacy.cs | 692 +++ .../Triangle/Voronoi/Legacy/IVoronoi.cs | 32 + .../Triangle/Voronoi/Legacy/SimpleVoronoi.cs | 306 + .../Triangle/Voronoi/Legacy/VoronoiRegion.cs | 111 + .../Triangle/Voronoi/StandardVoronoi.cs | 66 + .../Triangle/Voronoi/VoronoiBase.cs | 291 + Icons/Rokojori-Action-Library-Logo.svg | 51 + Icons/Rokojori-Action-Library-Logo.svg.import | 37 + Runtime/Files/FilePath.cs | 12 +- Runtime/Godot/Scenes/SceneFileReader.cs | 22 +- Runtime/Html/HtmlAttributeNode.cs | 25 - Runtime/Html/HtmlDocument.cs | 41 - Runtime/Html/HtmlElementNode.cs | 140 - Runtime/Html/HtmlElementNodeName.cs | 41 - Runtime/Html/HtmlElementSelector.cs | 14 - Runtime/Html/HtmlWalker.cs | 42 - Runtime/Logging/Message.cs | 125 + Runtime/Logging/RJLog.cs | 49 +- Runtime/Math/Geometry/Curve3.cs | 22 +- Runtime/Math/Geometry/Path2.cs | 210 +- Runtime/Math/Geometry/Shape2.cs | 170 + Runtime/Navigation/NavigationMap.cs | 71 + Runtime/Navigation/NavigationMeshes.cs | 75 + Runtime/Procedural/MeshCreationTest.cs | 175 + Runtime/Procedural/MeshGeometry.cs | 210 +- Runtime/Random/RandomEngine.cs | 10 + Runtime/Text/JSON/JSONValue.cs | 2 +- Runtime/Text/Lexing/Lexer.cs | 17 +- Runtime/Text/Lexing/LexerEvent.cs | 27 + Runtime/Text/Lexing/LexerLibrary/XMLLexer.cs | 129 + .../Lexing/LexerLibrary/XMLRegexExtension.cs | 24 + Runtime/Text/Lexing/LexerMatcher.cs | 49 +- Runtime/Text/Lexing/LexerMatcherLibrary.cs | 35 +- Runtime/Text/RegexExtensions.cs | 4 +- Runtime/Text/RegexMatches.cs | 44 + Runtime/Text/RegexUtility.cs | 15 +- Runtime/Tools/Lists.cs | 36 +- Runtime/XML/HTML/HTMLElementName.cs | 17 + Runtime/XML/Nodes/XMLAttributeNode.cs | 51 + Runtime/XML/Nodes/XMLCDataSectionNode.cs | 18 + Runtime/XML/Nodes/XMLCommentNode.cs | 18 + Runtime/XML/Nodes/XMLDocument.cs | 107 + Runtime/XML/Nodes/XMLDocumentFragmentNode.cs | 12 + Runtime/XML/Nodes/XMLDocumentTypeNode.cs | 18 + Runtime/XML/Nodes/XMLElementNode.cs | 191 + .../XML/Nodes/XMLProcessingInstructionNode.cs | 18 + .../Nodes/XMLTextNode.cs} | 4 +- Runtime/XML/SVG/Arc-Test.svg | 68 + Runtime/XML/SVG/Arc-Test.svg.import | 37 + Runtime/XML/SVG/SVGAttributeName.cs | 11 + Runtime/XML/SVG/SVGElementName.cs | 15 + Runtime/XML/SVG/SVGPathCommand.cs | 57 + Runtime/XML/SVG/SVGPathExtractor.cs | 396 ++ Runtime/XML/SVG/SVGPathParser.cs | 164 + Runtime/XML/XMLAttributeName.cs | 64 + Runtime/XML/XMLElementNodeName.cs | 57 + Runtime/XML/XMLElementSelector.cs | 22 + Runtime/{Html/HtmlNode.cs => XML/XMLNode.cs} | 23 +- Runtime/XML/XMLQuery.cs | 58 + Runtime/XML/XMLReader.cs | 238 + Runtime/XML/XMLReaderTest.cs | 85 + .../XMLSerializer.cs} | 72 +- Runtime/XML/XMLWalker.cs | 52 + 148 files changed, 33277 insertions(+), 451 deletions(-) create mode 100644 External/Clipper/CHANGELOG.md create mode 100644 External/Clipper/Clipper.cs create mode 100644 External/Clipper/LICENSE.md create mode 100644 External/Clipper/README.md create mode 100644 External/Triangle.NET/CHANGELOG.md create mode 100644 External/Triangle.NET/README.md create mode 100644 External/Triangle.NET/Triangle/Behavior.cs create mode 100644 External/Triangle.NET/Triangle/Configuration.cs create mode 100644 External/Triangle.NET/Triangle/Enums.cs create mode 100644 External/Triangle.NET/Triangle/Geometry/Contour.cs create mode 100644 External/Triangle.NET/Triangle/Geometry/Edge.cs create mode 100644 External/Triangle.NET/Triangle/Geometry/ExtensionMethods.cs create mode 100644 External/Triangle.NET/Triangle/Geometry/IEdge.cs create mode 100644 External/Triangle.NET/Triangle/Geometry/IPolygon.cs create mode 100644 External/Triangle.NET/Triangle/Geometry/ISegment.cs create mode 100644 External/Triangle.NET/Triangle/Geometry/ITriangle.cs create mode 100644 External/Triangle.NET/Triangle/Geometry/Point.cs create mode 100644 External/Triangle.NET/Triangle/Geometry/Polygon.cs create mode 100644 External/Triangle.NET/Triangle/Geometry/Rectangle.cs create mode 100644 External/Triangle.NET/Triangle/Geometry/RegionPointer.cs create mode 100644 External/Triangle.NET/Triangle/Geometry/Segment.cs create mode 100644 External/Triangle.NET/Triangle/Geometry/Vertex.cs create mode 100644 External/Triangle.NET/Triangle/IO/DebugWriter.cs create mode 100644 External/Triangle.NET/Triangle/IO/FileProcessor.cs create mode 100644 External/Triangle.NET/Triangle/IO/IFileFormat.cs create mode 100644 External/Triangle.NET/Triangle/IO/IMeshFormat.cs create mode 100644 External/Triangle.NET/Triangle/IO/IPolygonFormat.cs create mode 100644 External/Triangle.NET/Triangle/IO/InputTriangle.cs create mode 100644 External/Triangle.NET/Triangle/IO/TriangleFormat.cs create mode 100644 External/Triangle.NET/Triangle/IO/TriangleReader.cs create mode 100644 External/Triangle.NET/Triangle/IO/TriangleWriter.cs create mode 100644 External/Triangle.NET/Triangle/IPredicates.cs create mode 100644 External/Triangle.NET/Triangle/Log.cs create mode 100644 External/Triangle.NET/Triangle/Logging/ILog.cs create mode 100644 External/Triangle.NET/Triangle/Logging/ILogItem.cs create mode 100644 External/Triangle.NET/Triangle/Logging/LogItem.cs create mode 100644 External/Triangle.NET/Triangle/Mesh.cs create mode 100644 External/Triangle.NET/Triangle/MeshValidator.cs create mode 100644 External/Triangle.NET/Triangle/Meshing/Algorithm/Dwyer.cs create mode 100644 External/Triangle.NET/Triangle/Meshing/Algorithm/Incremental.cs create mode 100644 External/Triangle.NET/Triangle/Meshing/Algorithm/SweepLine.cs create mode 100644 External/Triangle.NET/Triangle/Meshing/ConstraintMesher.cs create mode 100644 External/Triangle.NET/Triangle/Meshing/ConstraintOptions.cs create mode 100644 External/Triangle.NET/Triangle/Meshing/Converter.cs create mode 100644 External/Triangle.NET/Triangle/Meshing/Data/BadSubseg.cs create mode 100644 External/Triangle.NET/Triangle/Meshing/Data/BadTriQueue.cs create mode 100644 External/Triangle.NET/Triangle/Meshing/Data/BadTriangle.cs create mode 100644 External/Triangle.NET/Triangle/Meshing/GenericMesher.cs create mode 100644 External/Triangle.NET/Triangle/Meshing/IConstraintMesher.cs create mode 100644 External/Triangle.NET/Triangle/Meshing/IMesh.cs create mode 100644 External/Triangle.NET/Triangle/Meshing/IQualityMesher.cs create mode 100644 External/Triangle.NET/Triangle/Meshing/ITriangulator.cs create mode 100644 External/Triangle.NET/Triangle/Meshing/Iterators/EdgeIterator.cs create mode 100644 External/Triangle.NET/Triangle/Meshing/Iterators/RegionIterator.cs create mode 100644 External/Triangle.NET/Triangle/Meshing/Iterators/VertexCirculator.cs create mode 100644 External/Triangle.NET/Triangle/Meshing/QualityMesher.cs create mode 100644 External/Triangle.NET/Triangle/Meshing/QualityOptions.cs create mode 100644 External/Triangle.NET/Triangle/NewLocation.cs create mode 100644 External/Triangle.NET/Triangle/RobustPredicates.cs create mode 100644 External/Triangle.NET/Triangle/Smoothing/ISmoother.cs create mode 100644 External/Triangle.NET/Triangle/Smoothing/SimpleSmoother.cs create mode 100644 External/Triangle.NET/Triangle/Smoothing/VoronoiFactory.cs create mode 100644 External/Triangle.NET/Triangle/Tools/AdjacencyMatrix.cs create mode 100644 External/Triangle.NET/Triangle/Tools/CuthillMcKee.cs create mode 100644 External/Triangle.NET/Triangle/Tools/Interpolation.cs create mode 100644 External/Triangle.NET/Triangle/Tools/IntersectionHelper.cs create mode 100644 External/Triangle.NET/Triangle/Tools/PolygonValidator.cs create mode 100644 External/Triangle.NET/Triangle/Tools/QualityMeasure.cs create mode 100644 External/Triangle.NET/Triangle/Tools/Statistic.cs create mode 100644 External/Triangle.NET/Triangle/Tools/TriangleQuadTree.cs create mode 100644 External/Triangle.NET/Triangle/Tools/VertexSorter.cs create mode 100644 External/Triangle.NET/Triangle/Topology/DCEL/DcelMesh.cs create mode 100644 External/Triangle.NET/Triangle/Topology/DCEL/Face.cs create mode 100644 External/Triangle.NET/Triangle/Topology/DCEL/HalfEdge.cs create mode 100644 External/Triangle.NET/Triangle/Topology/DCEL/Vertex.cs create mode 100644 External/Triangle.NET/Triangle/Topology/Osub.cs create mode 100644 External/Triangle.NET/Triangle/Topology/Otri.cs create mode 100644 External/Triangle.NET/Triangle/Topology/SubSegment.cs create mode 100644 External/Triangle.NET/Triangle/Topology/Triangle.cs create mode 100644 External/Triangle.NET/Triangle/TriangleLocator.cs create mode 100644 External/Triangle.NET/Triangle/TrianglePool.cs create mode 100644 External/Triangle.NET/Triangle/TriangleSampler.cs create mode 100644 External/Triangle.NET/Triangle/Voronoi/BoundedVoronoi.cs create mode 100644 External/Triangle.NET/Triangle/Voronoi/DefaultVoronoiFactory.cs create mode 100644 External/Triangle.NET/Triangle/Voronoi/IVoronoiFactory.cs create mode 100644 External/Triangle.NET/Triangle/Voronoi/Legacy/BoundedVoronoiLegacy.cs create mode 100644 External/Triangle.NET/Triangle/Voronoi/Legacy/IVoronoi.cs create mode 100644 External/Triangle.NET/Triangle/Voronoi/Legacy/SimpleVoronoi.cs create mode 100644 External/Triangle.NET/Triangle/Voronoi/Legacy/VoronoiRegion.cs create mode 100644 External/Triangle.NET/Triangle/Voronoi/StandardVoronoi.cs create mode 100644 External/Triangle.NET/Triangle/Voronoi/VoronoiBase.cs create mode 100644 Icons/Rokojori-Action-Library-Logo.svg create mode 100644 Icons/Rokojori-Action-Library-Logo.svg.import delete mode 100644 Runtime/Html/HtmlAttributeNode.cs delete mode 100644 Runtime/Html/HtmlDocument.cs delete mode 100644 Runtime/Html/HtmlElementNode.cs delete mode 100644 Runtime/Html/HtmlElementNodeName.cs delete mode 100644 Runtime/Html/HtmlElementSelector.cs delete mode 100644 Runtime/Html/HtmlWalker.cs create mode 100644 Runtime/Logging/Message.cs create mode 100644 Runtime/Math/Geometry/Shape2.cs create mode 100644 Runtime/Navigation/NavigationMap.cs create mode 100644 Runtime/Navigation/NavigationMeshes.cs create mode 100644 Runtime/Procedural/MeshCreationTest.cs create mode 100644 Runtime/Text/Lexing/LexerLibrary/XMLLexer.cs create mode 100644 Runtime/Text/Lexing/LexerLibrary/XMLRegexExtension.cs create mode 100644 Runtime/Text/RegexMatches.cs create mode 100644 Runtime/XML/HTML/HTMLElementName.cs create mode 100644 Runtime/XML/Nodes/XMLAttributeNode.cs create mode 100644 Runtime/XML/Nodes/XMLCDataSectionNode.cs create mode 100644 Runtime/XML/Nodes/XMLCommentNode.cs create mode 100644 Runtime/XML/Nodes/XMLDocument.cs create mode 100644 Runtime/XML/Nodes/XMLDocumentFragmentNode.cs create mode 100644 Runtime/XML/Nodes/XMLDocumentTypeNode.cs create mode 100644 Runtime/XML/Nodes/XMLElementNode.cs create mode 100644 Runtime/XML/Nodes/XMLProcessingInstructionNode.cs rename Runtime/{Html/HtmlTextNode.cs => XML/Nodes/XMLTextNode.cs} (63%) create mode 100644 Runtime/XML/SVG/Arc-Test.svg create mode 100644 Runtime/XML/SVG/Arc-Test.svg.import create mode 100644 Runtime/XML/SVG/SVGAttributeName.cs create mode 100644 Runtime/XML/SVG/SVGElementName.cs create mode 100644 Runtime/XML/SVG/SVGPathCommand.cs create mode 100644 Runtime/XML/SVG/SVGPathExtractor.cs create mode 100644 Runtime/XML/SVG/SVGPathParser.cs create mode 100644 Runtime/XML/XMLAttributeName.cs create mode 100644 Runtime/XML/XMLElementNodeName.cs create mode 100644 Runtime/XML/XMLElementSelector.cs rename Runtime/{Html/HtmlNode.cs => XML/XMLNode.cs} (57%) create mode 100644 Runtime/XML/XMLQuery.cs create mode 100644 Runtime/XML/XMLReader.cs create mode 100644 Runtime/XML/XMLReaderTest.cs rename Runtime/{Html/HtmlSerializer.cs => XML/XMLSerializer.cs} (62%) create mode 100644 Runtime/XML/XMLWalker.cs diff --git a/External/Clipper/CHANGELOG.md b/External/Clipper/CHANGELOG.md new file mode 100644 index 0000000..a3ed26f --- /dev/null +++ b/External/Clipper/CHANGELOG.md @@ -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 \ No newline at end of file diff --git a/External/Clipper/Clipper.cs b/External/Clipper/Clipper.cs new file mode 100644 index 0000000..9f014b5 --- /dev/null +++ b/External/Clipper/Clipper.cs @@ -0,0 +1,4913 @@ +/******************************************************************************* +* * +* Author : Angus Johnson * +* Version : 6.4.2 * +* Date : 27 February 2017 * +* Website : http://www.angusj.com * +* Copyright : Angus Johnson 2010-2017 * +* * +* License: * +* Use, modification & distribution is subject to Boost Software License Ver 1. * +* http://www.boost.org/LICENSE_1_0.txt * +* * +* Attributions: * +* The code in this library is an extension of Bala Vatti's clipping algorithm: * +* "A generic solution to polygon clipping" * +* Communications of the ACM, Vol 35, Issue 7 (July 1992) pp 56-63. * +* http://portal.acm.org/citation.cfm?id=129906 * +* * +* Computer graphics and geometric modeling: implementation and algorithms * +* By Max K. Agoston * +* Springer; 1 edition (January 4, 2005) * +* http://books.google.com/books?q=vatti+clipping+agoston * +* * +* See also: * +* "Polygon Offsetting by Computing Winding Numbers" * +* Paper no. DETC2005-85513 pp. 565-575 * +* ASME 2005 International Design Engineering Technical Conferences * +* and Computers and Information in Engineering Conference (IDETC/CIE2005) * +* September 24-28, 2005 , Long Beach, California, USA * +* http://www.me.berkeley.edu/~mcmains/pubs/DAC05OffsetPolygon.pdf * +* * +*******************************************************************************/ + +/******************************************************************************* +* * +* This is a translation of the Delphi Clipper library and the naming style * +* used has retained a Delphi flavour. * +* * +*******************************************************************************/ + +//use_int32: When enabled 32bit ints are used instead of 64bit ints. This +//improve performance but coordinate values are limited to the range +/- 46340 +//#define use_int32 + +//use_xyz: adds a Z member to IntPoint. Adds a minor cost to performance. +//#define use_xyz + +//use_lines: Enables open path clipping. Adds a very minor cost to performance. +#define use_lines + + +using System; +using System.Collections.Generic; +//using System.Text; //for Int128.AsString() & StringBuilder +//using System.IO; //debugging with streamReader & StreamWriter +//using System.Windows.Forms; //debugging to clipboard + +namespace ClipperLib +{ + +#if use_int32 + using cInt = Int32; +#else + using cInt = Int64; +#endif + + using Path = List; + using Paths = List>; + + public struct DoublePoint + { + public double X; + public double Y; + + public DoublePoint(double x = 0, double y = 0) + { + this.X = x; this.Y = y; + } + public DoublePoint(DoublePoint dp) + { + this.X = dp.X; this.Y = dp.Y; + } + public DoublePoint(IntPoint ip) + { + this.X = ip.X; this.Y = ip.Y; + } + }; + + + //------------------------------------------------------------------------------ + // PolyTree & PolyNode classes + //------------------------------------------------------------------------------ + + public class PolyTree : PolyNode + { + internal List m_AllPolys = new List(); + + //The GC probably handles this cleanup more efficiently ... + //~PolyTree(){Clear();} + + public void Clear() + { + for (int i = 0; i < m_AllPolys.Count; i++) + m_AllPolys[i] = null; + m_AllPolys.Clear(); + m_Childs.Clear(); + } + + public PolyNode GetFirst() + { + if (m_Childs.Count > 0) + return m_Childs[0]; + else + return null; + } + + public int Total + { + get + { + int result = m_AllPolys.Count; + //with negative offsets, ignore the hidden outer polygon ... + if (result > 0 && m_Childs[0] != m_AllPolys[0]) result--; + return result; + } + } + + } + + public class PolyNode + { + internal PolyNode m_Parent; + internal Path m_polygon = new Path(); + internal int m_Index; + internal JoinType m_jointype; + internal EndType m_endtype; + internal List m_Childs = new List(); + + private bool IsHoleNode() + { + bool result = true; + PolyNode node = m_Parent; + while (node != null) + { + result = !result; + node = node.m_Parent; + } + return result; + } + + public int ChildCount + { + get { return m_Childs.Count; } + } + + public Path Contour + { + get { return m_polygon; } + } + + internal void AddChild(PolyNode Child) + { + int cnt = m_Childs.Count; + m_Childs.Add(Child); + Child.m_Parent = this; + Child.m_Index = cnt; + } + + public PolyNode GetNext() + { + if (m_Childs.Count > 0) + return m_Childs[0]; + else + return GetNextSiblingUp(); + } + + internal PolyNode GetNextSiblingUp() + { + if (m_Parent == null) + return null; + else if (m_Index == m_Parent.m_Childs.Count - 1) + return m_Parent.GetNextSiblingUp(); + else + return m_Parent.m_Childs[m_Index + 1]; + } + + public List Childs + { + get { return m_Childs; } + } + + public PolyNode Parent + { + get { return m_Parent; } + } + + public bool IsHole + { + get { return IsHoleNode(); } + } + + public bool IsOpen { get; set; } + } + + + //------------------------------------------------------------------------------ + // Int128 struct (enables safe math on signed 64bit integers) + // eg Int128 val1((Int64)9223372036854775807); //ie 2^63 -1 + // Int128 val2((Int64)9223372036854775807); + // Int128 val3 = val1 * val2; + // val3.ToString => "85070591730234615847396907784232501249" (8.5e+37) + //------------------------------------------------------------------------------ + + internal struct Int128 + { + private Int64 hi; + private UInt64 lo; + + public Int128(Int64 _lo) + { + lo = (UInt64)_lo; + if (_lo < 0) hi = -1; + else hi = 0; + } + + public Int128(Int64 _hi, UInt64 _lo) + { + lo = _lo; + hi = _hi; + } + + public Int128(Int128 val) + { + hi = val.hi; + lo = val.lo; + } + + public bool IsNegative() + { + return hi < 0; + } + + public static bool operator ==(Int128 val1, Int128 val2) + { + if ((object)val1 == (object)val2) return true; + else if ((object)val1 == null || (object)val2 == null) return false; + return (val1.hi == val2.hi && val1.lo == val2.lo); + } + + public static bool operator !=(Int128 val1, Int128 val2) + { + return !(val1 == val2); + } + + public override bool Equals(System.Object obj) + { + if (obj == null || !(obj is Int128)) + return false; + Int128 i128 = (Int128)obj; + return (i128.hi == hi && i128.lo == lo); + } + + public override int GetHashCode() + { + return hi.GetHashCode() ^ lo.GetHashCode(); + } + + public static bool operator >(Int128 val1, Int128 val2) + { + if (val1.hi != val2.hi) + return val1.hi > val2.hi; + else + return val1.lo > val2.lo; + } + + public static bool operator <(Int128 val1, Int128 val2) + { + if (val1.hi != val2.hi) + return val1.hi < val2.hi; + else + return val1.lo < val2.lo; + } + + public static Int128 operator +(Int128 lhs, Int128 rhs) + { + lhs.hi += rhs.hi; + lhs.lo += rhs.lo; + if (lhs.lo < rhs.lo) lhs.hi++; + return lhs; + } + + public static Int128 operator -(Int128 lhs, Int128 rhs) + { + return lhs + -rhs; + } + + public static Int128 operator -(Int128 val) + { + if (val.lo == 0) + return new Int128(-val.hi, 0); + else + return new Int128(~val.hi, ~val.lo + 1); + } + + public static explicit operator double(Int128 val) + { + const double shift64 = 18446744073709551616.0; //2^64 + if (val.hi < 0) + { + if (val.lo == 0) + return (double)val.hi * shift64; + else + return -(double)(~val.lo + ~val.hi * shift64); + } + else + return (double)(val.lo + val.hi * shift64); + } + + //nb: Constructing two new Int128 objects every time we want to multiply longs + //is slow. So, although calling the Int128Mul method doesn't look as clean, the + //code runs significantly faster than if we'd used the * operator. + + public static Int128 Int128Mul(Int64 lhs, Int64 rhs) + { + bool negate = (lhs < 0) != (rhs < 0); + if (lhs < 0) lhs = -lhs; + if (rhs < 0) rhs = -rhs; + UInt64 int1Hi = (UInt64)lhs >> 32; + UInt64 int1Lo = (UInt64)lhs & 0xFFFFFFFF; + UInt64 int2Hi = (UInt64)rhs >> 32; + UInt64 int2Lo = (UInt64)rhs & 0xFFFFFFFF; + + //nb: see comments in clipper.pas + UInt64 a = int1Hi * int2Hi; + UInt64 b = int1Lo * int2Lo; + UInt64 c = int1Hi * int2Lo + int1Lo * int2Hi; + + UInt64 lo; + Int64 hi; + hi = (Int64)(a + (c >> 32)); + + unchecked { lo = (c << 32) + b; } + if (lo < b) hi++; + Int128 result = new Int128(hi, lo); + return negate ? -result : result; + } + + }; + + //------------------------------------------------------------------------------ + //------------------------------------------------------------------------------ + + public struct IntPoint + { + public cInt X; + public cInt Y; +#if use_xyz + public cInt Z; + + public IntPoint(cInt x, cInt y, cInt z = 0) + { + this.X = x; this.Y = y; this.Z = z; + } + + public IntPoint(double x, double y, double z = 0) + { + this.X = (cInt)x; this.Y = (cInt)y; this.Z = (cInt)z; + } + + public IntPoint(DoublePoint dp) + { + this.X = (cInt)dp.X; this.Y = (cInt)dp.Y; this.Z = 0; + } + + public IntPoint(IntPoint pt) + { + this.X = pt.X; this.Y = pt.Y; this.Z = pt.Z; + } +#else + public IntPoint(cInt X, cInt Y) + { + this.X = X; this.Y = Y; + } + public IntPoint(double x, double y) + { + this.X = (cInt)x; this.Y = (cInt)y; + } + + public IntPoint(IntPoint pt) + { + this.X = pt.X; this.Y = pt.Y; + } +#endif + + public static bool operator ==(IntPoint a, IntPoint b) + { + return a.X == b.X && a.Y == b.Y; + } + + public static bool operator !=(IntPoint a, IntPoint b) + { + return a.X != b.X || a.Y != b.Y; + } + + public override bool Equals(object obj) + { + if (obj == null) return false; + if (obj is IntPoint) + { + IntPoint a = (IntPoint)obj; + return (X == a.X) && (Y == a.Y); + } + else return false; + } + + public override int GetHashCode() + { + //simply prevents a compiler warning + return base.GetHashCode(); + } + + }// end struct IntPoint + + public struct IntRect + { + public cInt left; + public cInt top; + public cInt right; + public cInt bottom; + + public IntRect(cInt l, cInt t, cInt r, cInt b) + { + this.left = l; this.top = t; + this.right = r; this.bottom = b; + } + public IntRect(IntRect ir) + { + this.left = ir.left; this.top = ir.top; + this.right = ir.right; this.bottom = ir.bottom; + } + } + + public enum ClipType { ctIntersection, ctUnion, ctDifference, ctXor }; + public enum PolyType { ptSubject, ptClip }; + + //By far the most widely used winding rules for polygon filling are + //EvenOdd & NonZero (GDI, GDI+, XLib, OpenGL, Cairo, AGG, Quartz, SVG, Gr32) + //Others rules include Positive, Negative and ABS_GTR_EQ_TWO (only in OpenGL) + //see http://glprogramming.com/red/chapter11.html + public enum PolyFillType { pftEvenOdd, pftNonZero, pftPositive, pftNegative }; + + public enum JoinType { jtSquare, jtRound, jtMiter }; + public enum EndType { etClosedPolygon, etClosedLine, etOpenButt, etOpenSquare, etOpenRound }; + + internal enum EdgeSide {esLeft, esRight}; + internal enum Direction {dRightToLeft, dLeftToRight}; + + internal class TEdge { + internal IntPoint Bot; + internal IntPoint Curr; //current (updated for every new scanbeam) + internal IntPoint Top; + internal IntPoint Delta; + internal double Dx; + internal PolyType PolyTyp; + internal EdgeSide Side; //side only refers to current side of solution poly + internal int WindDelta; //1 or -1 depending on winding direction + internal int WindCnt; + internal int WindCnt2; //winding count of the opposite polytype + internal int OutIdx; + internal TEdge Next; + internal TEdge Prev; + internal TEdge NextInLML; + internal TEdge NextInAEL; + internal TEdge PrevInAEL; + internal TEdge NextInSEL; + internal TEdge PrevInSEL; + }; + + public class IntersectNode + { + internal TEdge Edge1; + internal TEdge Edge2; + internal IntPoint Pt; + }; + + public class MyIntersectNodeSort : IComparer + { + public int Compare(IntersectNode node1, IntersectNode node2) + { + cInt i = node2.Pt.Y - node1.Pt.Y; + if (i > 0) return 1; + else if (i < 0) return -1; + else return 0; + } + } + + internal class LocalMinima + { + internal cInt Y; + internal TEdge LeftBound; + internal TEdge RightBound; + internal LocalMinima Next; + }; + + internal class Scanbeam + { + internal cInt Y; + internal Scanbeam Next; + }; + + internal class Maxima + { + internal cInt X; + internal Maxima Next; + internal Maxima Prev; + }; + + //OutRec: contains a path in the clipping solution. Edges in the AEL will + //carry a pointer to an OutRec when they are part of the clipping solution. + internal class OutRec + { + internal int Idx; + internal bool IsHole; + internal bool IsOpen; + internal OutRec FirstLeft; //see comments in clipper.pas + internal OutPt Pts; + internal OutPt BottomPt; + internal PolyNode PolyNode; + }; + + internal class OutPt + { + internal int Idx; + internal IntPoint Pt; + internal OutPt Next; + internal OutPt Prev; + }; + + internal class Join + { + internal OutPt OutPt1; + internal OutPt OutPt2; + internal IntPoint OffPt; + }; + + public class ClipperBase + { + internal const double horizontal = -3.4E+38; + internal const int Skip = -2; + internal const int Unassigned = -1; + internal const double tolerance = 1.0E-20; + internal static bool near_zero(double val){return (val > -tolerance) && (val < tolerance);} + +#if use_int32 + public const cInt loRange = 0x7FFF; + public const cInt hiRange = 0x7FFF; +#else + public const cInt loRange = 0x3FFFFFFF; + public const cInt hiRange = 0x3FFFFFFFFFFFFFFFL; +#endif + + internal LocalMinima m_MinimaList; + internal LocalMinima m_CurrentLM; + internal List> m_edges = new List>(); + internal Scanbeam m_Scanbeam; + internal List m_PolyOuts; + internal TEdge m_ActiveEdges; + internal bool m_UseFullRange; + internal bool m_HasOpenPaths; + + //------------------------------------------------------------------------------ + + public bool PreserveCollinear + { + get; + set; + } + //------------------------------------------------------------------------------ + + public void Swap(ref cInt val1, ref cInt val2) + { + cInt tmp = val1; + val1 = val2; + val2 = tmp; + } + //------------------------------------------------------------------------------ + + internal static bool IsHorizontal(TEdge e) + { + return e.Delta.Y == 0; + } + //------------------------------------------------------------------------------ + + internal bool PointIsVertex(IntPoint pt, OutPt pp) + { + OutPt pp2 = pp; + do + { + if (pp2.Pt == pt) return true; + pp2 = pp2.Next; + } + while (pp2 != pp); + return false; + } + //------------------------------------------------------------------------------ + + internal bool PointOnLineSegment(IntPoint pt, + IntPoint linePt1, IntPoint linePt2, bool UseFullRange) + { + if (UseFullRange) + return ((pt.X == linePt1.X) && (pt.Y == linePt1.Y)) || + ((pt.X == linePt2.X) && (pt.Y == linePt2.Y)) || + (((pt.X > linePt1.X) == (pt.X < linePt2.X)) && + ((pt.Y > linePt1.Y) == (pt.Y < linePt2.Y)) && + ((Int128.Int128Mul((pt.X - linePt1.X), (linePt2.Y - linePt1.Y)) == + Int128.Int128Mul((linePt2.X - linePt1.X), (pt.Y - linePt1.Y))))); + else + return ((pt.X == linePt1.X) && (pt.Y == linePt1.Y)) || + ((pt.X == linePt2.X) && (pt.Y == linePt2.Y)) || + (((pt.X > linePt1.X) == (pt.X < linePt2.X)) && + ((pt.Y > linePt1.Y) == (pt.Y < linePt2.Y)) && + ((pt.X - linePt1.X) * (linePt2.Y - linePt1.Y) == + (linePt2.X - linePt1.X) * (pt.Y - linePt1.Y))); + } + //------------------------------------------------------------------------------ + + internal bool PointOnPolygon(IntPoint pt, OutPt pp, bool UseFullRange) + { + OutPt pp2 = pp; + while (true) + { + if (PointOnLineSegment(pt, pp2.Pt, pp2.Next.Pt, UseFullRange)) + return true; + pp2 = pp2.Next; + if (pp2 == pp) break; + } + return false; + } + //------------------------------------------------------------------------------ + + internal static bool SlopesEqual(TEdge e1, TEdge e2, bool UseFullRange) + { + if (UseFullRange) + return Int128.Int128Mul(e1.Delta.Y, e2.Delta.X) == + Int128.Int128Mul(e1.Delta.X, e2.Delta.Y); + else return (cInt)(e1.Delta.Y) * (e2.Delta.X) == + (cInt)(e1.Delta.X) * (e2.Delta.Y); + } + //------------------------------------------------------------------------------ + + internal static bool SlopesEqual(IntPoint pt1, IntPoint pt2, + IntPoint pt3, bool UseFullRange) + { + if (UseFullRange) + return Int128.Int128Mul(pt1.Y - pt2.Y, pt2.X - pt3.X) == + Int128.Int128Mul(pt1.X - pt2.X, pt2.Y - pt3.Y); + else return + (cInt)(pt1.Y - pt2.Y) * (pt2.X - pt3.X) - (cInt)(pt1.X - pt2.X) * (pt2.Y - pt3.Y) == 0; + } + //------------------------------------------------------------------------------ + + internal static bool SlopesEqual(IntPoint pt1, IntPoint pt2, + IntPoint pt3, IntPoint pt4, bool UseFullRange) + { + if (UseFullRange) + return Int128.Int128Mul(pt1.Y - pt2.Y, pt3.X - pt4.X) == + Int128.Int128Mul(pt1.X - pt2.X, pt3.Y - pt4.Y); + else return + (cInt)(pt1.Y - pt2.Y) * (pt3.X - pt4.X) - (cInt)(pt1.X - pt2.X) * (pt3.Y - pt4.Y) == 0; + } + //------------------------------------------------------------------------------ + + internal ClipperBase() //constructor (nb: no external instantiation) + { + m_MinimaList = null; + m_CurrentLM = null; + m_UseFullRange = false; + m_HasOpenPaths = false; + } + //------------------------------------------------------------------------------ + + public virtual void Clear() + { + DisposeLocalMinimaList(); + for (int i = 0; i < m_edges.Count; ++i) + { + for (int j = 0; j < m_edges[i].Count; ++j) m_edges[i][j] = null; + m_edges[i].Clear(); + } + m_edges.Clear(); + m_UseFullRange = false; + m_HasOpenPaths = false; + } + //------------------------------------------------------------------------------ + + private void DisposeLocalMinimaList() + { + while( m_MinimaList != null ) + { + LocalMinima tmpLm = m_MinimaList.Next; + m_MinimaList = null; + m_MinimaList = tmpLm; + } + m_CurrentLM = null; + } + //------------------------------------------------------------------------------ + + void RangeTest(IntPoint Pt, ref bool useFullRange) + { + if (useFullRange) + { + if (Pt.X > hiRange || Pt.Y > hiRange || -Pt.X > hiRange || -Pt.Y > hiRange) + throw new ClipperException("Coordinate outside allowed range"); + } + else if (Pt.X > loRange || Pt.Y > loRange || -Pt.X > loRange || -Pt.Y > loRange) + { + useFullRange = true; + RangeTest(Pt, ref useFullRange); + } + } + //------------------------------------------------------------------------------ + + private void InitEdge(TEdge e, TEdge eNext, + TEdge ePrev, IntPoint pt) + { + e.Next = eNext; + e.Prev = ePrev; + e.Curr = pt; + e.OutIdx = Unassigned; + } + //------------------------------------------------------------------------------ + + private void InitEdge2(TEdge e, PolyType polyType) + { + if (e.Curr.Y >= e.Next.Curr.Y) + { + e.Bot = e.Curr; + e.Top = e.Next.Curr; + } + else + { + e.Top = e.Curr; + e.Bot = e.Next.Curr; + } + SetDx(e); + e.PolyTyp = polyType; + } + //------------------------------------------------------------------------------ + + private TEdge FindNextLocMin(TEdge E) + { + TEdge E2; + for (;;) + { + while (E.Bot != E.Prev.Bot || E.Curr == E.Top) E = E.Next; + if (E.Dx != horizontal && E.Prev.Dx != horizontal) break; + while (E.Prev.Dx == horizontal) E = E.Prev; + E2 = E; + while (E.Dx == horizontal) E = E.Next; + if (E.Top.Y == E.Prev.Bot.Y) continue; //ie just an intermediate horz. + if (E2.Prev.Bot.X < E.Bot.X) E = E2; + break; + } + return E; + } + //------------------------------------------------------------------------------ + + private TEdge ProcessBound(TEdge E, bool LeftBoundIsForward) + { + TEdge EStart, Result = E; + TEdge Horz; + + if (Result.OutIdx == Skip) + { + //check if there are edges beyond the skip edge in the bound and if so + //create another LocMin and calling ProcessBound once more ... + E = Result; + if (LeftBoundIsForward) + { + while (E.Top.Y == E.Next.Bot.Y) E = E.Next; + while (E != Result && E.Dx == horizontal) E = E.Prev; + } + else + { + while (E.Top.Y == E.Prev.Bot.Y) E = E.Prev; + while (E != Result && E.Dx == horizontal) E = E.Next; + } + if (E == Result) + { + if (LeftBoundIsForward) Result = E.Next; + else Result = E.Prev; + } + else + { + //there are more edges in the bound beyond result starting with E + if (LeftBoundIsForward) + E = Result.Next; + else + E = Result.Prev; + LocalMinima locMin = new LocalMinima(); + locMin.Next = null; + locMin.Y = E.Bot.Y; + locMin.LeftBound = null; + locMin.RightBound = E; + E.WindDelta = 0; + Result = ProcessBound(E, LeftBoundIsForward); + InsertLocalMinima(locMin); + } + return Result; + } + + if (E.Dx == horizontal) + { + //We need to be careful with open paths because this may not be a + //true local minima (ie E may be following a skip edge). + //Also, consecutive horz. edges may start heading left before going right. + if (LeftBoundIsForward) EStart = E.Prev; + else EStart = E.Next; + if (EStart.Dx == horizontal) //ie an adjoining horizontal skip edge + { + if (EStart.Bot.X != E.Bot.X && EStart.Top.X != E.Bot.X) + ReverseHorizontal(E); + } + else if (EStart.Bot.X != E.Bot.X) + ReverseHorizontal(E); + } + + EStart = E; + if (LeftBoundIsForward) + { + while (Result.Top.Y == Result.Next.Bot.Y && Result.Next.OutIdx != Skip) + Result = Result.Next; + if (Result.Dx == horizontal && Result.Next.OutIdx != Skip) + { + //nb: at the top of a bound, horizontals are added to the bound + //only when the preceding edge attaches to the horizontal's left vertex + //unless a Skip edge is encountered when that becomes the top divide + Horz = Result; + while (Horz.Prev.Dx == horizontal) Horz = Horz.Prev; + if (Horz.Prev.Top.X > Result.Next.Top.X) Result = Horz.Prev; + } + while (E != Result) + { + E.NextInLML = E.Next; + if (E.Dx == horizontal && E != EStart && E.Bot.X != E.Prev.Top.X) + ReverseHorizontal(E); + E = E.Next; + } + if (E.Dx == horizontal && E != EStart && E.Bot.X != E.Prev.Top.X) + ReverseHorizontal(E); + Result = Result.Next; //move to the edge just beyond current bound + } + else + { + while (Result.Top.Y == Result.Prev.Bot.Y && Result.Prev.OutIdx != Skip) + Result = Result.Prev; + if (Result.Dx == horizontal && Result.Prev.OutIdx != Skip) + { + Horz = Result; + while (Horz.Next.Dx == horizontal) Horz = Horz.Next; + if (Horz.Next.Top.X == Result.Prev.Top.X || + Horz.Next.Top.X > Result.Prev.Top.X) Result = Horz.Next; + } + + while (E != Result) + { + E.NextInLML = E.Prev; + if (E.Dx == horizontal && E != EStart && E.Bot.X != E.Next.Top.X) + ReverseHorizontal(E); + E = E.Prev; + } + if (E.Dx == horizontal && E != EStart && E.Bot.X != E.Next.Top.X) + ReverseHorizontal(E); + Result = Result.Prev; //move to the edge just beyond current bound + } + return Result; + } + //------------------------------------------------------------------------------ + + + public bool AddPath(Path pg, PolyType polyType, bool Closed) + { +#if use_lines + if (!Closed && polyType == PolyType.ptClip) + throw new ClipperException("AddPath: Open paths must be subject."); +#else + if (!Closed) + throw new ClipperException("AddPath: Open paths have been disabled."); +#endif + + int highI = (int)pg.Count - 1; + if (Closed) while (highI > 0 && (pg[highI] == pg[0])) --highI; + while (highI > 0 && (pg[highI] == pg[highI - 1])) --highI; + if ((Closed && highI < 2) || (!Closed && highI < 1)) return false; + + //create a new edge array ... + List edges = new List(highI+1); + for (int i = 0; i <= highI; i++) edges.Add(new TEdge()); + + bool IsFlat = true; + + //1. Basic (first) edge initialization ... + edges[1].Curr = pg[1]; + RangeTest(pg[0], ref m_UseFullRange); + RangeTest(pg[highI], ref m_UseFullRange); + InitEdge(edges[0], edges[1], edges[highI], pg[0]); + InitEdge(edges[highI], edges[0], edges[highI - 1], pg[highI]); + for (int i = highI - 1; i >= 1; --i) + { + RangeTest(pg[i], ref m_UseFullRange); + InitEdge(edges[i], edges[i + 1], edges[i - 1], pg[i]); + } + TEdge eStart = edges[0]; + + //2. Remove duplicate vertices, and (when closed) collinear edges ... + TEdge E = eStart, eLoopStop = eStart; + for (;;) + { + //nb: allows matching start and end points when not Closed ... + if (E.Curr == E.Next.Curr && (Closed || E.Next != eStart)) + { + if (E == E.Next) break; + if (E == eStart) eStart = E.Next; + E = RemoveEdge(E); + eLoopStop = E; + continue; + } + if (E.Prev == E.Next) + break; //only two vertices + else if (Closed && + SlopesEqual(E.Prev.Curr, E.Curr, E.Next.Curr, m_UseFullRange) && + (!PreserveCollinear || + !Pt2IsBetweenPt1AndPt3(E.Prev.Curr, E.Curr, E.Next.Curr))) + { + //Collinear edges are allowed for open paths but in closed paths + //the default is to merge adjacent collinear edges into a single edge. + //However, if the PreserveCollinear property is enabled, only overlapping + //collinear edges (ie spikes) will be removed from closed paths. + if (E == eStart) eStart = E.Next; + E = RemoveEdge(E); + E = E.Prev; + eLoopStop = E; + continue; + } + E = E.Next; + if ((E == eLoopStop) || (!Closed && E.Next == eStart)) break; + } + + if ((!Closed && (E == E.Next)) || (Closed && (E.Prev == E.Next))) + return false; + + if (!Closed) + { + m_HasOpenPaths = true; + eStart.Prev.OutIdx = Skip; + } + + //3. Do second stage of edge initialization ... + E = eStart; + do + { + InitEdge2(E, polyType); + E = E.Next; + if (IsFlat && E.Curr.Y != eStart.Curr.Y) IsFlat = false; + } + while (E != eStart); + + //4. Finally, add edge bounds to LocalMinima list ... + + //Totally flat paths must be handled differently when adding them + //to LocalMinima list to avoid endless loops etc ... + if (IsFlat) + { + if (Closed) return false; + E.Prev.OutIdx = Skip; + LocalMinima locMin = new LocalMinima(); + locMin.Next = null; + locMin.Y = E.Bot.Y; + locMin.LeftBound = null; + locMin.RightBound = E; + locMin.RightBound.Side = EdgeSide.esRight; + locMin.RightBound.WindDelta = 0; + for ( ; ; ) + { + if (E.Bot.X != E.Prev.Top.X) ReverseHorizontal(E); + if (E.Next.OutIdx == Skip) break; + E.NextInLML = E.Next; + E = E.Next; + } + InsertLocalMinima(locMin); + m_edges.Add(edges); + return true; + } + + m_edges.Add(edges); + bool leftBoundIsForward; + TEdge EMin = null; + + //workaround to avoid an endless loop in the while loop below when + //open paths have matching start and end points ... + if (E.Prev.Bot == E.Prev.Top) E = E.Next; + + for (;;) + { + E = FindNextLocMin(E); + if (E == EMin) break; + else if (EMin == null) EMin = E; + + //E and E.Prev now share a local minima (left aligned if horizontal). + //Compare their slopes to find which starts which bound ... + LocalMinima locMin = new LocalMinima(); + locMin.Next = null; + locMin.Y = E.Bot.Y; + if (E.Dx < E.Prev.Dx) + { + locMin.LeftBound = E.Prev; + locMin.RightBound = E; + leftBoundIsForward = false; //Q.nextInLML = Q.prev + } else + { + locMin.LeftBound = E; + locMin.RightBound = E.Prev; + leftBoundIsForward = true; //Q.nextInLML = Q.next + } + locMin.LeftBound.Side = EdgeSide.esLeft; + locMin.RightBound.Side = EdgeSide.esRight; + + if (!Closed) locMin.LeftBound.WindDelta = 0; + else if (locMin.LeftBound.Next == locMin.RightBound) + locMin.LeftBound.WindDelta = -1; + else locMin.LeftBound.WindDelta = 1; + locMin.RightBound.WindDelta = -locMin.LeftBound.WindDelta; + + E = ProcessBound(locMin.LeftBound, leftBoundIsForward); + if (E.OutIdx == Skip) E = ProcessBound(E, leftBoundIsForward); + + TEdge E2 = ProcessBound(locMin.RightBound, !leftBoundIsForward); + if (E2.OutIdx == Skip) E2 = ProcessBound(E2, !leftBoundIsForward); + + if (locMin.LeftBound.OutIdx == Skip) + locMin.LeftBound = null; + else if (locMin.RightBound.OutIdx == Skip) + locMin.RightBound = null; + InsertLocalMinima(locMin); + if (!leftBoundIsForward) E = E2; + } + return true; + + } + //------------------------------------------------------------------------------ + + public bool AddPaths(Paths ppg, PolyType polyType, bool closed) + { + bool result = false; + for (int i = 0; i < ppg.Count; ++i) + if (AddPath(ppg[i], polyType, closed)) result = true; + return result; + } + //------------------------------------------------------------------------------ + + internal bool Pt2IsBetweenPt1AndPt3(IntPoint pt1, IntPoint pt2, IntPoint pt3) + { + if ((pt1 == pt3) || (pt1 == pt2) || (pt3 == pt2)) return false; + else if (pt1.X != pt3.X) return (pt2.X > pt1.X) == (pt2.X < pt3.X); + else return (pt2.Y > pt1.Y) == (pt2.Y < pt3.Y); + } + //------------------------------------------------------------------------------ + + TEdge RemoveEdge(TEdge e) + { + //removes e from double_linked_list (but without removing from memory) + e.Prev.Next = e.Next; + e.Next.Prev = e.Prev; + TEdge result = e.Next; + e.Prev = null; //flag as removed (see ClipperBase.Clear) + return result; + } + //------------------------------------------------------------------------------ + + private void SetDx(TEdge e) + { + e.Delta.X = (e.Top.X - e.Bot.X); + e.Delta.Y = (e.Top.Y - e.Bot.Y); + if (e.Delta.Y == 0) e.Dx = horizontal; + else e.Dx = (double)(e.Delta.X) / (e.Delta.Y); + } + //--------------------------------------------------------------------------- + + private void InsertLocalMinima(LocalMinima newLm) + { + if( m_MinimaList == null ) + { + m_MinimaList = newLm; + } + else if( newLm.Y >= m_MinimaList.Y ) + { + newLm.Next = m_MinimaList; + m_MinimaList = newLm; + } else + { + LocalMinima tmpLm = m_MinimaList; + while( tmpLm.Next != null && ( newLm.Y < tmpLm.Next.Y ) ) + tmpLm = tmpLm.Next; + newLm.Next = tmpLm.Next; + tmpLm.Next = newLm; + } + } + //------------------------------------------------------------------------------ + + internal Boolean PopLocalMinima(cInt Y, out LocalMinima current) + { + current = m_CurrentLM; + if (m_CurrentLM != null && m_CurrentLM.Y == Y) + { + m_CurrentLM = m_CurrentLM.Next; + return true; + } + return false; + } + //------------------------------------------------------------------------------ + + private void ReverseHorizontal(TEdge e) + { + //swap horizontal edges' top and bottom x's so they follow the natural + //progression of the bounds - ie so their xbots will align with the + //adjoining lower edge. [Helpful in the ProcessHorizontal() method.] + Swap(ref e.Top.X, ref e.Bot.X); +#if use_xyz + Swap(ref e.Top.Z, ref e.Bot.Z); +#endif + } + //------------------------------------------------------------------------------ + + internal virtual void Reset() + { + m_CurrentLM = m_MinimaList; + if (m_CurrentLM == null) return; //ie nothing to process + + //reset all edges ... + m_Scanbeam = null; + LocalMinima lm = m_MinimaList; + while (lm != null) + { + InsertScanbeam(lm.Y); + TEdge e = lm.LeftBound; + if (e != null) + { + e.Curr = e.Bot; + e.OutIdx = Unassigned; + } + e = lm.RightBound; + if (e != null) + { + e.Curr = e.Bot; + e.OutIdx = Unassigned; + } + lm = lm.Next; + } + m_ActiveEdges = null; + } + //------------------------------------------------------------------------------ + + public static IntRect GetBounds(Paths paths) + { + int i = 0, cnt = paths.Count; + while (i < cnt && paths[i].Count == 0) i++; + if (i == cnt) return new IntRect(0,0,0,0); + IntRect result = new IntRect(); + result.left = paths[i][0].X; + result.right = result.left; + result.top = paths[i][0].Y; + result.bottom = result.top; + for (; i < cnt; i++) + for (int j = 0; j < paths[i].Count; j++) + { + if (paths[i][j].X < result.left) result.left = paths[i][j].X; + else if (paths[i][j].X > result.right) result.right = paths[i][j].X; + if (paths[i][j].Y < result.top) result.top = paths[i][j].Y; + else if (paths[i][j].Y > result.bottom) result.bottom = paths[i][j].Y; + } + return result; + } + //------------------------------------------------------------------------------ + + internal void InsertScanbeam(cInt Y) + { + //single-linked list: sorted descending, ignoring dups. + if (m_Scanbeam == null) + { + m_Scanbeam = new Scanbeam(); + m_Scanbeam.Next = null; + m_Scanbeam.Y = Y; + } + else if (Y > m_Scanbeam.Y) + { + Scanbeam newSb = new Scanbeam(); + newSb.Y = Y; + newSb.Next = m_Scanbeam; + m_Scanbeam = newSb; + } + else + { + Scanbeam sb2 = m_Scanbeam; + while (sb2.Next != null && (Y <= sb2.Next.Y)) sb2 = sb2.Next; + if (Y == sb2.Y) return; //ie ignores duplicates + Scanbeam newSb = new Scanbeam(); + newSb.Y = Y; + newSb.Next = sb2.Next; + sb2.Next = newSb; + } + } + //------------------------------------------------------------------------------ + + internal Boolean PopScanbeam(out cInt Y) + { + if (m_Scanbeam == null) + { + Y = 0; + return false; + } + Y = m_Scanbeam.Y; + m_Scanbeam = m_Scanbeam.Next; + return true; + } + //------------------------------------------------------------------------------ + + internal Boolean LocalMinimaPending() + { + return (m_CurrentLM != null); + } + //------------------------------------------------------------------------------ + + internal OutRec CreateOutRec() + { + OutRec result = new OutRec(); + result.Idx = Unassigned; + result.IsHole = false; + result.IsOpen = false; + result.FirstLeft = null; + result.Pts = null; + result.BottomPt = null; + result.PolyNode = null; + m_PolyOuts.Add(result); + result.Idx = m_PolyOuts.Count - 1; + return result; + } + //------------------------------------------------------------------------------ + + internal void DisposeOutRec(int index) + { + OutRec outRec = m_PolyOuts[index]; + outRec.Pts = null; + outRec = null; + m_PolyOuts[index] = null; + } + //------------------------------------------------------------------------------ + + internal void UpdateEdgeIntoAEL(ref TEdge e) + { + if (e.NextInLML == null) + throw new ClipperException("UpdateEdgeIntoAEL: invalid call"); + TEdge AelPrev = e.PrevInAEL; + TEdge AelNext = e.NextInAEL; + e.NextInLML.OutIdx = e.OutIdx; + if (AelPrev != null) + AelPrev.NextInAEL = e.NextInLML; + else m_ActiveEdges = e.NextInLML; + if (AelNext != null) + AelNext.PrevInAEL = e.NextInLML; + e.NextInLML.Side = e.Side; + e.NextInLML.WindDelta = e.WindDelta; + e.NextInLML.WindCnt = e.WindCnt; + e.NextInLML.WindCnt2 = e.WindCnt2; + e = e.NextInLML; + e.Curr = e.Bot; + e.PrevInAEL = AelPrev; + e.NextInAEL = AelNext; + if (!IsHorizontal(e)) InsertScanbeam(e.Top.Y); + } + //------------------------------------------------------------------------------ + + internal void SwapPositionsInAEL(TEdge edge1, TEdge edge2) + { + //check that one or other edge hasn't already been removed from AEL ... + if (edge1.NextInAEL == edge1.PrevInAEL || + edge2.NextInAEL == edge2.PrevInAEL) return; + + if (edge1.NextInAEL == edge2) + { + TEdge next = edge2.NextInAEL; + if (next != null) + next.PrevInAEL = edge1; + TEdge prev = edge1.PrevInAEL; + if (prev != null) + prev.NextInAEL = edge2; + edge2.PrevInAEL = prev; + edge2.NextInAEL = edge1; + edge1.PrevInAEL = edge2; + edge1.NextInAEL = next; + } + else if (edge2.NextInAEL == edge1) + { + TEdge next = edge1.NextInAEL; + if (next != null) + next.PrevInAEL = edge2; + TEdge prev = edge2.PrevInAEL; + if (prev != null) + prev.NextInAEL = edge1; + edge1.PrevInAEL = prev; + edge1.NextInAEL = edge2; + edge2.PrevInAEL = edge1; + edge2.NextInAEL = next; + } + else + { + TEdge next = edge1.NextInAEL; + TEdge prev = edge1.PrevInAEL; + edge1.NextInAEL = edge2.NextInAEL; + if (edge1.NextInAEL != null) + edge1.NextInAEL.PrevInAEL = edge1; + edge1.PrevInAEL = edge2.PrevInAEL; + if (edge1.PrevInAEL != null) + edge1.PrevInAEL.NextInAEL = edge1; + edge2.NextInAEL = next; + if (edge2.NextInAEL != null) + edge2.NextInAEL.PrevInAEL = edge2; + edge2.PrevInAEL = prev; + if (edge2.PrevInAEL != null) + edge2.PrevInAEL.NextInAEL = edge2; + } + + if (edge1.PrevInAEL == null) + m_ActiveEdges = edge1; + else if (edge2.PrevInAEL == null) + m_ActiveEdges = edge2; + } + //------------------------------------------------------------------------------ + + internal void DeleteFromAEL(TEdge e) + { + TEdge AelPrev = e.PrevInAEL; + TEdge AelNext = e.NextInAEL; + if (AelPrev == null && AelNext == null && (e != m_ActiveEdges)) + return; //already deleted + if (AelPrev != null) + AelPrev.NextInAEL = AelNext; + else m_ActiveEdges = AelNext; + if (AelNext != null) + AelNext.PrevInAEL = AelPrev; + e.NextInAEL = null; + e.PrevInAEL = null; + } + //------------------------------------------------------------------------------ + + } //end ClipperBase + + public class Clipper : ClipperBase + { + //InitOptions that can be passed to the constructor ... + public const int ioReverseSolution = 1; + public const int ioStrictlySimple = 2; + public const int ioPreserveCollinear = 4; + + private ClipType m_ClipType; + private Maxima m_Maxima; + private TEdge m_SortedEdges; + private List m_IntersectList; + IComparer m_IntersectNodeComparer; + private bool m_ExecuteLocked; + private PolyFillType m_ClipFillType; + private PolyFillType m_SubjFillType; + private List m_Joins; + private List m_GhostJoins; + private bool m_UsingPolyTree; +#if use_xyz + public delegate void ZFillCallback(IntPoint bot1, IntPoint top1, + IntPoint bot2, IntPoint top2, ref IntPoint pt); + public ZFillCallback ZFillFunction { get; set; } +#endif + public Clipper(int InitOptions = 0): base() //constructor + { + m_Scanbeam = null; + m_Maxima = null; + m_ActiveEdges = null; + m_SortedEdges = null; + m_IntersectList = new List(); + m_IntersectNodeComparer = new MyIntersectNodeSort(); + m_ExecuteLocked = false; + m_UsingPolyTree = false; + m_PolyOuts = new List(); + m_Joins = new List(); + m_GhostJoins = new List(); + ReverseSolution = (ioReverseSolution & InitOptions) != 0; + StrictlySimple = (ioStrictlySimple & InitOptions) != 0; + PreserveCollinear = (ioPreserveCollinear & InitOptions) != 0; +#if use_xyz + ZFillFunction = null; +#endif + } + //------------------------------------------------------------------------------ + + private void InsertMaxima(cInt X) + { + //double-linked list: sorted ascending, ignoring dups. + Maxima newMax = new Maxima(); + newMax.X = X; + if (m_Maxima == null) + { + m_Maxima = newMax; + m_Maxima.Next = null; + m_Maxima.Prev = null; + } + else if (X < m_Maxima.X) + { + newMax.Next = m_Maxima; + newMax.Prev = null; + m_Maxima = newMax; + } + else + { + Maxima m = m_Maxima; + while (m.Next != null && (X >= m.Next.X)) m = m.Next; + if (X == m.X) return; //ie ignores duplicates (& CG to clean up newMax) + //insert newMax between m and m.Next ... + newMax.Next = m.Next; + newMax.Prev = m; + if (m.Next != null) m.Next.Prev = newMax; + m.Next = newMax; + } + } + //------------------------------------------------------------------------------ + + public bool ReverseSolution + { + get; + set; + } + //------------------------------------------------------------------------------ + + public bool StrictlySimple + { + get; + set; + } + //------------------------------------------------------------------------------ + + public bool Execute(ClipType clipType, Paths solution, + PolyFillType FillType = PolyFillType.pftEvenOdd) + { + return Execute(clipType, solution, FillType, FillType); + } + //------------------------------------------------------------------------------ + + public bool Execute(ClipType clipType, PolyTree polytree, + PolyFillType FillType = PolyFillType.pftEvenOdd) + { + return Execute(clipType, polytree, FillType, FillType); + } + //------------------------------------------------------------------------------ + + public bool Execute(ClipType clipType, Paths solution, + PolyFillType subjFillType, PolyFillType clipFillType) + { + if (m_ExecuteLocked) return false; + if (m_HasOpenPaths) throw + new ClipperException("Error: PolyTree struct is needed for open path clipping."); + + m_ExecuteLocked = true; + solution.Clear(); + m_SubjFillType = subjFillType; + m_ClipFillType = clipFillType; + m_ClipType = clipType; + m_UsingPolyTree = false; + bool succeeded; + try + { + succeeded = ExecuteInternal(); + //build the return polygons ... + if (succeeded) BuildResult(solution); + } + finally + { + DisposeAllPolyPts(); + m_ExecuteLocked = false; + } + return succeeded; + } + //------------------------------------------------------------------------------ + + public bool Execute(ClipType clipType, PolyTree polytree, + PolyFillType subjFillType, PolyFillType clipFillType) + { + if (m_ExecuteLocked) return false; + m_ExecuteLocked = true; + m_SubjFillType = subjFillType; + m_ClipFillType = clipFillType; + m_ClipType = clipType; + m_UsingPolyTree = true; + bool succeeded; + try + { + succeeded = ExecuteInternal(); + //build the return polygons ... + if (succeeded) BuildResult2(polytree); + } + finally + { + DisposeAllPolyPts(); + m_ExecuteLocked = false; + } + return succeeded; + } + //------------------------------------------------------------------------------ + + internal void FixHoleLinkage(OutRec outRec) + { + //skip if an outermost polygon or + //already already points to the correct FirstLeft ... + if (outRec.FirstLeft == null || + (outRec.IsHole != outRec.FirstLeft.IsHole && + outRec.FirstLeft.Pts != null)) return; + + OutRec orfl = outRec.FirstLeft; + while (orfl != null && ((orfl.IsHole == outRec.IsHole) || orfl.Pts == null)) + orfl = orfl.FirstLeft; + outRec.FirstLeft = orfl; + } + //------------------------------------------------------------------------------ + + private bool ExecuteInternal() + { + try + { + Reset(); + m_SortedEdges = null; + m_Maxima = null; + + cInt botY, topY; + if (!PopScanbeam(out botY)) return false; + InsertLocalMinimaIntoAEL(botY); + while (PopScanbeam(out topY) || LocalMinimaPending()) + { + ProcessHorizontals(); + m_GhostJoins.Clear(); + if (!ProcessIntersections(topY)) return false; + ProcessEdgesAtTopOfScanbeam(topY); + botY = topY; + InsertLocalMinimaIntoAEL(botY); + } + + //fix orientations ... + foreach (OutRec outRec in m_PolyOuts) + { + if (outRec.Pts == null || outRec.IsOpen) continue; + if ((outRec.IsHole ^ ReverseSolution) == (Area(outRec) > 0)) + ReversePolyPtLinks(outRec.Pts); + } + + JoinCommonEdges(); + + foreach (OutRec outRec in m_PolyOuts) + { + if (outRec.Pts == null) + continue; + else if (outRec.IsOpen) + FixupOutPolyline(outRec); + else + FixupOutPolygon(outRec); + } + + if (StrictlySimple) DoSimplePolygons(); + return true; + } + //catch { return false; } + finally + { + m_Joins.Clear(); + m_GhostJoins.Clear(); + } + } + //------------------------------------------------------------------------------ + + private void DisposeAllPolyPts(){ + for (int i = 0; i < m_PolyOuts.Count; ++i) DisposeOutRec(i); + m_PolyOuts.Clear(); + } + //------------------------------------------------------------------------------ + + private void AddJoin(OutPt Op1, OutPt Op2, IntPoint OffPt) + { + Join j = new Join(); + j.OutPt1 = Op1; + j.OutPt2 = Op2; + j.OffPt = OffPt; + m_Joins.Add(j); + } + //------------------------------------------------------------------------------ + + private void AddGhostJoin(OutPt Op, IntPoint OffPt) + { + Join j = new Join(); + j.OutPt1 = Op; + j.OffPt = OffPt; + m_GhostJoins.Add(j); + } + //------------------------------------------------------------------------------ + +#if use_xyz + internal void SetZ(ref IntPoint pt, TEdge e1, TEdge e2) + { + if (pt.Z != 0 || ZFillFunction == null) return; + else if (pt == e1.Bot) pt.Z = e1.Bot.Z; + else if (pt == e1.Top) pt.Z = e1.Top.Z; + else if (pt == e2.Bot) pt.Z = e2.Bot.Z; + else if (pt == e2.Top) pt.Z = e2.Top.Z; + else ZFillFunction(e1.Bot, e1.Top, e2.Bot, e2.Top, ref pt); + } + //------------------------------------------------------------------------------ +#endif + + private void InsertLocalMinimaIntoAEL(cInt botY) + { + LocalMinima lm; + while (PopLocalMinima(botY, out lm)) + { + TEdge lb = lm.LeftBound; + TEdge rb = lm.RightBound; + + OutPt Op1 = null; + if (lb == null) + { + InsertEdgeIntoAEL(rb, null); + SetWindingCount(rb); + if (IsContributing(rb)) + Op1 = AddOutPt(rb, rb.Bot); + } + else if (rb == null) + { + InsertEdgeIntoAEL(lb, null); + SetWindingCount(lb); + if (IsContributing(lb)) + Op1 = AddOutPt(lb, lb.Bot); + InsertScanbeam(lb.Top.Y); + } + else + { + InsertEdgeIntoAEL(lb, null); + InsertEdgeIntoAEL(rb, lb); + SetWindingCount(lb); + rb.WindCnt = lb.WindCnt; + rb.WindCnt2 = lb.WindCnt2; + if (IsContributing(lb)) + Op1 = AddLocalMinPoly(lb, rb, lb.Bot); + InsertScanbeam(lb.Top.Y); + } + + if (rb != null) + { + if (IsHorizontal(rb)) + { + if (rb.NextInLML != null) + InsertScanbeam(rb.NextInLML.Top.Y); + AddEdgeToSEL(rb); + } + else + InsertScanbeam(rb.Top.Y); + } + + if (lb == null || rb == null) continue; + + //if output polygons share an Edge with a horizontal rb, they'll need joining later ... + if (Op1 != null && IsHorizontal(rb) && + m_GhostJoins.Count > 0 && rb.WindDelta != 0) + { + for (int i = 0; i < m_GhostJoins.Count; i++) + { + //if the horizontal Rb and a 'ghost' horizontal overlap, then convert + //the 'ghost' join to a real join ready for later ... + Join j = m_GhostJoins[i]; + if (HorzSegmentsOverlap(j.OutPt1.Pt.X, j.OffPt.X, rb.Bot.X, rb.Top.X)) + AddJoin(j.OutPt1, Op1, j.OffPt); + } + } + + if (lb.OutIdx >= 0 && lb.PrevInAEL != null && + lb.PrevInAEL.Curr.X == lb.Bot.X && + lb.PrevInAEL.OutIdx >= 0 && + SlopesEqual(lb.PrevInAEL.Curr, lb.PrevInAEL.Top, lb.Curr, lb.Top, m_UseFullRange) && + lb.WindDelta != 0 && lb.PrevInAEL.WindDelta != 0) + { + OutPt Op2 = AddOutPt(lb.PrevInAEL, lb.Bot); + AddJoin(Op1, Op2, lb.Top); + } + + if( lb.NextInAEL != rb ) + { + + if (rb.OutIdx >= 0 && rb.PrevInAEL.OutIdx >= 0 && + SlopesEqual(rb.PrevInAEL.Curr, rb.PrevInAEL.Top, rb.Curr, rb.Top, m_UseFullRange) && + rb.WindDelta != 0 && rb.PrevInAEL.WindDelta != 0) + { + OutPt Op2 = AddOutPt(rb.PrevInAEL, rb.Bot); + AddJoin(Op1, Op2, rb.Top); + } + + TEdge e = lb.NextInAEL; + if (e != null) + while (e != rb) + { + //nb: For calculating winding counts etc, IntersectEdges() assumes + //that param1 will be to the right of param2 ABOVE the intersection ... + IntersectEdges(rb, e, lb.Curr); //order important here + e = e.NextInAEL; + } + } + } + } + //------------------------------------------------------------------------------ + + private void InsertEdgeIntoAEL(TEdge edge, TEdge startEdge) + { + if (m_ActiveEdges == null) + { + edge.PrevInAEL = null; + edge.NextInAEL = null; + m_ActiveEdges = edge; + } + else if (startEdge == null && E2InsertsBeforeE1(m_ActiveEdges, edge)) + { + edge.PrevInAEL = null; + edge.NextInAEL = m_ActiveEdges; + m_ActiveEdges.PrevInAEL = edge; + m_ActiveEdges = edge; + } + else + { + if (startEdge == null) startEdge = m_ActiveEdges; + while (startEdge.NextInAEL != null && + !E2InsertsBeforeE1(startEdge.NextInAEL, edge)) + startEdge = startEdge.NextInAEL; + edge.NextInAEL = startEdge.NextInAEL; + if (startEdge.NextInAEL != null) startEdge.NextInAEL.PrevInAEL = edge; + edge.PrevInAEL = startEdge; + startEdge.NextInAEL = edge; + } + } + //---------------------------------------------------------------------- + + private bool E2InsertsBeforeE1(TEdge e1, TEdge e2) + { + if (e2.Curr.X == e1.Curr.X) + { + if (e2.Top.Y > e1.Top.Y) + return e2.Top.X < TopX(e1, e2.Top.Y); + else return e1.Top.X > TopX(e2, e1.Top.Y); + } + else return e2.Curr.X < e1.Curr.X; + } + //------------------------------------------------------------------------------ + + private bool IsEvenOddFillType(TEdge edge) + { + if (edge.PolyTyp == PolyType.ptSubject) + return m_SubjFillType == PolyFillType.pftEvenOdd; + else + return m_ClipFillType == PolyFillType.pftEvenOdd; + } + //------------------------------------------------------------------------------ + + private bool IsEvenOddAltFillType(TEdge edge) + { + if (edge.PolyTyp == PolyType.ptSubject) + return m_ClipFillType == PolyFillType.pftEvenOdd; + else + return m_SubjFillType == PolyFillType.pftEvenOdd; + } + //------------------------------------------------------------------------------ + + private bool IsContributing(TEdge edge) + { + PolyFillType pft, pft2; + if (edge.PolyTyp == PolyType.ptSubject) + { + pft = m_SubjFillType; + pft2 = m_ClipFillType; + } + else + { + pft = m_ClipFillType; + pft2 = m_SubjFillType; + } + + switch (pft) + { + case PolyFillType.pftEvenOdd: + //return false if a subj line has been flagged as inside a subj polygon + if (edge.WindDelta == 0 && edge.WindCnt != 1) return false; + break; + case PolyFillType.pftNonZero: + if (Math.Abs(edge.WindCnt) != 1) return false; + break; + case PolyFillType.pftPositive: + if (edge.WindCnt != 1) return false; + break; + default: //PolyFillType.pftNegative + if (edge.WindCnt != -1) return false; + break; + } + + switch (m_ClipType) + { + case ClipType.ctIntersection: + switch (pft2) + { + case PolyFillType.pftEvenOdd: + case PolyFillType.pftNonZero: + return (edge.WindCnt2 != 0); + case PolyFillType.pftPositive: + return (edge.WindCnt2 > 0); + default: + return (edge.WindCnt2 < 0); + } + case ClipType.ctUnion: + switch (pft2) + { + case PolyFillType.pftEvenOdd: + case PolyFillType.pftNonZero: + return (edge.WindCnt2 == 0); + case PolyFillType.pftPositive: + return (edge.WindCnt2 <= 0); + default: + return (edge.WindCnt2 >= 0); + } + case ClipType.ctDifference: + if (edge.PolyTyp == PolyType.ptSubject) + switch (pft2) + { + case PolyFillType.pftEvenOdd: + case PolyFillType.pftNonZero: + return (edge.WindCnt2 == 0); + case PolyFillType.pftPositive: + return (edge.WindCnt2 <= 0); + default: + return (edge.WindCnt2 >= 0); + } + else + switch (pft2) + { + case PolyFillType.pftEvenOdd: + case PolyFillType.pftNonZero: + return (edge.WindCnt2 != 0); + case PolyFillType.pftPositive: + return (edge.WindCnt2 > 0); + default: + return (edge.WindCnt2 < 0); + } + case ClipType.ctXor: + if (edge.WindDelta == 0) //XOr always contributing unless open + switch (pft2) + { + case PolyFillType.pftEvenOdd: + case PolyFillType.pftNonZero: + return (edge.WindCnt2 == 0); + case PolyFillType.pftPositive: + return (edge.WindCnt2 <= 0); + default: + return (edge.WindCnt2 >= 0); + } + else + return true; + } + return true; + } + //------------------------------------------------------------------------------ + + private void SetWindingCount(TEdge edge) + { + TEdge e = edge.PrevInAEL; + //find the edge of the same polytype that immediately preceeds 'edge' in AEL + while (e != null && ((e.PolyTyp != edge.PolyTyp) || (e.WindDelta == 0))) e = e.PrevInAEL; + if (e == null) + { + PolyFillType pft; + pft = (edge.PolyTyp == PolyType.ptSubject ? m_SubjFillType : m_ClipFillType); + if (edge.WindDelta == 0) edge.WindCnt = (pft == PolyFillType.pftNegative ? -1 : 1); + else edge.WindCnt = edge.WindDelta; + edge.WindCnt2 = 0; + e = m_ActiveEdges; //ie get ready to calc WindCnt2 + } + else if (edge.WindDelta == 0 && m_ClipType != ClipType.ctUnion) + { + edge.WindCnt = 1; + edge.WindCnt2 = e.WindCnt2; + e = e.NextInAEL; //ie get ready to calc WindCnt2 + } + else if (IsEvenOddFillType(edge)) + { + //EvenOdd filling ... + if (edge.WindDelta == 0) + { + //are we inside a subj polygon ... + bool Inside = true; + TEdge e2 = e.PrevInAEL; + while (e2 != null) + { + if (e2.PolyTyp == e.PolyTyp && e2.WindDelta != 0) + Inside = !Inside; + e2 = e2.PrevInAEL; + } + edge.WindCnt = (Inside ? 0 : 1); + } + else + { + edge.WindCnt = edge.WindDelta; + } + edge.WindCnt2 = e.WindCnt2; + e = e.NextInAEL; //ie get ready to calc WindCnt2 + } + else + { + //nonZero, Positive or Negative filling ... + if (e.WindCnt * e.WindDelta < 0) + { + //prev edge is 'decreasing' WindCount (WC) toward zero + //so we're outside the previous polygon ... + if (Math.Abs(e.WindCnt) > 1) + { + //outside prev poly but still inside another. + //when reversing direction of prev poly use the same WC + if (e.WindDelta * edge.WindDelta < 0) edge.WindCnt = e.WindCnt; + //otherwise continue to 'decrease' WC ... + else edge.WindCnt = e.WindCnt + edge.WindDelta; + } + else + //now outside all polys of same polytype so set own WC ... + edge.WindCnt = (edge.WindDelta == 0 ? 1 : edge.WindDelta); + } + else + { + //prev edge is 'increasing' WindCount (WC) away from zero + //so we're inside the previous polygon ... + if (edge.WindDelta == 0) + edge.WindCnt = (e.WindCnt < 0 ? e.WindCnt - 1 : e.WindCnt + 1); + //if wind direction is reversing prev then use same WC + else if (e.WindDelta * edge.WindDelta < 0) + edge.WindCnt = e.WindCnt; + //otherwise add to WC ... + else edge.WindCnt = e.WindCnt + edge.WindDelta; + } + edge.WindCnt2 = e.WindCnt2; + e = e.NextInAEL; //ie get ready to calc WindCnt2 + } + + //update WindCnt2 ... + if (IsEvenOddAltFillType(edge)) + { + //EvenOdd filling ... + while (e != edge) + { + if (e.WindDelta != 0) + edge.WindCnt2 = (edge.WindCnt2 == 0 ? 1 : 0); + e = e.NextInAEL; + } + } + else + { + //nonZero, Positive or Negative filling ... + while (e != edge) + { + edge.WindCnt2 += e.WindDelta; + e = e.NextInAEL; + } + } + } + //------------------------------------------------------------------------------ + + private void AddEdgeToSEL(TEdge edge) + { + //SEL pointers in PEdge are use to build transient lists of horizontal edges. + //However, since we don't need to worry about processing order, all additions + //are made to the front of the list ... + if (m_SortedEdges == null) + { + m_SortedEdges = edge; + edge.PrevInSEL = null; + edge.NextInSEL = null; + } + else + { + edge.NextInSEL = m_SortedEdges; + edge.PrevInSEL = null; + m_SortedEdges.PrevInSEL = edge; + m_SortedEdges = edge; + } + } + //------------------------------------------------------------------------------ + + internal Boolean PopEdgeFromSEL(out TEdge e) + { + //Pop edge from front of SEL (ie SEL is a FILO list) + e = m_SortedEdges; + if (e == null) return false; + TEdge oldE = e; + m_SortedEdges = e.NextInSEL; + if (m_SortedEdges != null) m_SortedEdges.PrevInSEL = null; + oldE.NextInSEL = null; + oldE.PrevInSEL = null; + return true; + } + //------------------------------------------------------------------------------ + + private void CopyAELToSEL() + { + TEdge e = m_ActiveEdges; + m_SortedEdges = e; + while (e != null) + { + e.PrevInSEL = e.PrevInAEL; + e.NextInSEL = e.NextInAEL; + e = e.NextInAEL; + } + } + //------------------------------------------------------------------------------ + + private void SwapPositionsInSEL(TEdge edge1, TEdge edge2) + { + if (edge1.NextInSEL == null && edge1.PrevInSEL == null) + return; + if (edge2.NextInSEL == null && edge2.PrevInSEL == null) + return; + + if (edge1.NextInSEL == edge2) + { + TEdge next = edge2.NextInSEL; + if (next != null) + next.PrevInSEL = edge1; + TEdge prev = edge1.PrevInSEL; + if (prev != null) + prev.NextInSEL = edge2; + edge2.PrevInSEL = prev; + edge2.NextInSEL = edge1; + edge1.PrevInSEL = edge2; + edge1.NextInSEL = next; + } + else if (edge2.NextInSEL == edge1) + { + TEdge next = edge1.NextInSEL; + if (next != null) + next.PrevInSEL = edge2; + TEdge prev = edge2.PrevInSEL; + if (prev != null) + prev.NextInSEL = edge1; + edge1.PrevInSEL = prev; + edge1.NextInSEL = edge2; + edge2.PrevInSEL = edge1; + edge2.NextInSEL = next; + } + else + { + TEdge next = edge1.NextInSEL; + TEdge prev = edge1.PrevInSEL; + edge1.NextInSEL = edge2.NextInSEL; + if (edge1.NextInSEL != null) + edge1.NextInSEL.PrevInSEL = edge1; + edge1.PrevInSEL = edge2.PrevInSEL; + if (edge1.PrevInSEL != null) + edge1.PrevInSEL.NextInSEL = edge1; + edge2.NextInSEL = next; + if (edge2.NextInSEL != null) + edge2.NextInSEL.PrevInSEL = edge2; + edge2.PrevInSEL = prev; + if (edge2.PrevInSEL != null) + edge2.PrevInSEL.NextInSEL = edge2; + } + + if (edge1.PrevInSEL == null) + m_SortedEdges = edge1; + else if (edge2.PrevInSEL == null) + m_SortedEdges = edge2; + } + //------------------------------------------------------------------------------ + + + private void AddLocalMaxPoly(TEdge e1, TEdge e2, IntPoint pt) + { + AddOutPt(e1, pt); + if (e2.WindDelta == 0) AddOutPt(e2, pt); + if (e1.OutIdx == e2.OutIdx) + { + e1.OutIdx = Unassigned; + e2.OutIdx = Unassigned; + } + else if (e1.OutIdx < e2.OutIdx) + AppendPolygon(e1, e2); + else + AppendPolygon(e2, e1); + } + //------------------------------------------------------------------------------ + + private OutPt AddLocalMinPoly(TEdge e1, TEdge e2, IntPoint pt) + { + OutPt result; + TEdge e, prevE; + if (IsHorizontal(e2) || (e1.Dx > e2.Dx)) + { + result = AddOutPt(e1, pt); + e2.OutIdx = e1.OutIdx; + e1.Side = EdgeSide.esLeft; + e2.Side = EdgeSide.esRight; + e = e1; + if (e.PrevInAEL == e2) + prevE = e2.PrevInAEL; + else + prevE = e.PrevInAEL; + } + else + { + result = AddOutPt(e2, pt); + e1.OutIdx = e2.OutIdx; + e1.Side = EdgeSide.esRight; + e2.Side = EdgeSide.esLeft; + e = e2; + if (e.PrevInAEL == e1) + prevE = e1.PrevInAEL; + else + prevE = e.PrevInAEL; + } + + if (prevE != null && prevE.OutIdx >= 0 && prevE.Top.Y < pt.Y && e.Top.Y < pt.Y) + { + cInt xPrev = TopX(prevE, pt.Y); + cInt xE = TopX(e, pt.Y); + if ((xPrev == xE) && (e.WindDelta != 0) && (prevE.WindDelta != 0) && + SlopesEqual(new IntPoint(xPrev, pt.Y), prevE.Top, new IntPoint(xE, pt.Y), e.Top, m_UseFullRange)) + { + OutPt outPt = AddOutPt(prevE, pt); + AddJoin(result, outPt, e.Top); + } + } + return result; + } + //------------------------------------------------------------------------------ + + private OutPt AddOutPt(TEdge e, IntPoint pt) + { + if (e.OutIdx < 0) + { + OutRec outRec = CreateOutRec(); + outRec.IsOpen = (e.WindDelta == 0); + OutPt newOp = new OutPt(); + outRec.Pts = newOp; + newOp.Idx = outRec.Idx; + newOp.Pt = pt; + newOp.Next = newOp; + newOp.Prev = newOp; + if (!outRec.IsOpen) + SetHoleState(e, outRec); + e.OutIdx = outRec.Idx; //nb: do this after SetZ ! + return newOp; + } + else + { + OutRec outRec = m_PolyOuts[e.OutIdx]; + //OutRec.Pts is the 'Left-most' point & OutRec.Pts.Prev is the 'Right-most' + OutPt op = outRec.Pts; + bool ToFront = (e.Side == EdgeSide.esLeft); + if (ToFront && pt == op.Pt) return op; + else if (!ToFront && pt == op.Prev.Pt) return op.Prev; + + OutPt newOp = new OutPt(); + newOp.Idx = outRec.Idx; + newOp.Pt = pt; + newOp.Next = op; + newOp.Prev = op.Prev; + newOp.Prev.Next = newOp; + op.Prev = newOp; + if (ToFront) outRec.Pts = newOp; + return newOp; + } + } + //------------------------------------------------------------------------------ + + private OutPt GetLastOutPt(TEdge e) + { + OutRec outRec = m_PolyOuts[e.OutIdx]; + if (e.Side == EdgeSide.esLeft) + return outRec.Pts; + else + return outRec.Pts.Prev; + } + //------------------------------------------------------------------------------ + + internal void SwapPoints(ref IntPoint pt1, ref IntPoint pt2) + { + IntPoint tmp = new IntPoint(pt1); + pt1 = pt2; + pt2 = tmp; + } + //------------------------------------------------------------------------------ + + private bool HorzSegmentsOverlap(cInt seg1a, cInt seg1b, cInt seg2a, cInt seg2b) + { + if (seg1a > seg1b) Swap(ref seg1a, ref seg1b); + if (seg2a > seg2b) Swap(ref seg2a, ref seg2b); + return (seg1a < seg2b) && (seg2a < seg1b); + } + //------------------------------------------------------------------------------ + + private void SetHoleState(TEdge e, OutRec outRec) + { + TEdge e2 = e.PrevInAEL; + TEdge eTmp = null; + while (e2 != null) + { + if (e2.OutIdx >= 0 && e2.WindDelta != 0) + { + if (eTmp == null) + eTmp = e2; + else if (eTmp.OutIdx == e2.OutIdx) + eTmp = null; //paired + } + e2 = e2.PrevInAEL; + } + + if (eTmp == null) + { + outRec.FirstLeft = null; + outRec.IsHole = false; + } + else + { + outRec.FirstLeft = m_PolyOuts[eTmp.OutIdx]; + outRec.IsHole = !outRec.FirstLeft.IsHole; + } + } + //------------------------------------------------------------------------------ + + private double GetDx(IntPoint pt1, IntPoint pt2) + { + if (pt1.Y == pt2.Y) return horizontal; + else return (double)(pt2.X - pt1.X) / (pt2.Y - pt1.Y); + } + //--------------------------------------------------------------------------- + + private bool FirstIsBottomPt(OutPt btmPt1, OutPt btmPt2) + { + OutPt p = btmPt1.Prev; + while ((p.Pt == btmPt1.Pt) && (p != btmPt1)) p = p.Prev; + double dx1p = Math.Abs(GetDx(btmPt1.Pt, p.Pt)); + p = btmPt1.Next; + while ((p.Pt == btmPt1.Pt) && (p != btmPt1)) p = p.Next; + double dx1n = Math.Abs(GetDx(btmPt1.Pt, p.Pt)); + + p = btmPt2.Prev; + while ((p.Pt == btmPt2.Pt) && (p != btmPt2)) p = p.Prev; + double dx2p = Math.Abs(GetDx(btmPt2.Pt, p.Pt)); + p = btmPt2.Next; + while ((p.Pt == btmPt2.Pt) && (p != btmPt2)) p = p.Next; + double dx2n = Math.Abs(GetDx(btmPt2.Pt, p.Pt)); + + if (Math.Max(dx1p, dx1n) == Math.Max(dx2p, dx2n) && + Math.Min(dx1p, dx1n) == Math.Min(dx2p, dx2n)) + return Area(btmPt1) > 0; //if otherwise identical use orientation + else + return (dx1p >= dx2p && dx1p >= dx2n) || (dx1n >= dx2p && dx1n >= dx2n); + } + //------------------------------------------------------------------------------ + + private OutPt GetBottomPt(OutPt pp) + { + OutPt dups = null; + OutPt p = pp.Next; + while (p != pp) + { + if (p.Pt.Y > pp.Pt.Y) + { + pp = p; + dups = null; + } + else if (p.Pt.Y == pp.Pt.Y && p.Pt.X <= pp.Pt.X) + { + if (p.Pt.X < pp.Pt.X) + { + dups = null; + pp = p; + } else + { + if (p.Next != pp && p.Prev != pp) dups = p; + } + } + p = p.Next; + } + if (dups != null) + { + //there appears to be at least 2 vertices at bottomPt so ... + while (dups != p) + { + if (!FirstIsBottomPt(p, dups)) pp = dups; + dups = dups.Next; + while (dups.Pt != pp.Pt) dups = dups.Next; + } + } + return pp; + } + //------------------------------------------------------------------------------ + + private OutRec GetLowermostRec(OutRec outRec1, OutRec outRec2) + { + //work out which polygon fragment has the correct hole state ... + if (outRec1.BottomPt == null) + outRec1.BottomPt = GetBottomPt(outRec1.Pts); + if (outRec2.BottomPt == null) + outRec2.BottomPt = GetBottomPt(outRec2.Pts); + OutPt bPt1 = outRec1.BottomPt; + OutPt bPt2 = outRec2.BottomPt; + if (bPt1.Pt.Y > bPt2.Pt.Y) return outRec1; + else if (bPt1.Pt.Y < bPt2.Pt.Y) return outRec2; + else if (bPt1.Pt.X < bPt2.Pt.X) return outRec1; + else if (bPt1.Pt.X > bPt2.Pt.X) return outRec2; + else if (bPt1.Next == bPt1) return outRec2; + else if (bPt2.Next == bPt2) return outRec1; + else if (FirstIsBottomPt(bPt1, bPt2)) return outRec1; + else return outRec2; + } + //------------------------------------------------------------------------------ + + bool OutRec1RightOfOutRec2(OutRec outRec1, OutRec outRec2) + { + do + { + outRec1 = outRec1.FirstLeft; + if (outRec1 == outRec2) return true; + } while (outRec1 != null); + return false; + } + //------------------------------------------------------------------------------ + + private OutRec GetOutRec(int idx) + { + OutRec outrec = m_PolyOuts[idx]; + while (outrec != m_PolyOuts[outrec.Idx]) + outrec = m_PolyOuts[outrec.Idx]; + return outrec; + } + //------------------------------------------------------------------------------ + + private void AppendPolygon(TEdge e1, TEdge e2) + { + OutRec outRec1 = m_PolyOuts[e1.OutIdx]; + OutRec outRec2 = m_PolyOuts[e2.OutIdx]; + + OutRec holeStateRec; + if (OutRec1RightOfOutRec2(outRec1, outRec2)) + holeStateRec = outRec2; + else if (OutRec1RightOfOutRec2(outRec2, outRec1)) + holeStateRec = outRec1; + else + holeStateRec = GetLowermostRec(outRec1, outRec2); + + //get the start and ends of both output polygons and + //join E2 poly onto E1 poly and delete pointers to E2 ... + OutPt p1_lft = outRec1.Pts; + OutPt p1_rt = p1_lft.Prev; + OutPt p2_lft = outRec2.Pts; + OutPt p2_rt = p2_lft.Prev; + + //join e2 poly onto e1 poly and delete pointers to e2 ... + if( e1.Side == EdgeSide.esLeft ) + { + if (e2.Side == EdgeSide.esLeft) + { + //z y x a b c + ReversePolyPtLinks(p2_lft); + p2_lft.Next = p1_lft; + p1_lft.Prev = p2_lft; + p1_rt.Next = p2_rt; + p2_rt.Prev = p1_rt; + outRec1.Pts = p2_rt; + } else + { + //x y z a b c + p2_rt.Next = p1_lft; + p1_lft.Prev = p2_rt; + p2_lft.Prev = p1_rt; + p1_rt.Next = p2_lft; + outRec1.Pts = p2_lft; + } + } else + { + if (e2.Side == EdgeSide.esRight) + { + //a b c z y x + ReversePolyPtLinks( p2_lft ); + p1_rt.Next = p2_rt; + p2_rt.Prev = p1_rt; + p2_lft.Next = p1_lft; + p1_lft.Prev = p2_lft; + } else + { + //a b c x y z + p1_rt.Next = p2_lft; + p2_lft.Prev = p1_rt; + p1_lft.Prev = p2_rt; + p2_rt.Next = p1_lft; + } + } + + outRec1.BottomPt = null; + if (holeStateRec == outRec2) + { + if (outRec2.FirstLeft != outRec1) + outRec1.FirstLeft = outRec2.FirstLeft; + outRec1.IsHole = outRec2.IsHole; + } + outRec2.Pts = null; + outRec2.BottomPt = null; + + outRec2.FirstLeft = outRec1; + + int OKIdx = e1.OutIdx; + int ObsoleteIdx = e2.OutIdx; + + e1.OutIdx = Unassigned; //nb: safe because we only get here via AddLocalMaxPoly + e2.OutIdx = Unassigned; + + TEdge e = m_ActiveEdges; + while( e != null ) + { + if( e.OutIdx == ObsoleteIdx ) + { + e.OutIdx = OKIdx; + e.Side = e1.Side; + break; + } + e = e.NextInAEL; + } + outRec2.Idx = outRec1.Idx; + } + //------------------------------------------------------------------------------ + + private void ReversePolyPtLinks(OutPt pp) + { + if (pp == null) return; + OutPt pp1; + OutPt pp2; + pp1 = pp; + do + { + pp2 = pp1.Next; + pp1.Next = pp1.Prev; + pp1.Prev = pp2; + pp1 = pp2; + } while (pp1 != pp); + } + //------------------------------------------------------------------------------ + + private static void SwapSides(TEdge edge1, TEdge edge2) + { + EdgeSide side = edge1.Side; + edge1.Side = edge2.Side; + edge2.Side = side; + } + //------------------------------------------------------------------------------ + + private static void SwapPolyIndexes(TEdge edge1, TEdge edge2) + { + int outIdx = edge1.OutIdx; + edge1.OutIdx = edge2.OutIdx; + edge2.OutIdx = outIdx; + } + //------------------------------------------------------------------------------ + + private void IntersectEdges(TEdge e1, TEdge e2, IntPoint pt) + { + //e1 will be to the left of e2 BELOW the intersection. Therefore e1 is before + //e2 in AEL except when e1 is being inserted at the intersection point ... + + bool e1Contributing = (e1.OutIdx >= 0); + bool e2Contributing = (e2.OutIdx >= 0); + +#if use_xyz + SetZ(ref pt, e1, e2); +#endif + +#if use_lines + //if either edge is on an OPEN path ... + if (e1.WindDelta == 0 || e2.WindDelta == 0) + { + //ignore subject-subject open path intersections UNLESS they + //are both open paths, AND they are both 'contributing maximas' ... + if (e1.WindDelta == 0 && e2.WindDelta == 0) return; + //if intersecting a subj line with a subj poly ... + else if (e1.PolyTyp == e2.PolyTyp && + e1.WindDelta != e2.WindDelta && m_ClipType == ClipType.ctUnion) + { + if (e1.WindDelta == 0) + { + if (e2Contributing) + { + AddOutPt(e1, pt); + if (e1Contributing) e1.OutIdx = Unassigned; + } + } + else + { + if (e1Contributing) + { + AddOutPt(e2, pt); + if (e2Contributing) e2.OutIdx = Unassigned; + } + } + } + else if (e1.PolyTyp != e2.PolyTyp) + { + if ((e1.WindDelta == 0) && Math.Abs(e2.WindCnt) == 1 && + (m_ClipType != ClipType.ctUnion || e2.WindCnt2 == 0)) + { + AddOutPt(e1, pt); + if (e1Contributing) e1.OutIdx = Unassigned; + } + else if ((e2.WindDelta == 0) && (Math.Abs(e1.WindCnt) == 1) && + (m_ClipType != ClipType.ctUnion || e1.WindCnt2 == 0)) + { + AddOutPt(e2, pt); + if (e2Contributing) e2.OutIdx = Unassigned; + } + } + return; + } +#endif + + //update winding counts... + //assumes that e1 will be to the Right of e2 ABOVE the intersection + if (e1.PolyTyp == e2.PolyTyp) + { + if (IsEvenOddFillType(e1)) + { + int oldE1WindCnt = e1.WindCnt; + e1.WindCnt = e2.WindCnt; + e2.WindCnt = oldE1WindCnt; + } + else + { + if (e1.WindCnt + e2.WindDelta == 0) e1.WindCnt = -e1.WindCnt; + else e1.WindCnt += e2.WindDelta; + if (e2.WindCnt - e1.WindDelta == 0) e2.WindCnt = -e2.WindCnt; + else e2.WindCnt -= e1.WindDelta; + } + } + else + { + if (!IsEvenOddFillType(e2)) e1.WindCnt2 += e2.WindDelta; + else e1.WindCnt2 = (e1.WindCnt2 == 0) ? 1 : 0; + if (!IsEvenOddFillType(e1)) e2.WindCnt2 -= e1.WindDelta; + else e2.WindCnt2 = (e2.WindCnt2 == 0) ? 1 : 0; + } + + PolyFillType e1FillType, e2FillType, e1FillType2, e2FillType2; + if (e1.PolyTyp == PolyType.ptSubject) + { + e1FillType = m_SubjFillType; + e1FillType2 = m_ClipFillType; + } + else + { + e1FillType = m_ClipFillType; + e1FillType2 = m_SubjFillType; + } + if (e2.PolyTyp == PolyType.ptSubject) + { + e2FillType = m_SubjFillType; + e2FillType2 = m_ClipFillType; + } + else + { + e2FillType = m_ClipFillType; + e2FillType2 = m_SubjFillType; + } + + int e1Wc, e2Wc; + switch (e1FillType) + { + case PolyFillType.pftPositive: e1Wc = e1.WindCnt; break; + case PolyFillType.pftNegative: e1Wc = -e1.WindCnt; break; + default: e1Wc = Math.Abs(e1.WindCnt); break; + } + switch (e2FillType) + { + case PolyFillType.pftPositive: e2Wc = e2.WindCnt; break; + case PolyFillType.pftNegative: e2Wc = -e2.WindCnt; break; + default: e2Wc = Math.Abs(e2.WindCnt); break; + } + + if (e1Contributing && e2Contributing) + { + if ((e1Wc != 0 && e1Wc != 1) || (e2Wc != 0 && e2Wc != 1) || + (e1.PolyTyp != e2.PolyTyp && m_ClipType != ClipType.ctXor)) + { + AddLocalMaxPoly(e1, e2, pt); + } + else + { + AddOutPt(e1, pt); + AddOutPt(e2, pt); + SwapSides(e1, e2); + SwapPolyIndexes(e1, e2); + } + } + else if (e1Contributing) + { + if (e2Wc == 0 || e2Wc == 1) + { + AddOutPt(e1, pt); + SwapSides(e1, e2); + SwapPolyIndexes(e1, e2); + } + + } + else if (e2Contributing) + { + if (e1Wc == 0 || e1Wc == 1) + { + AddOutPt(e2, pt); + SwapSides(e1, e2); + SwapPolyIndexes(e1, e2); + } + } + else if ( (e1Wc == 0 || e1Wc == 1) && (e2Wc == 0 || e2Wc == 1)) + { + //neither edge is currently contributing ... + cInt e1Wc2, e2Wc2; + switch (e1FillType2) + { + case PolyFillType.pftPositive: e1Wc2 = e1.WindCnt2; break; + case PolyFillType.pftNegative: e1Wc2 = -e1.WindCnt2; break; + default: e1Wc2 = Math.Abs(e1.WindCnt2); break; + } + switch (e2FillType2) + { + case PolyFillType.pftPositive: e2Wc2 = e2.WindCnt2; break; + case PolyFillType.pftNegative: e2Wc2 = -e2.WindCnt2; break; + default: e2Wc2 = Math.Abs(e2.WindCnt2); break; + } + + if (e1.PolyTyp != e2.PolyTyp) + { + AddLocalMinPoly(e1, e2, pt); + } + else if (e1Wc == 1 && e2Wc == 1) + switch (m_ClipType) + { + case ClipType.ctIntersection: + if (e1Wc2 > 0 && e2Wc2 > 0) + AddLocalMinPoly(e1, e2, pt); + break; + case ClipType.ctUnion: + if (e1Wc2 <= 0 && e2Wc2 <= 0) + AddLocalMinPoly(e1, e2, pt); + break; + case ClipType.ctDifference: + if (((e1.PolyTyp == PolyType.ptClip) && (e1Wc2 > 0) && (e2Wc2 > 0)) || + ((e1.PolyTyp == PolyType.ptSubject) && (e1Wc2 <= 0) && (e2Wc2 <= 0))) + AddLocalMinPoly(e1, e2, pt); + break; + case ClipType.ctXor: + AddLocalMinPoly(e1, e2, pt); + break; + } + else + SwapSides(e1, e2); + } + } + //------------------------------------------------------------------------------ + + private void DeleteFromSEL(TEdge e) + { + TEdge SelPrev = e.PrevInSEL; + TEdge SelNext = e.NextInSEL; + if (SelPrev == null && SelNext == null && (e != m_SortedEdges)) + return; //already deleted + if (SelPrev != null) + SelPrev.NextInSEL = SelNext; + else m_SortedEdges = SelNext; + if (SelNext != null) + SelNext.PrevInSEL = SelPrev; + e.NextInSEL = null; + e.PrevInSEL = null; + } + //------------------------------------------------------------------------------ + + private void ProcessHorizontals() + { + TEdge horzEdge; //m_SortedEdges; + while (PopEdgeFromSEL(out horzEdge)) + ProcessHorizontal(horzEdge); + } + //------------------------------------------------------------------------------ + + void GetHorzDirection(TEdge HorzEdge, out Direction Dir, out cInt Left, out cInt Right) + { + if (HorzEdge.Bot.X < HorzEdge.Top.X) + { + Left = HorzEdge.Bot.X; + Right = HorzEdge.Top.X; + Dir = Direction.dLeftToRight; + } else + { + Left = HorzEdge.Top.X; + Right = HorzEdge.Bot.X; + Dir = Direction.dRightToLeft; + } + } + //------------------------------------------------------------------------ + + private void ProcessHorizontal(TEdge horzEdge) + { + Direction dir; + cInt horzLeft, horzRight; + bool IsOpen = horzEdge.WindDelta == 0; + + GetHorzDirection(horzEdge, out dir, out horzLeft, out horzRight); + + TEdge eLastHorz = horzEdge, eMaxPair = null; + while (eLastHorz.NextInLML != null && IsHorizontal(eLastHorz.NextInLML)) + eLastHorz = eLastHorz.NextInLML; + if (eLastHorz.NextInLML == null) + eMaxPair = GetMaximaPair(eLastHorz); + + Maxima currMax = m_Maxima; + if (currMax != null) + { + //get the first maxima in range (X) ... + if (dir == Direction.dLeftToRight) + { + while (currMax != null && currMax.X <= horzEdge.Bot.X) + currMax = currMax.Next; + if (currMax != null && currMax.X >= eLastHorz.Top.X) + currMax = null; + } + else + { + while (currMax.Next != null && currMax.Next.X < horzEdge.Bot.X) + currMax = currMax.Next; + if (currMax.X <= eLastHorz.Top.X) currMax = null; + } + } + + OutPt op1 = null; + for (;;) //loop through consec. horizontal edges + { + bool IsLastHorz = (horzEdge == eLastHorz); + TEdge e = GetNextInAEL(horzEdge, dir); + while(e != null) + { + + //this code block inserts extra coords into horizontal edges (in output + //polygons) whereever maxima touch these horizontal edges. This helps + //'simplifying' polygons (ie if the Simplify property is set). + if (currMax != null) + { + if (dir == Direction.dLeftToRight) + { + while (currMax != null && currMax.X < e.Curr.X) + { + if (horzEdge.OutIdx >= 0 && !IsOpen) + AddOutPt(horzEdge, new IntPoint(currMax.X, horzEdge.Bot.Y)); + currMax = currMax.Next; + } + } + else + { + while (currMax != null && currMax.X > e.Curr.X) + { + if (horzEdge.OutIdx >= 0 && !IsOpen) + AddOutPt(horzEdge, new IntPoint(currMax.X, horzEdge.Bot.Y)); + currMax = currMax.Prev; + } + } + }; + + if ((dir == Direction.dLeftToRight && e.Curr.X > horzRight) || + (dir == Direction.dRightToLeft && e.Curr.X < horzLeft)) break; + + //Also break if we've got to the end of an intermediate horizontal edge ... + //nb: Smaller Dx's are to the right of larger Dx's ABOVE the horizontal. + if (e.Curr.X == horzEdge.Top.X && horzEdge.NextInLML != null && + e.Dx < horzEdge.NextInLML.Dx) break; + + if (horzEdge.OutIdx >= 0 && !IsOpen) //note: may be done multiple times + { +#if use_xyz + if (dir == Direction.dLeftToRight) SetZ(ref e.Curr, horzEdge, e); + else SetZ(ref e.Curr, e, horzEdge); +#endif + + op1 = AddOutPt(horzEdge, e.Curr); + TEdge eNextHorz = m_SortedEdges; + while (eNextHorz != null) + { + if (eNextHorz.OutIdx >= 0 && + HorzSegmentsOverlap(horzEdge.Bot.X, + horzEdge.Top.X, eNextHorz.Bot.X, eNextHorz.Top.X)) + { + OutPt op2 = GetLastOutPt(eNextHorz); + AddJoin(op2, op1, eNextHorz.Top); + } + eNextHorz = eNextHorz.NextInSEL; + } + AddGhostJoin(op1, horzEdge.Bot); + } + + //OK, so far we're still in range of the horizontal Edge but make sure + //we're at the last of consec. horizontals when matching with eMaxPair + if(e == eMaxPair && IsLastHorz) + { + if (horzEdge.OutIdx >= 0) + AddLocalMaxPoly(horzEdge, eMaxPair, horzEdge.Top); + DeleteFromAEL(horzEdge); + DeleteFromAEL(eMaxPair); + return; + } + + if(dir == Direction.dLeftToRight) + { + IntPoint Pt = new IntPoint(e.Curr.X, horzEdge.Curr.Y); + IntersectEdges(horzEdge, e, Pt); + } + else + { + IntPoint Pt = new IntPoint(e.Curr.X, horzEdge.Curr.Y); + IntersectEdges(e, horzEdge, Pt); + } + TEdge eNext = GetNextInAEL(e, dir); + SwapPositionsInAEL(horzEdge, e); + e = eNext; + } //end while(e != null) + + //Break out of loop if HorzEdge.NextInLML is not also horizontal ... + if (horzEdge.NextInLML == null || !IsHorizontal(horzEdge.NextInLML)) break; + + UpdateEdgeIntoAEL(ref horzEdge); + if (horzEdge.OutIdx >= 0) AddOutPt(horzEdge, horzEdge.Bot); + GetHorzDirection(horzEdge, out dir, out horzLeft, out horzRight); + + } //end for (;;) + + if (horzEdge.OutIdx >= 0 && op1 == null) + { + op1 = GetLastOutPt(horzEdge); + TEdge eNextHorz = m_SortedEdges; + while (eNextHorz != null) + { + if (eNextHorz.OutIdx >= 0 && + HorzSegmentsOverlap(horzEdge.Bot.X, + horzEdge.Top.X, eNextHorz.Bot.X, eNextHorz.Top.X)) + { + OutPt op2 = GetLastOutPt(eNextHorz); + AddJoin(op2, op1, eNextHorz.Top); + } + eNextHorz = eNextHorz.NextInSEL; + } + AddGhostJoin(op1, horzEdge.Top); + } + + if (horzEdge.NextInLML != null) + { + if(horzEdge.OutIdx >= 0) + { + op1 = AddOutPt( horzEdge, horzEdge.Top); + + UpdateEdgeIntoAEL(ref horzEdge); + if (horzEdge.WindDelta == 0) return; + //nb: HorzEdge is no longer horizontal here + TEdge ePrev = horzEdge.PrevInAEL; + TEdge eNext = horzEdge.NextInAEL; + if (ePrev != null && ePrev.Curr.X == horzEdge.Bot.X && + ePrev.Curr.Y == horzEdge.Bot.Y && ePrev.WindDelta != 0 && + (ePrev.OutIdx >= 0 && ePrev.Curr.Y > ePrev.Top.Y && + SlopesEqual(horzEdge, ePrev, m_UseFullRange))) + { + OutPt op2 = AddOutPt(ePrev, horzEdge.Bot); + AddJoin(op1, op2, horzEdge.Top); + } + else if (eNext != null && eNext.Curr.X == horzEdge.Bot.X && + eNext.Curr.Y == horzEdge.Bot.Y && eNext.WindDelta != 0 && + eNext.OutIdx >= 0 && eNext.Curr.Y > eNext.Top.Y && + SlopesEqual(horzEdge, eNext, m_UseFullRange)) + { + OutPt op2 = AddOutPt(eNext, horzEdge.Bot); + AddJoin(op1, op2, horzEdge.Top); + } + } + else + UpdateEdgeIntoAEL(ref horzEdge); + } + else + { + if (horzEdge.OutIdx >= 0) AddOutPt(horzEdge, horzEdge.Top); + DeleteFromAEL(horzEdge); + } + } + //------------------------------------------------------------------------------ + + private TEdge GetNextInAEL(TEdge e, Direction Direction) + { + return Direction == Direction.dLeftToRight ? e.NextInAEL: e.PrevInAEL; + } + //------------------------------------------------------------------------------ + + private bool IsMinima(TEdge e) + { + return e != null && (e.Prev.NextInLML != e) && (e.Next.NextInLML != e); + } + //------------------------------------------------------------------------------ + + private bool IsMaxima(TEdge e, double Y) + { + return (e != null && e.Top.Y == Y && e.NextInLML == null); + } + //------------------------------------------------------------------------------ + + private bool IsIntermediate(TEdge e, double Y) + { + return (e.Top.Y == Y && e.NextInLML != null); + } + //------------------------------------------------------------------------------ + + internal TEdge GetMaximaPair(TEdge e) + { + if ((e.Next.Top == e.Top) && e.Next.NextInLML == null) + return e.Next; + else if ((e.Prev.Top == e.Top) && e.Prev.NextInLML == null) + return e.Prev; + else + return null; + } + //------------------------------------------------------------------------------ + + internal TEdge GetMaximaPairEx(TEdge e) + { + //as above but returns null if MaxPair isn't in AEL (unless it's horizontal) + TEdge result = GetMaximaPair(e); + if (result == null || result.OutIdx == Skip || + ((result.NextInAEL == result.PrevInAEL) && !IsHorizontal(result))) return null; + return result; + } + //------------------------------------------------------------------------------ + + private bool ProcessIntersections(cInt topY) + { + if( m_ActiveEdges == null ) return true; + try { + BuildIntersectList(topY); + if ( m_IntersectList.Count == 0) return true; + if (m_IntersectList.Count == 1 || FixupIntersectionOrder()) + ProcessIntersectList(); + else + return false; + } + catch { + m_SortedEdges = null; + m_IntersectList.Clear(); + throw new ClipperException("ProcessIntersections error"); + } + m_SortedEdges = null; + return true; + } + //------------------------------------------------------------------------------ + + private void BuildIntersectList(cInt topY) + { + if ( m_ActiveEdges == null ) return; + + //prepare for sorting ... + TEdge e = m_ActiveEdges; + m_SortedEdges = e; + while( e != null ) + { + e.PrevInSEL = e.PrevInAEL; + e.NextInSEL = e.NextInAEL; + e.Curr.X = TopX( e, topY ); + e = e.NextInAEL; + } + + //bubblesort ... + bool isModified = true; + while( isModified && m_SortedEdges != null ) + { + isModified = false; + e = m_SortedEdges; + while( e.NextInSEL != null ) + { + TEdge eNext = e.NextInSEL; + IntPoint pt; + if (e.Curr.X > eNext.Curr.X) + { + IntersectPoint(e, eNext, out pt); + if (pt.Y < topY) + pt = new IntPoint(TopX(e, topY), topY); + IntersectNode newNode = new IntersectNode(); + newNode.Edge1 = e; + newNode.Edge2 = eNext; + newNode.Pt = pt; + m_IntersectList.Add(newNode); + + SwapPositionsInSEL(e, eNext); + isModified = true; + } + else + e = eNext; + } + if( e.PrevInSEL != null ) e.PrevInSEL.NextInSEL = null; + else break; + } + m_SortedEdges = null; + } + //------------------------------------------------------------------------------ + + private bool EdgesAdjacent(IntersectNode inode) + { + return (inode.Edge1.NextInSEL == inode.Edge2) || + (inode.Edge1.PrevInSEL == inode.Edge2); + } + //------------------------------------------------------------------------------ + + private static int IntersectNodeSort(IntersectNode node1, IntersectNode node2) + { + //the following typecast is safe because the differences in Pt.Y will + //be limited to the height of the scanbeam. + return (int)(node2.Pt.Y - node1.Pt.Y); + } + //------------------------------------------------------------------------------ + + private bool FixupIntersectionOrder() + { + //pre-condition: intersections are sorted bottom-most first. + //Now it's crucial that intersections are made only between adjacent edges, + //so to ensure this the order of intersections may need adjusting ... + m_IntersectList.Sort(m_IntersectNodeComparer); + + CopyAELToSEL(); + int cnt = m_IntersectList.Count; + for (int i = 0; i < cnt; i++) + { + if (!EdgesAdjacent(m_IntersectList[i])) + { + int j = i + 1; + while (j < cnt && !EdgesAdjacent(m_IntersectList[j])) j++; + if (j == cnt) return false; + + IntersectNode tmp = m_IntersectList[i]; + m_IntersectList[i] = m_IntersectList[j]; + m_IntersectList[j] = tmp; + + } + SwapPositionsInSEL(m_IntersectList[i].Edge1, m_IntersectList[i].Edge2); + } + return true; + } + //------------------------------------------------------------------------------ + + private void ProcessIntersectList() + { + for (int i = 0; i < m_IntersectList.Count; i++) + { + IntersectNode iNode = m_IntersectList[i]; + { + IntersectEdges(iNode.Edge1, iNode.Edge2, iNode.Pt); + SwapPositionsInAEL(iNode.Edge1, iNode.Edge2); + } + } + m_IntersectList.Clear(); + } + //------------------------------------------------------------------------------ + + internal static cInt Round(double value) + { + return value < 0 ? (cInt)(value - 0.5) : (cInt)(value + 0.5); + } + //------------------------------------------------------------------------------ + + private static cInt TopX(TEdge edge, cInt currentY) + { + if (currentY == edge.Top.Y) + return edge.Top.X; + return edge.Bot.X + Round(edge.Dx *(currentY - edge.Bot.Y)); + } + //------------------------------------------------------------------------------ + + private void IntersectPoint(TEdge edge1, TEdge edge2, out IntPoint ip) + { + ip = new IntPoint(); + double b1, b2; + //nb: with very large coordinate values, it's possible for SlopesEqual() to + //return false but for the edge.Dx value be equal due to double precision rounding. + if (edge1.Dx == edge2.Dx) + { + ip.Y = edge1.Curr.Y; + ip.X = TopX(edge1, ip.Y); + return; + } + + if (edge1.Delta.X == 0) + { + ip.X = edge1.Bot.X; + if (IsHorizontal(edge2)) + { + ip.Y = edge2.Bot.Y; + } + else + { + b2 = edge2.Bot.Y - (edge2.Bot.X / edge2.Dx); + ip.Y = Round(ip.X / edge2.Dx + b2); + } + } + else if (edge2.Delta.X == 0) + { + ip.X = edge2.Bot.X; + if (IsHorizontal(edge1)) + { + ip.Y = edge1.Bot.Y; + } + else + { + b1 = edge1.Bot.Y - (edge1.Bot.X / edge1.Dx); + ip.Y = Round(ip.X / edge1.Dx + b1); + } + } + else + { + b1 = edge1.Bot.X - edge1.Bot.Y * edge1.Dx; + b2 = edge2.Bot.X - edge2.Bot.Y * edge2.Dx; + double q = (b2 - b1) / (edge1.Dx - edge2.Dx); + ip.Y = Round(q); + if (Math.Abs(edge1.Dx) < Math.Abs(edge2.Dx)) + ip.X = Round(edge1.Dx * q + b1); + else + ip.X = Round(edge2.Dx * q + b2); + } + + if (ip.Y < edge1.Top.Y || ip.Y < edge2.Top.Y) + { + if (edge1.Top.Y > edge2.Top.Y) + ip.Y = edge1.Top.Y; + else + ip.Y = edge2.Top.Y; + if (Math.Abs(edge1.Dx) < Math.Abs(edge2.Dx)) + ip.X = TopX(edge1, ip.Y); + else + ip.X = TopX(edge2, ip.Y); + } + //finally, don't allow 'ip' to be BELOW curr.Y (ie bottom of scanbeam) ... + if (ip.Y > edge1.Curr.Y) + { + ip.Y = edge1.Curr.Y; + //better to use the more vertical edge to derive X ... + if (Math.Abs(edge1.Dx) > Math.Abs(edge2.Dx)) + ip.X = TopX(edge2, ip.Y); + else + ip.X = TopX(edge1, ip.Y); + } + } + //------------------------------------------------------------------------------ + + private void ProcessEdgesAtTopOfScanbeam(cInt topY) + { + TEdge e = m_ActiveEdges; + while(e != null) + { + //1. process maxima, treating them as if they're 'bent' horizontal edges, + // but exclude maxima with horizontal edges. nb: e can't be a horizontal. + bool IsMaximaEdge = IsMaxima(e, topY); + + if(IsMaximaEdge) + { + TEdge eMaxPair = GetMaximaPairEx(e); + IsMaximaEdge = (eMaxPair == null || !IsHorizontal(eMaxPair)); + } + + if(IsMaximaEdge) + { + if (StrictlySimple) InsertMaxima(e.Top.X); + TEdge ePrev = e.PrevInAEL; + DoMaxima(e); + if( ePrev == null) e = m_ActiveEdges; + else e = ePrev.NextInAEL; + } + else + { + //2. promote horizontal edges, otherwise update Curr.X and Curr.Y ... + if (IsIntermediate(e, topY) && IsHorizontal(e.NextInLML)) + { + UpdateEdgeIntoAEL(ref e); + if (e.OutIdx >= 0) + AddOutPt(e, e.Bot); + AddEdgeToSEL(e); + } + else + { + e.Curr.X = TopX( e, topY ); + e.Curr.Y = topY; +#if use_xyz + if (e.Top.Y == topY) e.Curr.Z = e.Top.Z; + else if (e.Bot.Y == topY) e.Curr.Z = e.Bot.Z; + else e.Curr.Z = 0; +#endif + } + //When StrictlySimple and 'e' is being touched by another edge, then + //make sure both edges have a vertex here ... + if (StrictlySimple) + { + TEdge ePrev = e.PrevInAEL; + if ((e.OutIdx >= 0) && (e.WindDelta != 0) && ePrev != null && + (ePrev.OutIdx >= 0) && (ePrev.Curr.X == e.Curr.X) && + (ePrev.WindDelta != 0)) + { + IntPoint ip = new IntPoint(e.Curr); +#if use_xyz + SetZ(ref ip, ePrev, e); +#endif + OutPt op = AddOutPt(ePrev, ip); + OutPt op2 = AddOutPt(e, ip); + AddJoin(op, op2, ip); //StrictlySimple (type-3) join + } + } + + e = e.NextInAEL; + } + } + + //3. Process horizontals at the Top of the scanbeam ... + ProcessHorizontals(); + m_Maxima = null; + + //4. Promote intermediate vertices ... + e = m_ActiveEdges; + while (e != null) + { + if(IsIntermediate(e, topY)) + { + OutPt op = null; + if( e.OutIdx >= 0 ) + op = AddOutPt(e, e.Top); + UpdateEdgeIntoAEL(ref e); + + //if output polygons share an edge, they'll need joining later ... + TEdge ePrev = e.PrevInAEL; + TEdge eNext = e.NextInAEL; + if (ePrev != null && ePrev.Curr.X == e.Bot.X && + ePrev.Curr.Y == e.Bot.Y && op != null && + ePrev.OutIdx >= 0 && ePrev.Curr.Y > ePrev.Top.Y && + SlopesEqual(e.Curr, e.Top, ePrev.Curr, ePrev.Top, m_UseFullRange) && + (e.WindDelta != 0) && (ePrev.WindDelta != 0)) + { + OutPt op2 = AddOutPt(ePrev, e.Bot); + AddJoin(op, op2, e.Top); + } + else if (eNext != null && eNext.Curr.X == e.Bot.X && + eNext.Curr.Y == e.Bot.Y && op != null && + eNext.OutIdx >= 0 && eNext.Curr.Y > eNext.Top.Y && + SlopesEqual(e.Curr, e.Top, eNext.Curr, eNext.Top, m_UseFullRange) && + (e.WindDelta != 0) && (eNext.WindDelta != 0)) + { + OutPt op2 = AddOutPt(eNext, e.Bot); + AddJoin(op, op2, e.Top); + } + } + e = e.NextInAEL; + } + } + //------------------------------------------------------------------------------ + + private void DoMaxima(TEdge e) + { + TEdge eMaxPair = GetMaximaPairEx(e); + if (eMaxPair == null) + { + if (e.OutIdx >= 0) + AddOutPt(e, e.Top); + DeleteFromAEL(e); + return; + } + + TEdge eNext = e.NextInAEL; + while(eNext != null && eNext != eMaxPair) + { + IntersectEdges(e, eNext, e.Top); + SwapPositionsInAEL(e, eNext); + eNext = e.NextInAEL; + } + + if(e.OutIdx == Unassigned && eMaxPair.OutIdx == Unassigned) + { + DeleteFromAEL(e); + DeleteFromAEL(eMaxPair); + } + else if( e.OutIdx >= 0 && eMaxPair.OutIdx >= 0 ) + { + if (e.OutIdx >= 0) AddLocalMaxPoly(e, eMaxPair, e.Top); + DeleteFromAEL(e); + DeleteFromAEL(eMaxPair); + } +#if use_lines + else if (e.WindDelta == 0) + { + if (e.OutIdx >= 0) + { + AddOutPt(e, e.Top); + e.OutIdx = Unassigned; + } + DeleteFromAEL(e); + + if (eMaxPair.OutIdx >= 0) + { + AddOutPt(eMaxPair, e.Top); + eMaxPair.OutIdx = Unassigned; + } + DeleteFromAEL(eMaxPair); + } +#endif + else throw new ClipperException("DoMaxima error"); + } + //------------------------------------------------------------------------------ + + public static void ReversePaths(Paths polys) + { + foreach (var poly in polys) { poly.Reverse(); } + } + //------------------------------------------------------------------------------ + + public static bool Orientation(Path poly) + { + return Area(poly) >= 0; + } + //------------------------------------------------------------------------------ + + private int PointCount(OutPt pts) + { + if (pts == null) return 0; + int result = 0; + OutPt p = pts; + do + { + result++; + p = p.Next; + } + while (p != pts); + return result; + } + //------------------------------------------------------------------------------ + + private void BuildResult(Paths polyg) + { + polyg.Clear(); + polyg.Capacity = m_PolyOuts.Count; + for (int i = 0; i < m_PolyOuts.Count; i++) + { + OutRec outRec = m_PolyOuts[i]; + if (outRec.Pts == null) continue; + OutPt p = outRec.Pts.Prev; + int cnt = PointCount(p); + if (cnt < 2) continue; + Path pg = new Path(cnt); + for (int j = 0; j < cnt; j++) + { + pg.Add(p.Pt); + p = p.Prev; + } + polyg.Add(pg); + } + } + //------------------------------------------------------------------------------ + + private void BuildResult2(PolyTree polytree) + { + polytree.Clear(); + + //add each output polygon/contour to polytree ... + polytree.m_AllPolys.Capacity = m_PolyOuts.Count; + for (int i = 0; i < m_PolyOuts.Count; i++) + { + OutRec outRec = m_PolyOuts[i]; + int cnt = PointCount(outRec.Pts); + if ((outRec.IsOpen && cnt < 2) || + (!outRec.IsOpen && cnt < 3)) continue; + FixHoleLinkage(outRec); + PolyNode pn = new PolyNode(); + polytree.m_AllPolys.Add(pn); + outRec.PolyNode = pn; + pn.m_polygon.Capacity = cnt; + OutPt op = outRec.Pts.Prev; + for (int j = 0; j < cnt; j++) + { + pn.m_polygon.Add(op.Pt); + op = op.Prev; + } + } + + //fixup PolyNode links etc ... + polytree.m_Childs.Capacity = m_PolyOuts.Count; + for (int i = 0; i < m_PolyOuts.Count; i++) + { + OutRec outRec = m_PolyOuts[i]; + if (outRec.PolyNode == null) continue; + else if (outRec.IsOpen) + { + outRec.PolyNode.IsOpen = true; + polytree.AddChild(outRec.PolyNode); + } + else if (outRec.FirstLeft != null && + outRec.FirstLeft.PolyNode != null) + outRec.FirstLeft.PolyNode.AddChild(outRec.PolyNode); + else + polytree.AddChild(outRec.PolyNode); + } + } + //------------------------------------------------------------------------------ + + private void FixupOutPolyline(OutRec outrec) + { + OutPt pp = outrec.Pts; + OutPt lastPP = pp.Prev; + while (pp != lastPP) + { + pp = pp.Next; + if (pp.Pt == pp.Prev.Pt) + { + if (pp == lastPP) lastPP = pp.Prev; + OutPt tmpPP = pp.Prev; + tmpPP.Next = pp.Next; + pp.Next.Prev = tmpPP; + pp = tmpPP; + } + } + if (pp == pp.Prev) outrec.Pts = null; + } + //------------------------------------------------------------------------------ + + private void FixupOutPolygon(OutRec outRec) + { + //FixupOutPolygon() - removes duplicate points and simplifies consecutive + //parallel edges by removing the middle vertex. + OutPt lastOK = null; + outRec.BottomPt = null; + OutPt pp = outRec.Pts; + bool preserveCol = PreserveCollinear || StrictlySimple; + for (;;) + { + if (pp.Prev == pp || pp.Prev == pp.Next) + { + outRec.Pts = null; + return; + } + //test for duplicate points and collinear edges ... + if ((pp.Pt == pp.Next.Pt) || (pp.Pt == pp.Prev.Pt) || + (SlopesEqual(pp.Prev.Pt, pp.Pt, pp.Next.Pt, m_UseFullRange) && + (!preserveCol || !Pt2IsBetweenPt1AndPt3(pp.Prev.Pt, pp.Pt, pp.Next.Pt)))) + { + lastOK = null; + pp.Prev.Next = pp.Next; + pp.Next.Prev = pp.Prev; + pp = pp.Prev; + } + else if (pp == lastOK) break; + else + { + if (lastOK == null) lastOK = pp; + pp = pp.Next; + } + } + outRec.Pts = pp; + } + //------------------------------------------------------------------------------ + + OutPt DupOutPt(OutPt outPt, bool InsertAfter) + { + OutPt result = new OutPt(); + result.Pt = outPt.Pt; + result.Idx = outPt.Idx; + if (InsertAfter) + { + result.Next = outPt.Next; + result.Prev = outPt; + outPt.Next.Prev = result; + outPt.Next = result; + } + else + { + result.Prev = outPt.Prev; + result.Next = outPt; + outPt.Prev.Next = result; + outPt.Prev = result; + } + return result; + } + //------------------------------------------------------------------------------ + + bool GetOverlap(cInt a1, cInt a2, cInt b1, cInt b2, out cInt Left, out cInt Right) + { + if (a1 < a2) + { + if (b1 < b2) {Left = Math.Max(a1,b1); Right = Math.Min(a2,b2);} + else {Left = Math.Max(a1,b2); Right = Math.Min(a2,b1);} + } + else + { + if (b1 < b2) {Left = Math.Max(a2,b1); Right = Math.Min(a1,b2);} + else { Left = Math.Max(a2, b2); Right = Math.Min(a1, b1); } + } + return Left < Right; + } + //------------------------------------------------------------------------------ + + bool JoinHorz(OutPt op1, OutPt op1b, OutPt op2, OutPt op2b, + IntPoint Pt, bool DiscardLeft) + { + Direction Dir1 = (op1.Pt.X > op1b.Pt.X ? + Direction.dRightToLeft : Direction.dLeftToRight); + Direction Dir2 = (op2.Pt.X > op2b.Pt.X ? + Direction.dRightToLeft : Direction.dLeftToRight); + if (Dir1 == Dir2) return false; + + //When DiscardLeft, we want Op1b to be on the Left of Op1, otherwise we + //want Op1b to be on the Right. (And likewise with Op2 and Op2b.) + //So, to facilitate this while inserting Op1b and Op2b ... + //when DiscardLeft, make sure we're AT or RIGHT of Pt before adding Op1b, + //otherwise make sure we're AT or LEFT of Pt. (Likewise with Op2b.) + if (Dir1 == Direction.dLeftToRight) + { + while (op1.Next.Pt.X <= Pt.X && + op1.Next.Pt.X >= op1.Pt.X && op1.Next.Pt.Y == Pt.Y) + op1 = op1.Next; + if (DiscardLeft && (op1.Pt.X != Pt.X)) op1 = op1.Next; + op1b = DupOutPt(op1, !DiscardLeft); + if (op1b.Pt != Pt) + { + op1 = op1b; + op1.Pt = Pt; + op1b = DupOutPt(op1, !DiscardLeft); + } + } + else + { + while (op1.Next.Pt.X >= Pt.X && + op1.Next.Pt.X <= op1.Pt.X && op1.Next.Pt.Y == Pt.Y) + op1 = op1.Next; + if (!DiscardLeft && (op1.Pt.X != Pt.X)) op1 = op1.Next; + op1b = DupOutPt(op1, DiscardLeft); + if (op1b.Pt != Pt) + { + op1 = op1b; + op1.Pt = Pt; + op1b = DupOutPt(op1, DiscardLeft); + } + } + + if (Dir2 == Direction.dLeftToRight) + { + while (op2.Next.Pt.X <= Pt.X && + op2.Next.Pt.X >= op2.Pt.X && op2.Next.Pt.Y == Pt.Y) + op2 = op2.Next; + if (DiscardLeft && (op2.Pt.X != Pt.X)) op2 = op2.Next; + op2b = DupOutPt(op2, !DiscardLeft); + if (op2b.Pt != Pt) + { + op2 = op2b; + op2.Pt = Pt; + op2b = DupOutPt(op2, !DiscardLeft); + }; + } else + { + while (op2.Next.Pt.X >= Pt.X && + op2.Next.Pt.X <= op2.Pt.X && op2.Next.Pt.Y == Pt.Y) + op2 = op2.Next; + if (!DiscardLeft && (op2.Pt.X != Pt.X)) op2 = op2.Next; + op2b = DupOutPt(op2, DiscardLeft); + if (op2b.Pt != Pt) + { + op2 = op2b; + op2.Pt = Pt; + op2b = DupOutPt(op2, DiscardLeft); + }; + }; + + if ((Dir1 == Direction.dLeftToRight) == DiscardLeft) + { + op1.Prev = op2; + op2.Next = op1; + op1b.Next = op2b; + op2b.Prev = op1b; + } + else + { + op1.Next = op2; + op2.Prev = op1; + op1b.Prev = op2b; + op2b.Next = op1b; + } + return true; + } + //------------------------------------------------------------------------------ + + private bool JoinPoints(Join j, OutRec outRec1, OutRec outRec2) + { + OutPt op1 = j.OutPt1, op1b; + OutPt op2 = j.OutPt2, op2b; + + //There are 3 kinds of joins for output polygons ... + //1. Horizontal joins where Join.OutPt1 & Join.OutPt2 are vertices anywhere + //along (horizontal) collinear edges (& Join.OffPt is on the same horizontal). + //2. Non-horizontal joins where Join.OutPt1 & Join.OutPt2 are at the same + //location at the Bottom of the overlapping segment (& Join.OffPt is above). + //3. StrictlySimple joins where edges touch but are not collinear and where + //Join.OutPt1, Join.OutPt2 & Join.OffPt all share the same point. + bool isHorizontal = (j.OutPt1.Pt.Y == j.OffPt.Y); + + if (isHorizontal && (j.OffPt == j.OutPt1.Pt) && (j.OffPt == j.OutPt2.Pt)) + { + //Strictly Simple join ... + if (outRec1 != outRec2) return false; + op1b = j.OutPt1.Next; + while (op1b != op1 && (op1b.Pt == j.OffPt)) + op1b = op1b.Next; + bool reverse1 = (op1b.Pt.Y > j.OffPt.Y); + op2b = j.OutPt2.Next; + while (op2b != op2 && (op2b.Pt == j.OffPt)) + op2b = op2b.Next; + bool reverse2 = (op2b.Pt.Y > j.OffPt.Y); + if (reverse1 == reverse2) return false; + if (reverse1) + { + op1b = DupOutPt(op1, false); + op2b = DupOutPt(op2, true); + op1.Prev = op2; + op2.Next = op1; + op1b.Next = op2b; + op2b.Prev = op1b; + j.OutPt1 = op1; + j.OutPt2 = op1b; + return true; + } else + { + op1b = DupOutPt(op1, true); + op2b = DupOutPt(op2, false); + op1.Next = op2; + op2.Prev = op1; + op1b.Prev = op2b; + op2b.Next = op1b; + j.OutPt1 = op1; + j.OutPt2 = op1b; + return true; + } + } + else if (isHorizontal) + { + //treat horizontal joins differently to non-horizontal joins since with + //them we're not yet sure where the overlapping is. OutPt1.Pt & OutPt2.Pt + //may be anywhere along the horizontal edge. + op1b = op1; + while (op1.Prev.Pt.Y == op1.Pt.Y && op1.Prev != op1b && op1.Prev != op2) + op1 = op1.Prev; + while (op1b.Next.Pt.Y == op1b.Pt.Y && op1b.Next != op1 && op1b.Next != op2) + op1b = op1b.Next; + if (op1b.Next == op1 || op1b.Next == op2) return false; //a flat 'polygon' + + op2b = op2; + while (op2.Prev.Pt.Y == op2.Pt.Y && op2.Prev != op2b && op2.Prev != op1b) + op2 = op2.Prev; + while (op2b.Next.Pt.Y == op2b.Pt.Y && op2b.Next != op2 && op2b.Next != op1) + op2b = op2b.Next; + if (op2b.Next == op2 || op2b.Next == op1) return false; //a flat 'polygon' + + cInt Left, Right; + //Op1 -. Op1b & Op2 -. Op2b are the extremites of the horizontal edges + if (!GetOverlap(op1.Pt.X, op1b.Pt.X, op2.Pt.X, op2b.Pt.X, out Left, out Right)) + return false; + + //DiscardLeftSide: when overlapping edges are joined, a spike will created + //which needs to be cleaned up. However, we don't want Op1 or Op2 caught up + //on the discard Side as either may still be needed for other joins ... + IntPoint Pt; + bool DiscardLeftSide; + if (op1.Pt.X >= Left && op1.Pt.X <= Right) + { + Pt = op1.Pt; DiscardLeftSide = (op1.Pt.X > op1b.Pt.X); + } + else if (op2.Pt.X >= Left&& op2.Pt.X <= Right) + { + Pt = op2.Pt; DiscardLeftSide = (op2.Pt.X > op2b.Pt.X); + } + else if (op1b.Pt.X >= Left && op1b.Pt.X <= Right) + { + Pt = op1b.Pt; DiscardLeftSide = op1b.Pt.X > op1.Pt.X; + } + else + { + Pt = op2b.Pt; DiscardLeftSide = (op2b.Pt.X > op2.Pt.X); + } + j.OutPt1 = op1; + j.OutPt2 = op2; + return JoinHorz(op1, op1b, op2, op2b, Pt, DiscardLeftSide); + } else + { + //nb: For non-horizontal joins ... + // 1. Jr.OutPt1.Pt.Y == Jr.OutPt2.Pt.Y + // 2. Jr.OutPt1.Pt > Jr.OffPt.Y + + //make sure the polygons are correctly oriented ... + op1b = op1.Next; + while ((op1b.Pt == op1.Pt) && (op1b != op1)) op1b = op1b.Next; + bool Reverse1 = ((op1b.Pt.Y > op1.Pt.Y) || + !SlopesEqual(op1.Pt, op1b.Pt, j.OffPt, m_UseFullRange)); + if (Reverse1) + { + op1b = op1.Prev; + while ((op1b.Pt == op1.Pt) && (op1b != op1)) op1b = op1b.Prev; + if ((op1b.Pt.Y > op1.Pt.Y) || + !SlopesEqual(op1.Pt, op1b.Pt, j.OffPt, m_UseFullRange)) return false; + }; + op2b = op2.Next; + while ((op2b.Pt == op2.Pt) && (op2b != op2)) op2b = op2b.Next; + bool Reverse2 = ((op2b.Pt.Y > op2.Pt.Y) || + !SlopesEqual(op2.Pt, op2b.Pt, j.OffPt, m_UseFullRange)); + if (Reverse2) + { + op2b = op2.Prev; + while ((op2b.Pt == op2.Pt) && (op2b != op2)) op2b = op2b.Prev; + if ((op2b.Pt.Y > op2.Pt.Y) || + !SlopesEqual(op2.Pt, op2b.Pt, j.OffPt, m_UseFullRange)) return false; + } + + if ((op1b == op1) || (op2b == op2) || (op1b == op2b) || + ((outRec1 == outRec2) && (Reverse1 == Reverse2))) return false; + + if (Reverse1) + { + op1b = DupOutPt(op1, false); + op2b = DupOutPt(op2, true); + op1.Prev = op2; + op2.Next = op1; + op1b.Next = op2b; + op2b.Prev = op1b; + j.OutPt1 = op1; + j.OutPt2 = op1b; + return true; + } else + { + op1b = DupOutPt(op1, true); + op2b = DupOutPt(op2, false); + op1.Next = op2; + op2.Prev = op1; + op1b.Prev = op2b; + op2b.Next = op1b; + j.OutPt1 = op1; + j.OutPt2 = op1b; + return true; + } + } + } + //---------------------------------------------------------------------- + + public static int PointInPolygon(IntPoint pt, Path path) + { + //returns 0 if false, +1 if true, -1 if pt ON polygon boundary + //See "The Point in Polygon Problem for Arbitrary Polygons" by Hormann & Agathos + //http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.88.5498&rep=rep1&type=pdf + int result = 0, cnt = path.Count; + if (cnt < 3) return 0; + IntPoint ip = path[0]; + for (int i = 1; i <= cnt; ++i) + { + IntPoint ipNext = (i == cnt ? path[0] : path[i]); + if (ipNext.Y == pt.Y) + { + if ((ipNext.X == pt.X) || (ip.Y == pt.Y && + ((ipNext.X > pt.X) == (ip.X < pt.X)))) return -1; + } + if ((ip.Y < pt.Y) != (ipNext.Y < pt.Y)) + { + if (ip.X >= pt.X) + { + if (ipNext.X > pt.X) result = 1 - result; + else + { + double d = (double)(ip.X - pt.X) * (ipNext.Y - pt.Y) - + (double)(ipNext.X - pt.X) * (ip.Y - pt.Y); + if (d == 0) return -1; + else if ((d > 0) == (ipNext.Y > ip.Y)) result = 1 - result; + } + } + else + { + if (ipNext.X > pt.X) + { + double d = (double)(ip.X - pt.X) * (ipNext.Y - pt.Y) - + (double)(ipNext.X - pt.X) * (ip.Y - pt.Y); + if (d == 0) return -1; + else if ((d > 0) == (ipNext.Y > ip.Y)) result = 1 - result; + } + } + } + ip = ipNext; + } + return result; + } + //------------------------------------------------------------------------------ + + //See "The Point in Polygon Problem for Arbitrary Polygons" by Hormann & Agathos + //http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.88.5498&rep=rep1&type=pdf + private static int PointInPolygon(IntPoint pt, OutPt op) + { + //returns 0 if false, +1 if true, -1 if pt ON polygon boundary + int result = 0; + OutPt startOp = op; + cInt ptx = pt.X, pty = pt.Y; + cInt poly0x = op.Pt.X, poly0y = op.Pt.Y; + do + { + op = op.Next; + cInt poly1x = op.Pt.X, poly1y = op.Pt.Y; + + if (poly1y == pty) + { + if ((poly1x == ptx) || (poly0y == pty && + ((poly1x > ptx) == (poly0x < ptx)))) return -1; + } + if ((poly0y < pty) != (poly1y < pty)) + { + if (poly0x >= ptx) + { + if (poly1x > ptx) result = 1 - result; + else + { + double d = (double)(poly0x - ptx) * (poly1y - pty) - + (double)(poly1x - ptx) * (poly0y - pty); + if (d == 0) return -1; + if ((d > 0) == (poly1y > poly0y)) result = 1 - result; + } + } + else + { + if (poly1x > ptx) + { + double d = (double)(poly0x - ptx) * (poly1y - pty) - + (double)(poly1x - ptx) * (poly0y - pty); + if (d == 0) return -1; + if ((d > 0) == (poly1y > poly0y)) result = 1 - result; + } + } + } + poly0x = poly1x; poly0y = poly1y; + } while (startOp != op); + return result; + } + //------------------------------------------------------------------------------ + + private static bool Poly2ContainsPoly1(OutPt outPt1, OutPt outPt2) + { + OutPt op = outPt1; + do + { + //nb: PointInPolygon returns 0 if false, +1 if true, -1 if pt on polygon + int res = PointInPolygon(op.Pt, outPt2); + if (res >= 0) return res > 0; + op = op.Next; + } + while (op != outPt1); + return true; + } + //---------------------------------------------------------------------- + + private void FixupFirstLefts1(OutRec OldOutRec, OutRec NewOutRec) + { + foreach (OutRec outRec in m_PolyOuts) + { + OutRec firstLeft = ParseFirstLeft(outRec.FirstLeft); + if (outRec.Pts != null && firstLeft == OldOutRec) + { + if (Poly2ContainsPoly1(outRec.Pts, NewOutRec.Pts)) + outRec.FirstLeft = NewOutRec; + } + } + } + //---------------------------------------------------------------------- + + private void FixupFirstLefts2(OutRec innerOutRec, OutRec outerOutRec) + { + //A polygon has split into two such that one is now the inner of the other. + //It's possible that these polygons now wrap around other polygons, so check + //every polygon that's also contained by OuterOutRec's FirstLeft container + //(including nil) to see if they've become inner to the new inner polygon ... + OutRec orfl = outerOutRec.FirstLeft; + foreach (OutRec outRec in m_PolyOuts) + { + if (outRec.Pts == null || outRec == outerOutRec || outRec == innerOutRec) + continue; + OutRec firstLeft = ParseFirstLeft(outRec.FirstLeft); + if (firstLeft != orfl && firstLeft != innerOutRec && firstLeft != outerOutRec) + continue; + if (Poly2ContainsPoly1(outRec.Pts, innerOutRec.Pts)) + outRec.FirstLeft = innerOutRec; + else if (Poly2ContainsPoly1(outRec.Pts, outerOutRec.Pts)) + outRec.FirstLeft = outerOutRec; + else if (outRec.FirstLeft == innerOutRec || outRec.FirstLeft == outerOutRec) + outRec.FirstLeft = orfl; + } + } + //---------------------------------------------------------------------- + + private void FixupFirstLefts3(OutRec OldOutRec, OutRec NewOutRec) + { + //same as FixupFirstLefts1 but doesn't call Poly2ContainsPoly1() + foreach (OutRec outRec in m_PolyOuts) + { + OutRec firstLeft = ParseFirstLeft(outRec.FirstLeft); + if (outRec.Pts != null && firstLeft == OldOutRec) + outRec.FirstLeft = NewOutRec; + } + } + //---------------------------------------------------------------------- + + private static OutRec ParseFirstLeft(OutRec FirstLeft) + { + while (FirstLeft != null && FirstLeft.Pts == null) + FirstLeft = FirstLeft.FirstLeft; + return FirstLeft; + } + //------------------------------------------------------------------------------ + + private void JoinCommonEdges() + { + for (int i = 0; i < m_Joins.Count; i++) + { + Join join = m_Joins[i]; + + OutRec outRec1 = GetOutRec(join.OutPt1.Idx); + OutRec outRec2 = GetOutRec(join.OutPt2.Idx); + + if (outRec1.Pts == null || outRec2.Pts == null) continue; + if (outRec1.IsOpen || outRec2.IsOpen) continue; + + //get the polygon fragment with the correct hole state (FirstLeft) + //before calling JoinPoints() ... + OutRec holeStateRec; + if (outRec1 == outRec2) holeStateRec = outRec1; + else if (OutRec1RightOfOutRec2(outRec1, outRec2)) holeStateRec = outRec2; + else if (OutRec1RightOfOutRec2(outRec2, outRec1)) holeStateRec = outRec1; + else holeStateRec = GetLowermostRec(outRec1, outRec2); + + if (!JoinPoints(join, outRec1, outRec2)) continue; + + if (outRec1 == outRec2) + { + //instead of joining two polygons, we've just created a new one by + //splitting one polygon into two. + outRec1.Pts = join.OutPt1; + outRec1.BottomPt = null; + outRec2 = CreateOutRec(); + outRec2.Pts = join.OutPt2; + + //update all OutRec2.Pts Idx's ... + UpdateOutPtIdxs(outRec2); + + if (Poly2ContainsPoly1(outRec2.Pts, outRec1.Pts)) + { + //outRec1 contains outRec2 ... + outRec2.IsHole = !outRec1.IsHole; + outRec2.FirstLeft = outRec1; + + if (m_UsingPolyTree) FixupFirstLefts2(outRec2, outRec1); + + if ((outRec2.IsHole ^ ReverseSolution) == (Area(outRec2) > 0)) + ReversePolyPtLinks(outRec2.Pts); + + } + else if (Poly2ContainsPoly1(outRec1.Pts, outRec2.Pts)) + { + //outRec2 contains outRec1 ... + outRec2.IsHole = outRec1.IsHole; + outRec1.IsHole = !outRec2.IsHole; + outRec2.FirstLeft = outRec1.FirstLeft; + outRec1.FirstLeft = outRec2; + + if (m_UsingPolyTree) FixupFirstLefts2(outRec1, outRec2); + + if ((outRec1.IsHole ^ ReverseSolution) == (Area(outRec1) > 0)) + ReversePolyPtLinks(outRec1.Pts); + } + else + { + //the 2 polygons are completely separate ... + outRec2.IsHole = outRec1.IsHole; + outRec2.FirstLeft = outRec1.FirstLeft; + + //fixup FirstLeft pointers that may need reassigning to OutRec2 + if (m_UsingPolyTree) FixupFirstLefts1(outRec1, outRec2); + } + + } else + { + //joined 2 polygons together ... + + outRec2.Pts = null; + outRec2.BottomPt = null; + outRec2.Idx = outRec1.Idx; + + outRec1.IsHole = holeStateRec.IsHole; + if (holeStateRec == outRec2) + outRec1.FirstLeft = outRec2.FirstLeft; + outRec2.FirstLeft = outRec1; + + //fixup FirstLeft pointers that may need reassigning to OutRec1 + if (m_UsingPolyTree) FixupFirstLefts3(outRec2, outRec1); + } + } + } + //------------------------------------------------------------------------------ + + private void UpdateOutPtIdxs(OutRec outrec) + { + OutPt op = outrec.Pts; + do + { + op.Idx = outrec.Idx; + op = op.Prev; + } + while(op != outrec.Pts); + } + //------------------------------------------------------------------------------ + + private void DoSimplePolygons() + { + int i = 0; + while (i < m_PolyOuts.Count) + { + OutRec outrec = m_PolyOuts[i++]; + OutPt op = outrec.Pts; + if (op == null || outrec.IsOpen) continue; + do //for each Pt in Polygon until duplicate found do ... + { + OutPt op2 = op.Next; + while (op2 != outrec.Pts) + { + if ((op.Pt == op2.Pt) && op2.Next != op && op2.Prev != op) + { + //split the polygon into two ... + OutPt op3 = op.Prev; + OutPt op4 = op2.Prev; + op.Prev = op4; + op4.Next = op; + op2.Prev = op3; + op3.Next = op2; + + outrec.Pts = op; + OutRec outrec2 = CreateOutRec(); + outrec2.Pts = op2; + UpdateOutPtIdxs(outrec2); + if (Poly2ContainsPoly1(outrec2.Pts, outrec.Pts)) + { + //OutRec2 is contained by OutRec1 ... + outrec2.IsHole = !outrec.IsHole; + outrec2.FirstLeft = outrec; + if (m_UsingPolyTree) FixupFirstLefts2(outrec2, outrec); + } + else + if (Poly2ContainsPoly1(outrec.Pts, outrec2.Pts)) + { + //OutRec1 is contained by OutRec2 ... + outrec2.IsHole = outrec.IsHole; + outrec.IsHole = !outrec2.IsHole; + outrec2.FirstLeft = outrec.FirstLeft; + outrec.FirstLeft = outrec2; + if (m_UsingPolyTree) FixupFirstLefts2(outrec, outrec2); + } + else + { + //the 2 polygons are separate ... + outrec2.IsHole = outrec.IsHole; + outrec2.FirstLeft = outrec.FirstLeft; + if (m_UsingPolyTree) FixupFirstLefts1(outrec, outrec2); + } + op2 = op; //ie get ready for the next iteration + } + op2 = op2.Next; + } + op = op.Next; + } + while (op != outrec.Pts); + } + } + //------------------------------------------------------------------------------ + + public static double Area(Path poly) + { + int cnt = (int)poly.Count; + if (cnt < 3) return 0; + double a = 0; + for (int i = 0, j = cnt - 1; i < cnt; ++i) + { + a += ((double)poly[j].X + poly[i].X) * ((double)poly[j].Y - poly[i].Y); + j = i; + } + return -a * 0.5; + } + //------------------------------------------------------------------------------ + + internal double Area(OutRec outRec) + { + return Area(outRec.Pts); + } + //------------------------------------------------------------------------------ + + internal double Area(OutPt op) + { + OutPt opFirst = op; + if (op == null) return 0; + double a = 0; + do { + a = a + (double)(op.Prev.Pt.X + op.Pt.X) * (double)(op.Prev.Pt.Y - op.Pt.Y); + op = op.Next; + } while (op != opFirst); + return a * 0.5; + } + + //------------------------------------------------------------------------------ + // SimplifyPolygon functions ... + // Convert self-intersecting polygons into simple polygons + //------------------------------------------------------------------------------ + + public static Paths SimplifyPolygon(Path poly, + PolyFillType fillType = PolyFillType.pftEvenOdd) + { + Paths result = new Paths(); + Clipper c = new Clipper(); + c.StrictlySimple = true; + c.AddPath(poly, PolyType.ptSubject, true); + c.Execute(ClipType.ctUnion, result, fillType, fillType); + return result; + } + //------------------------------------------------------------------------------ + + public static Paths SimplifyPolygons(Paths polys, + PolyFillType fillType = PolyFillType.pftEvenOdd) + { + Paths result = new Paths(); + Clipper c = new Clipper(); + c.StrictlySimple = true; + c.AddPaths(polys, PolyType.ptSubject, true); + c.Execute(ClipType.ctUnion, result, fillType, fillType); + return result; + } + //------------------------------------------------------------------------------ + + private static double DistanceSqrd(IntPoint pt1, IntPoint pt2) + { + double dx = ((double)pt1.X - pt2.X); + double dy = ((double)pt1.Y - pt2.Y); + return (dx*dx + dy*dy); + } + //------------------------------------------------------------------------------ + + private static double DistanceFromLineSqrd(IntPoint pt, IntPoint ln1, IntPoint ln2) + { + //The equation of a line in general form (Ax + By + C = 0) + //given 2 points (x¹,y¹) & (x²,y²) is ... + //(y¹ - y²)x + (x² - x¹)y + (y² - y¹)x¹ - (x² - x¹)y¹ = 0 + //A = (y¹ - y²); B = (x² - x¹); C = (y² - y¹)x¹ - (x² - x¹)y¹ + //perpendicular distance of point (x³,y³) = (Ax³ + By³ + C)/Sqrt(A² + B²) + //see http://en.wikipedia.org/wiki/Perpendicular_distance + double A = ln1.Y - ln2.Y; + double B = ln2.X - ln1.X; + double C = A * ln1.X + B * ln1.Y; + C = A * pt.X + B * pt.Y - C; + return (C * C) / (A * A + B * B); + } + //--------------------------------------------------------------------------- + + private static bool SlopesNearCollinear(IntPoint pt1, + IntPoint pt2, IntPoint pt3, double distSqrd) + { + //this function is more accurate when the point that's GEOMETRICALLY + //between the other 2 points is the one that's tested for distance. + //nb: with 'spikes', either pt1 or pt3 is geometrically between the other pts + if (Math.Abs(pt1.X - pt2.X) > Math.Abs(pt1.Y - pt2.Y)) + { + if ((pt1.X > pt2.X) == (pt1.X < pt3.X)) + return DistanceFromLineSqrd(pt1, pt2, pt3) < distSqrd; + else if ((pt2.X > pt1.X) == (pt2.X < pt3.X)) + return DistanceFromLineSqrd(pt2, pt1, pt3) < distSqrd; + else + return DistanceFromLineSqrd(pt3, pt1, pt2) < distSqrd; + } + else + { + if ((pt1.Y > pt2.Y) == (pt1.Y < pt3.Y)) + return DistanceFromLineSqrd(pt1, pt2, pt3) < distSqrd; + else if ((pt2.Y > pt1.Y) == (pt2.Y < pt3.Y)) + return DistanceFromLineSqrd(pt2, pt1, pt3) < distSqrd; + else + return DistanceFromLineSqrd(pt3, pt1, pt2) < distSqrd; + } + } + //------------------------------------------------------------------------------ + + private static bool PointsAreClose(IntPoint pt1, IntPoint pt2, double distSqrd) + { + double dx = (double)pt1.X - pt2.X; + double dy = (double)pt1.Y - pt2.Y; + return ((dx * dx) + (dy * dy) <= distSqrd); + } + //------------------------------------------------------------------------------ + + private static OutPt ExcludeOp(OutPt op) + { + OutPt result = op.Prev; + result.Next = op.Next; + op.Next.Prev = result; + result.Idx = 0; + return result; + } + //------------------------------------------------------------------------------ + + public static Path CleanPolygon(Path path, double distance = 1.415) + { + //distance = proximity in units/pixels below which vertices will be stripped. + //Default ~= sqrt(2) so when adjacent vertices or semi-adjacent vertices have + //both x & y coords within 1 unit, then the second vertex will be stripped. + + int cnt = path.Count; + + if (cnt == 0) return new Path(); + + OutPt [] outPts = new OutPt[cnt]; + for (int i = 0; i < cnt; ++i) outPts[i] = new OutPt(); + + for (int i = 0; i < cnt; ++i) + { + outPts[i].Pt = path[i]; + outPts[i].Next = outPts[(i + 1) % cnt]; + outPts[i].Next.Prev = outPts[i]; + outPts[i].Idx = 0; + } + + double distSqrd = distance * distance; + OutPt op = outPts[0]; + while (op.Idx == 0 && op.Next != op.Prev) + { + if (PointsAreClose(op.Pt, op.Prev.Pt, distSqrd)) + { + op = ExcludeOp(op); + cnt--; + } + else if (PointsAreClose(op.Prev.Pt, op.Next.Pt, distSqrd)) + { + ExcludeOp(op.Next); + op = ExcludeOp(op); + cnt -= 2; + } + else if (SlopesNearCollinear(op.Prev.Pt, op.Pt, op.Next.Pt, distSqrd)) + { + op = ExcludeOp(op); + cnt--; + } + else + { + op.Idx = 1; + op = op.Next; + } + } + + if (cnt < 3) cnt = 0; + Path result = new Path(cnt); + for (int i = 0; i < cnt; ++i) + { + result.Add(op.Pt); + op = op.Next; + } + outPts = null; + return result; + } + //------------------------------------------------------------------------------ + + public static Paths CleanPolygons(Paths polys, + double distance = 1.415) + { + Paths result = new Paths(polys.Count); + for (int i = 0; i < polys.Count; i++) + result.Add(CleanPolygon(polys[i], distance)); + return result; + } + //------------------------------------------------------------------------------ + + internal static Paths Minkowski(Path pattern, Path path, bool IsSum, bool IsClosed) + { + int delta = (IsClosed ? 1 : 0); + int polyCnt = pattern.Count; + int pathCnt = path.Count; + Paths result = new Paths(pathCnt); + if (IsSum) + for (int i = 0; i < pathCnt; i++) + { + Path p = new Path(polyCnt); + foreach (IntPoint ip in pattern) + p.Add(new IntPoint(path[i].X + ip.X, path[i].Y + ip.Y)); + result.Add(p); + } + else + for (int i = 0; i < pathCnt; i++) + { + Path p = new Path(polyCnt); + foreach (IntPoint ip in pattern) + p.Add(new IntPoint(path[i].X - ip.X, path[i].Y - ip.Y)); + result.Add(p); + } + + Paths quads = new Paths((pathCnt + delta) * (polyCnt + 1)); + for (int i = 0; i < pathCnt - 1 + delta; i++) + for (int j = 0; j < polyCnt; j++) + { + Path quad = new Path(4); + quad.Add(result[i % pathCnt][j % polyCnt]); + quad.Add(result[(i + 1) % pathCnt][j % polyCnt]); + quad.Add(result[(i + 1) % pathCnt][(j + 1) % polyCnt]); + quad.Add(result[i % pathCnt][(j + 1) % polyCnt]); + if (!Orientation(quad)) quad.Reverse(); + quads.Add(quad); + } + return quads; + } + //------------------------------------------------------------------------------ + + public static Paths MinkowskiSum(Path pattern, Path path, bool pathIsClosed) + { + Paths paths = Minkowski(pattern, path, true, pathIsClosed); + Clipper c = new Clipper(); + c.AddPaths(paths, PolyType.ptSubject, true); + c.Execute(ClipType.ctUnion, paths, PolyFillType.pftNonZero, PolyFillType.pftNonZero); + return paths; + } + //------------------------------------------------------------------------------ + + private static Path TranslatePath(Path path, IntPoint delta) + { + Path outPath = new Path(path.Count); + for (int i = 0; i < path.Count; i++) + outPath.Add(new IntPoint(path[i].X + delta.X, path[i].Y + delta.Y)); + return outPath; + } + //------------------------------------------------------------------------------ + + public static Paths MinkowskiSum(Path pattern, Paths paths, bool pathIsClosed) + { + Paths solution = new Paths(); + Clipper c = new Clipper(); + for (int i = 0; i < paths.Count; ++i) + { + Paths tmp = Minkowski(pattern, paths[i], true, pathIsClosed); + c.AddPaths(tmp, PolyType.ptSubject, true); + if (pathIsClosed) + { + Path path = TranslatePath(paths[i], pattern[0]); + c.AddPath(path, PolyType.ptClip, true); + } + } + c.Execute(ClipType.ctUnion, solution, + PolyFillType.pftNonZero, PolyFillType.pftNonZero); + return solution; + } + //------------------------------------------------------------------------------ + + public static Paths MinkowskiDiff(Path poly1, Path poly2) + { + Paths paths = Minkowski(poly1, poly2, false, true); + Clipper c = new Clipper(); + c.AddPaths(paths, PolyType.ptSubject, true); + c.Execute(ClipType.ctUnion, paths, PolyFillType.pftNonZero, PolyFillType.pftNonZero); + return paths; + } + //------------------------------------------------------------------------------ + + internal enum NodeType { ntAny, ntOpen, ntClosed }; + + public static Paths PolyTreeToPaths(PolyTree polytree) + { + + Paths result = new Paths(); + result.Capacity = polytree.Total; + AddPolyNodeToPaths(polytree, NodeType.ntAny, result); + return result; + } + //------------------------------------------------------------------------------ + + internal static void AddPolyNodeToPaths(PolyNode polynode, NodeType nt, Paths paths) + { + bool match = true; + switch (nt) + { + case NodeType.ntOpen: return; + case NodeType.ntClosed: match = !polynode.IsOpen; break; + default: break; + } + + if (polynode.m_polygon.Count > 0 && match) + paths.Add(polynode.m_polygon); + foreach (PolyNode pn in polynode.Childs) + AddPolyNodeToPaths(pn, nt, paths); + } + //------------------------------------------------------------------------------ + + public static Paths OpenPathsFromPolyTree(PolyTree polytree) + { + Paths result = new Paths(); + result.Capacity = polytree.ChildCount; + for (int i = 0; i < polytree.ChildCount; i++) + if (polytree.Childs[i].IsOpen) + result.Add(polytree.Childs[i].m_polygon); + return result; + } + //------------------------------------------------------------------------------ + + public static Paths ClosedPathsFromPolyTree(PolyTree polytree) + { + Paths result = new Paths(); + result.Capacity = polytree.Total; + AddPolyNodeToPaths(polytree, NodeType.ntClosed, result); + return result; + } + //------------------------------------------------------------------------------ + + } //end Clipper + + public class ClipperOffset + { + private Paths m_destPolys; + private Path m_srcPoly; + private Path m_destPoly; + private List m_normals = new List(); + private double m_delta, m_sinA, m_sin, m_cos; + private double m_miterLim, m_StepsPerRad; + + private IntPoint m_lowest; + private PolyNode m_polyNodes = new PolyNode(); + + public double ArcTolerance { get; set; } + public double MiterLimit { get; set; } + + private const double two_pi = Math.PI * 2; + private const double def_arc_tolerance = 0.25; + + public ClipperOffset( + double miterLimit = 2.0, double arcTolerance = def_arc_tolerance) + { + MiterLimit = miterLimit; + ArcTolerance = arcTolerance; + m_lowest.X = -1; + } + //------------------------------------------------------------------------------ + + public void Clear() + { + m_polyNodes.Childs.Clear(); + m_lowest.X = -1; + } + //------------------------------------------------------------------------------ + + internal static cInt Round(double value) + { + return value < 0 ? (cInt)(value - 0.5) : (cInt)(value + 0.5); + } + //------------------------------------------------------------------------------ + + public void AddPath(Path path, JoinType joinType, EndType endType) + { + int highI = path.Count - 1; + if (highI < 0) return; + PolyNode newNode = new PolyNode(); + newNode.m_jointype = joinType; + newNode.m_endtype = endType; + + //strip duplicate points from path and also get index to the lowest point ... + if (endType == EndType.etClosedLine || endType == EndType.etClosedPolygon) + while (highI > 0 && path[0] == path[highI]) highI--; + newNode.m_polygon.Capacity = highI + 1; + newNode.m_polygon.Add(path[0]); + int j = 0, k = 0; + for (int i = 1; i <= highI; i++) + if (newNode.m_polygon[j] != path[i]) + { + j++; + newNode.m_polygon.Add(path[i]); + if (path[i].Y > newNode.m_polygon[k].Y || + (path[i].Y == newNode.m_polygon[k].Y && + path[i].X < newNode.m_polygon[k].X)) k = j; + } + if (endType == EndType.etClosedPolygon && j < 2) return; + + m_polyNodes.AddChild(newNode); + + //if this path's lowest pt is lower than all the others then update m_lowest + if (endType != EndType.etClosedPolygon) return; + if (m_lowest.X < 0) + m_lowest = new IntPoint(m_polyNodes.ChildCount - 1, k); + else + { + IntPoint ip = m_polyNodes.Childs[(int)m_lowest.X].m_polygon[(int)m_lowest.Y]; + if (newNode.m_polygon[k].Y > ip.Y || + (newNode.m_polygon[k].Y == ip.Y && + newNode.m_polygon[k].X < ip.X)) + m_lowest = new IntPoint(m_polyNodes.ChildCount - 1, k); + } + } + //------------------------------------------------------------------------------ + + public void AddPaths(Paths paths, JoinType joinType, EndType endType) + { + foreach (Path p in paths) + AddPath(p, joinType, endType); + } + //------------------------------------------------------------------------------ + + private void FixOrientations() + { + //fixup orientations of all closed paths if the orientation of the + //closed path with the lowermost vertex is wrong ... + if (m_lowest.X >= 0 && + !Clipper.Orientation(m_polyNodes.Childs[(int)m_lowest.X].m_polygon)) + { + for (int i = 0; i < m_polyNodes.ChildCount; i++) + { + PolyNode node = m_polyNodes.Childs[i]; + if (node.m_endtype == EndType.etClosedPolygon || + (node.m_endtype == EndType.etClosedLine && + Clipper.Orientation(node.m_polygon))) + node.m_polygon.Reverse(); + } + } + else + { + for (int i = 0; i < m_polyNodes.ChildCount; i++) + { + PolyNode node = m_polyNodes.Childs[i]; + if (node.m_endtype == EndType.etClosedLine && + !Clipper.Orientation(node.m_polygon)) + node.m_polygon.Reverse(); + } + } + } + //------------------------------------------------------------------------------ + + internal static DoublePoint GetUnitNormal(IntPoint pt1, IntPoint pt2) + { + double dx = (pt2.X - pt1.X); + double dy = (pt2.Y - pt1.Y); + if ((dx == 0) && (dy == 0)) return new DoublePoint(); + + double f = 1 * 1.0 / Math.Sqrt(dx * dx + dy * dy); + dx *= f; + dy *= f; + + return new DoublePoint(dy, -dx); + } + //------------------------------------------------------------------------------ + + private void DoOffset(double delta) + { + m_destPolys = new Paths(); + m_delta = delta; + + //if Zero offset, just copy any CLOSED polygons to m_p and return ... + if (ClipperBase.near_zero(delta)) + { + m_destPolys.Capacity = m_polyNodes.ChildCount; + for (int i = 0; i < m_polyNodes.ChildCount; i++) + { + PolyNode node = m_polyNodes.Childs[i]; + if (node.m_endtype == EndType.etClosedPolygon) + m_destPolys.Add(node.m_polygon); + } + return; + } + + //see offset_triginometry3.svg in the documentation folder ... + if (MiterLimit > 2) m_miterLim = 2 / (MiterLimit * MiterLimit); + else m_miterLim = 0.5; + + double y; + if (ArcTolerance <= 0.0) + y = def_arc_tolerance; + else if (ArcTolerance > Math.Abs(delta) * def_arc_tolerance) + y = Math.Abs(delta) * def_arc_tolerance; + else + y = ArcTolerance; + //see offset_triginometry2.svg in the documentation folder ... + double steps = Math.PI / Math.Acos(1 - y / Math.Abs(delta)); + m_sin = Math.Sin(two_pi / steps); + m_cos = Math.Cos(two_pi / steps); + m_StepsPerRad = steps / two_pi; + if (delta < 0.0) m_sin = -m_sin; + + m_destPolys.Capacity = m_polyNodes.ChildCount * 2; + for (int i = 0; i < m_polyNodes.ChildCount; i++) + { + PolyNode node = m_polyNodes.Childs[i]; + m_srcPoly = node.m_polygon; + + int len = m_srcPoly.Count; + + if (len == 0 || (delta <= 0 && (len < 3 || + node.m_endtype != EndType.etClosedPolygon))) + continue; + + m_destPoly = new Path(); + + if (len == 1) + { + if (node.m_jointype == JoinType.jtRound) + { + double X = 1.0, Y = 0.0; + for (int j = 1; j <= steps; j++) + { + m_destPoly.Add(new IntPoint( + Round(m_srcPoly[0].X + X * delta), + Round(m_srcPoly[0].Y + Y * delta))); + double X2 = X; + X = X * m_cos - m_sin * Y; + Y = X2 * m_sin + Y * m_cos; + } + } + else + { + double X = -1.0, Y = -1.0; + for (int j = 0; j < 4; ++j) + { + m_destPoly.Add(new IntPoint( + Round(m_srcPoly[0].X + X * delta), + Round(m_srcPoly[0].Y + Y * delta))); + if (X < 0) X = 1; + else if (Y < 0) Y = 1; + else X = -1; + } + } + m_destPolys.Add(m_destPoly); + continue; + } + + //build m_normals ... + m_normals.Clear(); + m_normals.Capacity = len; + for (int j = 0; j < len - 1; j++) + m_normals.Add(GetUnitNormal(m_srcPoly[j], m_srcPoly[j + 1])); + if (node.m_endtype == EndType.etClosedLine || + node.m_endtype == EndType.etClosedPolygon) + m_normals.Add(GetUnitNormal(m_srcPoly[len - 1], m_srcPoly[0])); + else + m_normals.Add(new DoublePoint(m_normals[len - 2])); + + if (node.m_endtype == EndType.etClosedPolygon) + { + int k = len - 1; + for (int j = 0; j < len; j++) + OffsetPoint(j, ref k, node.m_jointype); + m_destPolys.Add(m_destPoly); + } + else if (node.m_endtype == EndType.etClosedLine) + { + int k = len - 1; + for (int j = 0; j < len; j++) + OffsetPoint(j, ref k, node.m_jointype); + m_destPolys.Add(m_destPoly); + m_destPoly = new Path(); + //re-build m_normals ... + DoublePoint n = m_normals[len - 1]; + for (int j = len - 1; j > 0; j--) + m_normals[j] = new DoublePoint(-m_normals[j - 1].X, -m_normals[j - 1].Y); + m_normals[0] = new DoublePoint(-n.X, -n.Y); + k = 0; + for (int j = len - 1; j >= 0; j--) + OffsetPoint(j, ref k, node.m_jointype); + m_destPolys.Add(m_destPoly); + } + else + { + int k = 0; + for (int j = 1; j < len - 1; ++j) + OffsetPoint(j, ref k, node.m_jointype); + + IntPoint pt1; + if (node.m_endtype == EndType.etOpenButt) + { + int j = len - 1; + pt1 = new IntPoint((cInt)Round(m_srcPoly[j].X + m_normals[j].X * + delta), (cInt)Round(m_srcPoly[j].Y + m_normals[j].Y * delta)); + m_destPoly.Add(pt1); + pt1 = new IntPoint((cInt)Round(m_srcPoly[j].X - m_normals[j].X * + delta), (cInt)Round(m_srcPoly[j].Y - m_normals[j].Y * delta)); + m_destPoly.Add(pt1); + } + else + { + int j = len - 1; + k = len - 2; + m_sinA = 0; + m_normals[j] = new DoublePoint(-m_normals[j].X, -m_normals[j].Y); + if (node.m_endtype == EndType.etOpenSquare) + DoSquare(j, k); + else + DoRound(j, k); + } + + //re-build m_normals ... + for (int j = len - 1; j > 0; j--) + m_normals[j] = new DoublePoint(-m_normals[j - 1].X, -m_normals[j - 1].Y); + + m_normals[0] = new DoublePoint(-m_normals[1].X, -m_normals[1].Y); + + k = len - 1; + for (int j = k - 1; j > 0; --j) + OffsetPoint(j, ref k, node.m_jointype); + + if (node.m_endtype == EndType.etOpenButt) + { + pt1 = new IntPoint((cInt)Round(m_srcPoly[0].X - m_normals[0].X * delta), + (cInt)Round(m_srcPoly[0].Y - m_normals[0].Y * delta)); + m_destPoly.Add(pt1); + pt1 = new IntPoint((cInt)Round(m_srcPoly[0].X + m_normals[0].X * delta), + (cInt)Round(m_srcPoly[0].Y + m_normals[0].Y * delta)); + m_destPoly.Add(pt1); + } + else + { + k = 1; + m_sinA = 0; + if (node.m_endtype == EndType.etOpenSquare) + DoSquare(0, 1); + else + DoRound(0, 1); + } + m_destPolys.Add(m_destPoly); + } + } + } + //------------------------------------------------------------------------------ + + public void Execute(ref Paths solution, double delta) + { + solution.Clear(); + FixOrientations(); + DoOffset(delta); + //now clean up 'corners' ... + Clipper clpr = new Clipper(); + clpr.AddPaths(m_destPolys, PolyType.ptSubject, true); + if (delta > 0) + { + clpr.Execute(ClipType.ctUnion, solution, + PolyFillType.pftPositive, PolyFillType.pftPositive); + } + else + { + IntRect r = Clipper.GetBounds(m_destPolys); + Path outer = new Path(4); + + outer.Add(new IntPoint(r.left - 10, r.bottom + 10)); + outer.Add(new IntPoint(r.right + 10, r.bottom + 10)); + outer.Add(new IntPoint(r.right + 10, r.top - 10)); + outer.Add(new IntPoint(r.left - 10, r.top - 10)); + + clpr.AddPath(outer, PolyType.ptSubject, true); + clpr.ReverseSolution = true; + clpr.Execute(ClipType.ctUnion, solution, PolyFillType.pftNegative, PolyFillType.pftNegative); + if (solution.Count > 0) solution.RemoveAt(0); + } + } + //------------------------------------------------------------------------------ + + public void Execute(ref PolyTree solution, double delta) + { + solution.Clear(); + FixOrientations(); + DoOffset(delta); + + //now clean up 'corners' ... + Clipper clpr = new Clipper(); + clpr.AddPaths(m_destPolys, PolyType.ptSubject, true); + if (delta > 0) + { + clpr.Execute(ClipType.ctUnion, solution, + PolyFillType.pftPositive, PolyFillType.pftPositive); + } + else + { + IntRect r = Clipper.GetBounds(m_destPolys); + Path outer = new Path(4); + + outer.Add(new IntPoint(r.left - 10, r.bottom + 10)); + outer.Add(new IntPoint(r.right + 10, r.bottom + 10)); + outer.Add(new IntPoint(r.right + 10, r.top - 10)); + outer.Add(new IntPoint(r.left - 10, r.top - 10)); + + clpr.AddPath(outer, PolyType.ptSubject, true); + clpr.ReverseSolution = true; + clpr.Execute(ClipType.ctUnion, solution, PolyFillType.pftNegative, PolyFillType.pftNegative); + //remove the outer PolyNode rectangle ... + if (solution.ChildCount == 1 && solution.Childs[0].ChildCount > 0) + { + PolyNode outerNode = solution.Childs[0]; + solution.Childs.Capacity = outerNode.ChildCount; + solution.Childs[0] = outerNode.Childs[0]; + solution.Childs[0].m_Parent = solution; + for (int i = 1; i < outerNode.ChildCount; i++) + solution.AddChild(outerNode.Childs[i]); + } + else + solution.Clear(); + } + } + //------------------------------------------------------------------------------ + + void OffsetPoint(int j, ref int k, JoinType jointype) + { + //cross product ... + m_sinA = (m_normals[k].X * m_normals[j].Y - m_normals[j].X * m_normals[k].Y); + + if (Math.Abs(m_sinA * m_delta) < 1.0) + { + //dot product ... + double cosA = (m_normals[k].X * m_normals[j].X + m_normals[j].Y * m_normals[k].Y); + if (cosA > 0) // angle ==> 0 degrees + { + m_destPoly.Add(new IntPoint(Round(m_srcPoly[j].X + m_normals[k].X * m_delta), + Round(m_srcPoly[j].Y + m_normals[k].Y * m_delta))); + return; + } + //else angle ==> 180 degrees + } + else if (m_sinA > 1.0) m_sinA = 1.0; + else if (m_sinA < -1.0) m_sinA = -1.0; + + if (m_sinA * m_delta < 0) + { + m_destPoly.Add(new IntPoint(Round(m_srcPoly[j].X + m_normals[k].X * m_delta), + Round(m_srcPoly[j].Y + m_normals[k].Y * m_delta))); + m_destPoly.Add(m_srcPoly[j]); + m_destPoly.Add(new IntPoint(Round(m_srcPoly[j].X + m_normals[j].X * m_delta), + Round(m_srcPoly[j].Y + m_normals[j].Y * m_delta))); + } + else + switch (jointype) + { + case JoinType.jtMiter: + { + double r = 1 + (m_normals[j].X * m_normals[k].X + + m_normals[j].Y * m_normals[k].Y); + if (r >= m_miterLim) DoMiter(j, k, r); else DoSquare(j, k); + break; + } + case JoinType.jtSquare: DoSquare(j, k); break; + case JoinType.jtRound: DoRound(j, k); break; + } + k = j; + } + //------------------------------------------------------------------------------ + + internal void DoSquare(int j, int k) + { + double dx = Math.Tan(Math.Atan2(m_sinA, + m_normals[k].X * m_normals[j].X + m_normals[k].Y * m_normals[j].Y) / 4); + m_destPoly.Add(new IntPoint( + Round(m_srcPoly[j].X + m_delta * (m_normals[k].X - m_normals[k].Y * dx)), + Round(m_srcPoly[j].Y + m_delta * (m_normals[k].Y + m_normals[k].X * dx)))); + m_destPoly.Add(new IntPoint( + Round(m_srcPoly[j].X + m_delta * (m_normals[j].X + m_normals[j].Y * dx)), + Round(m_srcPoly[j].Y + m_delta * (m_normals[j].Y - m_normals[j].X * dx)))); + } + //------------------------------------------------------------------------------ + + internal void DoMiter(int j, int k, double r) + { + double q = m_delta / r; + m_destPoly.Add(new IntPoint(Round(m_srcPoly[j].X + (m_normals[k].X + m_normals[j].X) * q), + Round(m_srcPoly[j].Y + (m_normals[k].Y + m_normals[j].Y) * q))); + } + //------------------------------------------------------------------------------ + + internal void DoRound(int j, int k) + { + double a = Math.Atan2(m_sinA, + m_normals[k].X * m_normals[j].X + m_normals[k].Y * m_normals[j].Y); + int steps = Math.Max((int)Round(m_StepsPerRad * Math.Abs(a)),1); + + double X = m_normals[k].X, Y = m_normals[k].Y, X2; + for (int i = 0; i < steps; ++i) + { + m_destPoly.Add(new IntPoint( + Round(m_srcPoly[j].X + X * m_delta), + Round(m_srcPoly[j].Y + Y * m_delta))); + X2 = X; + X = X * m_cos - m_sin * Y; + Y = X2 * m_sin + Y * m_cos; + } + m_destPoly.Add(new IntPoint( + Round(m_srcPoly[j].X + m_normals[j].X * m_delta), + Round(m_srcPoly[j].Y + m_normals[j].Y * m_delta))); + } + //------------------------------------------------------------------------------ + } + + class ClipperException : Exception + { + public ClipperException(string description) : base(description){} + } + //------------------------------------------------------------------------------ + +} //end ClipperLib namespace diff --git a/External/Clipper/LICENSE.md b/External/Clipper/LICENSE.md new file mode 100644 index 0000000..3d94797 --- /dev/null +++ b/External/Clipper/LICENSE.md @@ -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. \ No newline at end of file diff --git a/External/Clipper/README.md b/External/Clipper/README.md new file mode 100644 index 0000000..31a2a32 --- /dev/null +++ b/External/Clipper/README.md @@ -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). \ No newline at end of file diff --git a/External/Triangle.NET/CHANGELOG.md b/External/Triangle.NET/CHANGELOG.md new file mode 100644 index 0000000..87ce397 --- /dev/null +++ b/External/Triangle.NET/CHANGELOG.md @@ -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). \ No newline at end of file diff --git a/External/Triangle.NET/README.md b/External/Triangle.NET/README.md new file mode 100644 index 0000000..f890811 --- /dev/null +++ b/External/Triangle.NET/README.md @@ -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). \ No newline at end of file diff --git a/External/Triangle.NET/Triangle/Behavior.cs b/External/Triangle.NET/Triangle/Behavior.cs new file mode 100644 index 0000000..15601a5 --- /dev/null +++ b/External/Triangle.NET/Triangle/Behavior.cs @@ -0,0 +1,248 @@ +// ----------------------------------------------------------------------- +// +// 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/ +// +// ----------------------------------------------------------------------- + +namespace TriangleNet +{ + using System; + using TriangleNet.Geometry; + + /// + /// Controls the behavior of the meshing software. + /// + 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 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; + + /// + /// Creates an instance of the Behavior class. + /// + public Behavior(bool quality = false, double minAngle = 20.0) + { + if (quality) + { + this.quality = true; + this.minAngle = minAngle; + + Update(); + } + } + + /// + /// Update quality options dependencies. + /// + 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 + + /// + /// No exact arithmetic. + /// + public static bool NoExact { get; set; } + + #endregion + + #region Public properties + + /// + /// Quality mesh generation. + /// + public bool Quality + { + get { return quality; } + set + { + quality = value; + if (quality) + { + Update(); + } + } + } + + /// + /// Minimum angle constraint. + /// + public double MinAngle + { + get { return minAngle; } + set { minAngle = value; Update(); } + } + + /// + /// Maximum angle constraint. + /// + public double MaxAngle + { + get { return maxAngle; } + set { maxAngle = value; Update(); } + } + + /// + /// Maximum area constraint. + /// + public double MaxArea + { + get { return maxArea; } + set + { + maxArea = value; + fixedArea = value > 0.0; + } + } + + /// + /// Apply a maximum triangle area constraint. + /// + public bool VarArea + { + get { return varArea; } + set { varArea = value; } + } + + /// + /// Input is a Planar Straight Line Graph. + /// + public bool Poly + { + get { return poly; } + set { poly = value; } + } + + /// + /// Apply a user-defined triangle constraint. + /// + public Func UserTest + { + get { return usertest; } + set { usertest = value; } + } + + /// + /// Enclose the convex hull with segments. + /// + public bool Convex + { + get { return convex; } + set { convex = value; } + } + + /// + /// Conforming Delaunay (all triangles are truly Delaunay). + /// + public bool ConformingDelaunay + { + get { return conformDel; } + set { conformDel = value; } + } + + /// + /// Suppresses boundary segment splitting. + /// + /// + /// 0 = split segments + /// 1 = no new vertices on the boundary + /// 2 = prevent all segment splitting, including internal boundaries + /// + public int NoBisect + { + get { return noBisect; } + set + { + noBisect = value; + if (noBisect < 0 || noBisect > 2) + { + noBisect = 0; + } + } + } + + /// + /// Compute boundary information. + /// + public bool UseBoundaryMarkers + { + get { return boundaryMarkers; } + set { boundaryMarkers = value; } + } + + /// + /// Ignores holes in polygons. + /// + public bool NoHoles + { + get { return noHoles; } + set { noHoles = value; } + } + + /// + /// Jettison unused vertices from output. + /// + public bool Jettison + { + get { return jettison; } + set { jettison = value; } + } + + #endregion + } +} diff --git a/External/Triangle.NET/Triangle/Configuration.cs b/External/Triangle.NET/Triangle/Configuration.cs new file mode 100644 index 0000000..6dcb8e4 --- /dev/null +++ b/External/Triangle.NET/Triangle/Configuration.cs @@ -0,0 +1,44 @@ +// ----------------------------------------------------------------------- +// +// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/ +// +// ----------------------------------------------------------------------- + +namespace TriangleNet +{ + using System; + using TriangleNet.Meshing; + using TriangleNet.Meshing.Algorithm; + + /// + /// Configure advanced aspects of the library. + /// + public class Configuration + { + public Configuration() + : this(() => RobustPredicates.Default, () => new TrianglePool()) + { + } + + public Configuration(Func predicates) + : this(predicates, () => new TrianglePool()) + { + } + + public Configuration(Func predicates, Func trianglePool) + { + Predicates = predicates; + TrianglePool = trianglePool; + } + + /// + /// Gets or sets the factory method for the implementation. + /// + public Func Predicates { get; set; } + + /// + /// Gets or sets the factory method for the . + /// + public Func TrianglePool { get; set; } + } +} diff --git a/External/Triangle.NET/Triangle/Enums.cs b/External/Triangle.NET/Triangle/Enums.cs new file mode 100644 index 0000000..3213cda --- /dev/null +++ b/External/Triangle.NET/Triangle/Enums.cs @@ -0,0 +1,46 @@ +// ----------------------------------------------------------------------- +// +// 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/ +// +// ----------------------------------------------------------------------- + +namespace TriangleNet +{ + /// + /// The type of the mesh vertex. + /// + public enum VertexType { InputVertex, SegmentVertex, FreeVertex, DeadVertex, UndeadVertex }; + + /// + /// Node renumbering algorithms. + /// + public enum NodeNumbering { None, Linear, CuthillMcKee }; + + /// + /// Labels that signify the result of point location. + /// + /// 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. + /// + public enum LocateResult { InTriangle, OnEdge, OnVertex, Outside }; + + /// + /// Labels that signify the result of vertex insertion. + /// + /// 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. + /// + enum InsertVertexResult { Successful, Encroaching, Violating, Duplicate }; + + /// + /// Labels that signify the result of direction finding. + /// + /// 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. + /// + enum FindDirectionResult { Within, Leftcollinear, Rightcollinear }; +} diff --git a/External/Triangle.NET/Triangle/Geometry/Contour.cs b/External/Triangle.NET/Triangle/Geometry/Contour.cs new file mode 100644 index 0000000..db8fce4 --- /dev/null +++ b/External/Triangle.NET/Triangle/Geometry/Contour.cs @@ -0,0 +1,247 @@ +// ----------------------------------------------------------------------- +// +// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/ +// +// ----------------------------------------------------------------------- + +namespace TriangleNet.Geometry +{ + using System; + using System.Linq; + using System.Collections.Generic; + + public class Contour + { + int marker; + + bool convex; + + /// + /// Gets or sets the list of points making up the contour. + /// + public List Points { get; set; } + + /// + /// Initializes a new instance of the class. + /// + /// The points that make up the contour. + public Contour(IEnumerable points) + : this(points, 0, false) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The points that make up the contour. + /// Contour marker. + public Contour(IEnumerable points, int marker) + : this(points, marker, false) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The points that make up the contour. + /// Contour marker. + /// The hole is convex. + public Contour(IEnumerable points, int marker, bool convex) + { + AddPoints(points); + + this.marker = marker; + this.convex = convex; + } + + public List GetSegments() + { + var segments = new List(); + + 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; + } + + /// + /// Try to find a point inside the contour. + /// + /// The number of iterations on each segment (default = 5). + /// Threshold for co-linear points (default = 2e-5). + /// Point inside the contour + /// Throws if no point could be found. + /// + /// 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 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). + /// + 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 points) + { + this.Points = new List(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 contour, int limit, double eps) + { + var bounds = new Rectangle(); + bounds.Expand(contour.Cast()); + + 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(); + } + + /// + /// Return true if the given point is inside the polygon, or false if it is not. + /// + /// The point to check. + /// The polygon (list of contour points). + /// + /// + /// 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/ + /// + private static bool IsPointInPolygon(Point point, List 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 + } +} diff --git a/External/Triangle.NET/Triangle/Geometry/Edge.cs b/External/Triangle.NET/Triangle/Geometry/Edge.cs new file mode 100644 index 0000000..f8daeb6 --- /dev/null +++ b/External/Triangle.NET/Triangle/Geometry/Edge.cs @@ -0,0 +1,58 @@ +// ----------------------------------------------------------------------- +// +// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/ +// +// ----------------------------------------------------------------------- + +namespace TriangleNet.Geometry +{ + /// + /// Represents a straight line segment in 2D space. + /// + public class Edge : IEdge + { + /// + /// Gets the first endpoints index. + /// + public int P0 + { + get; + private set; + } + + /// + /// Gets the second endpoints index. + /// + public int P1 + { + get; + private set; + } + + /// + /// Gets the segments boundary mark. + /// + public int Label + { + get; + private set; + } + + /// + /// Initializes a new instance of the class. + /// + public Edge(int p0, int p1) + : this(p0, p1, 0) + { } + + /// + /// Initializes a new instance of the class. + /// + public Edge(int p0, int p1, int label) + { + this.P0 = p0; + this.P1 = p1; + this.Label = label; + } + } +} diff --git a/External/Triangle.NET/Triangle/Geometry/ExtensionMethods.cs b/External/Triangle.NET/Triangle/Geometry/ExtensionMethods.cs new file mode 100644 index 0000000..9087adb --- /dev/null +++ b/External/Triangle.NET/Triangle/Geometry/ExtensionMethods.cs @@ -0,0 +1,143 @@ + +namespace TriangleNet.Geometry +{ + using System; + using TriangleNet.Meshing; + + public static class ExtensionMethods + { + #region IPolygon extensions + + /// + /// Triangulates a polygon. + /// + public static IMesh Triangulate(this IPolygon polygon) + { + return (new GenericMesher()).Triangulate(polygon, null, null); + } + + /// + /// Triangulates a polygon, applying constraint options. + /// + /// Constraint options. + public static IMesh Triangulate(this IPolygon polygon, ConstraintOptions options) + { + return (new GenericMesher()).Triangulate(polygon, options, null); + } + + /// + /// Triangulates a polygon, applying quality options. + /// + /// Quality options. + public static IMesh Triangulate(this IPolygon polygon, QualityOptions quality) + { + return (new GenericMesher()).Triangulate(polygon, null, quality); + } + + /// + /// Triangulates a polygon, applying quality and constraint options. + /// + /// Constraint options. + /// Quality options. + public static IMesh Triangulate(this IPolygon polygon, ConstraintOptions options, QualityOptions quality) + { + return (new GenericMesher()).Triangulate(polygon, options, quality); + } + + /// + /// Triangulates a polygon, applying quality and constraint options. + /// + /// Constraint options. + /// Quality options. + /// The triangulation algorithm. + 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 + + /// + /// Test whether a given point lies inside a triangle or not. + /// + /// Point to locate. + /// True, if point is inside or on the edge of this triangle. + public static bool Contains(this ITriangle triangle, Point p) + { + return Contains(triangle, p.X, p.Y); + } + + /// + /// Test whether a given point lies inside a triangle or not. + /// + /// Point to locate. + /// Point to locate. + /// True, if point is inside or on the edge of this triangle. + 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 + } +} diff --git a/External/Triangle.NET/Triangle/Geometry/IEdge.cs b/External/Triangle.NET/Triangle/Geometry/IEdge.cs new file mode 100644 index 0000000..a7edf5f --- /dev/null +++ b/External/Triangle.NET/Triangle/Geometry/IEdge.cs @@ -0,0 +1,29 @@ +// ----------------------------------------------------------------------- +// +// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/ +// +// ----------------------------------------------------------------------- + +namespace TriangleNet.Geometry +{ + public interface IEdge + { + /// + /// Gets the first endpoints index. + /// + int P0 { get; } + + /// + /// Gets the second endpoints index. + /// + int P1 { get; } + + /// + /// Gets or sets a general-purpose label. + /// + /// + /// This is used for the segments boundary mark. + /// + int Label { get; } + } +} diff --git a/External/Triangle.NET/Triangle/Geometry/IPolygon.cs b/External/Triangle.NET/Triangle/Geometry/IPolygon.cs new file mode 100644 index 0000000..a627b78 --- /dev/null +++ b/External/Triangle.NET/Triangle/Geometry/IPolygon.cs @@ -0,0 +1,93 @@ +// ----------------------------------------------------------------------- +// +// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/ +// +// ----------------------------------------------------------------------- + +namespace TriangleNet.Geometry +{ + using System; + using System.Collections.Generic; + + /// + /// Polygon interface. + /// + public interface IPolygon + { + /// + /// Gets the vertices of the polygon. + /// + List Points { get; } + + /// + /// Gets the segments of the polygon. + /// + List Segments { get; } + + /// + /// Gets a list of points defining the holes of the polygon. + /// + List Holes { get; } + + /// + /// Gets a list of pointers defining the regions of the polygon. + /// + List Regions { get; } + + /// + /// Gets or sets a value indicating whether the vertices have marks or not. + /// + bool HasPointMarkers { get; set; } + + /// + /// Gets or sets a value indicating whether the segments have marks or not. + /// + bool HasSegmentMarkers { get; set; } + + [Obsolete("Use polygon.Add(contour) method instead.")] + void AddContour(IEnumerable points, int marker, bool hole, bool convex); + + [Obsolete("Use polygon.Add(contour) method instead.")] + void AddContour(IEnumerable points, int marker, Point hole); + + /// + /// Compute the bounds of the polygon. + /// + /// Rectangle defining an axis-aligned bounding box. + Rectangle Bounds(); + + /// + /// Add a vertex to the polygon. + /// + /// The vertex to insert. + void Add(Vertex vertex); + + /// + /// Add a segment to the polygon. + /// + /// The segment to insert. + /// If true, both endpoints will be added to the points list. + void Add(ISegment segment, bool insert = false); + + /// + /// Add a segment to the polygon. + /// + /// The segment to insert. + /// The index of the segment endpoint to add to the points list (must be 0 or 1). + void Add(ISegment segment, int index); + + /// + /// Add a contour to the polygon. + /// + /// The contour to insert. + /// Treat contour as a hole. + void Add(Contour contour, bool hole = false); + + /// + /// Add a contour to the polygon. + /// + /// The contour to insert. + /// Point inside the contour, making it a hole. + void Add(Contour contour, Point hole); + } +} diff --git a/External/Triangle.NET/Triangle/Geometry/ISegment.cs b/External/Triangle.NET/Triangle/Geometry/ISegment.cs new file mode 100644 index 0000000..5a99983 --- /dev/null +++ b/External/Triangle.NET/Triangle/Geometry/ISegment.cs @@ -0,0 +1,26 @@ +// ----------------------------------------------------------------------- +// +// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/ +// +// ----------------------------------------------------------------------- + +namespace TriangleNet.Geometry +{ + /// + /// Interface for segment geometry. + /// + public interface ISegment : IEdge + { + /// + /// Gets the vertex at given index. + /// + /// The local index (0 or 1). + Vertex GetVertex(int index); + + /// + /// Gets an adjoining triangle. + /// + /// The triangle index (0 or 1). + ITriangle GetTriangle(int index); + } +} diff --git a/External/Triangle.NET/Triangle/Geometry/ITriangle.cs b/External/Triangle.NET/Triangle/Geometry/ITriangle.cs new file mode 100644 index 0000000..603741a --- /dev/null +++ b/External/Triangle.NET/Triangle/Geometry/ITriangle.cs @@ -0,0 +1,69 @@ +// ----------------------------------------------------------------------- +// +// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/ +// +// ----------------------------------------------------------------------- + +namespace TriangleNet.Geometry +{ + using TriangleNet.Topology; + + /// + /// Triangle interface. + /// + public interface ITriangle + { + /// + /// Gets or sets the triangle ID. + /// + int ID { get; set; } + + /// + /// Gets or sets a general-purpose label. + /// + /// + /// This is used for region information. + /// + int Label { get; set; } + + /// + /// Gets or sets the triangle area constraint. + /// + double Area { get; set; } + + /// + /// Gets the vertex at given index. + /// + /// The local index (0, 1 or 2). + /// The vertex. + Vertex GetVertex(int index); + + /// + /// Gets the ID of the vertex at given index. + /// + /// The local index (0, 1 or 2). + /// The vertex ID. + int GetVertexID(int index); + + /// + /// Gets the neighbor triangle at given index. + /// + /// The local index (0, 1 or 2). + /// The neighbor triangle. + ITriangle GetNeighbor(int index); + + /// + /// Gets the ID of the neighbor triangle at given index. + /// + /// The local index (0, 1 or 2). + /// The neighbor triangle ID. + int GetNeighborID(int index); + + /// + /// Gets the segment at given index. + /// + /// The local index (0, 1 or 2). + /// The segment. + ISegment GetSegment(int index); + } +} diff --git a/External/Triangle.NET/Triangle/Geometry/Point.cs b/External/Triangle.NET/Triangle/Geometry/Point.cs new file mode 100644 index 0000000..3c1fb95 --- /dev/null +++ b/External/Triangle.NET/Triangle/Geometry/Point.cs @@ -0,0 +1,180 @@ +// ----------------------------------------------------------------------- +// +// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/ +// +// ----------------------------------------------------------------------- + +namespace TriangleNet.Geometry +{ + using System; + using System.Diagnostics; + + /// + /// Represents a 2D point. + /// +#if USE_Z + [DebuggerDisplay("ID {ID} [{X}, {Y}, {Z}]")] +#else + [DebuggerDisplay("ID {ID} [{X}, {Y}]")] +#endif + public class Point : IComparable, IEquatable + { + 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 + + /// + /// Gets or sets the vertex id. + /// + public int ID + { + get { return this.id; } + set { this.id = value; } + } + + /// + /// Gets or sets the vertex x coordinate. + /// + public double X + { + get { return this.x; } + set { this.x = value; } + } + + /// + /// Gets or sets the vertex y coordinate. + /// + public double Y + { + get { return this.y; } + set { this.y = value; } + } + +#if USE_Z + /// + /// Gets or sets the vertex z coordinate. + /// + public double Z + { + get { return this.z; } + set { this.z = value; } + } +#endif + + /// + /// Gets or sets a general-purpose label. + /// + /// + /// This is used for the vertex boundary mark. + /// + 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; + } + } +} diff --git a/External/Triangle.NET/Triangle/Geometry/Polygon.cs b/External/Triangle.NET/Triangle/Geometry/Polygon.cs new file mode 100644 index 0000000..7d10e7a --- /dev/null +++ b/External/Triangle.NET/Triangle/Geometry/Polygon.cs @@ -0,0 +1,184 @@ +// ----------------------------------------------------------------------- +// +// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/ +// +// ----------------------------------------------------------------------- + +namespace TriangleNet.Geometry +{ + using System; + using System.Linq; + using System.Collections.Generic; + + /// + /// A polygon represented as a planar straight line graph. + /// + public class Polygon : IPolygon + { + List points; + List holes; + List regions; + + List segments; + + /// + public List Points + { + get { return points; } + } + + /// + public List Holes + { + get { return holes; } + } + + /// + public List Regions + { + get { return regions; } + } + + /// + public List Segments + { + get { return segments; } + } + + /// + public bool HasPointMarkers { get; set; } + + /// + public bool HasSegmentMarkers { get; set; } + + /// + public int Count + { + get { return points.Count; } + } + + /// + /// Initializes a new instance of the class. + /// + public Polygon() + : this(3, false) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The default capacity for the points list. + public Polygon(int capacity) + : this(3, false) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The default capacity for the points list. + /// Use point and segment markers. + public Polygon(int capacity, bool markers) + { + points = new List(capacity); + holes = new List(); + regions = new List(); + + segments = new List(); + + HasPointMarkers = markers; + HasSegmentMarkers = markers; + } + + [Obsolete("Use polygon.Add(contour) method instead.")] + public void AddContour(IEnumerable 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 points, int marker, Point hole) + { + this.Add(new Contour(points, marker), hole); + } + + /// + public Rectangle Bounds() + { + var bounds = new Rectangle(); + bounds.Expand(this.points.Cast()); + + return bounds; + } + + /// + /// Add a vertex to the polygon. + /// + /// The vertex to insert. + public void Add(Vertex vertex) + { + this.points.Add(vertex); + } + + /// + /// Add a segment to the polygon. + /// + /// The segment to insert. + /// If true, both endpoints will be added to the points list. + 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)); + } + } + + /// + /// Add a segment to the polygon. + /// + /// The segment to insert. + /// The index of the segment endpoint to add to the points list (must be 0 or 1). + public void Add(ISegment segment, int index) + { + this.segments.Add(segment); + + this.points.Add(segment.GetVertex(index)); + } + + /// + /// Add a contour to the polygon. + /// + /// The contour to insert. + /// Treat contour as a hole. + 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()); + } + } + + /// + /// Add a contour to the polygon. + /// + /// The contour to insert. + /// Point inside the contour, making it a hole. + public void Add(Contour contour, Point hole) + { + this.points.AddRange(contour.Points); + this.segments.AddRange(contour.GetSegments()); + + this.holes.Add(hole); + } + } +} diff --git a/External/Triangle.NET/Triangle/Geometry/Rectangle.cs b/External/Triangle.NET/Triangle/Geometry/Rectangle.cs new file mode 100644 index 0000000..44ea007 --- /dev/null +++ b/External/Triangle.NET/Triangle/Geometry/Rectangle.cs @@ -0,0 +1,189 @@ +// ----------------------------------------------------------------------- +// +// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/ +// +// ----------------------------------------------------------------------- + +namespace TriangleNet.Geometry +{ + using System; + using System.Collections.Generic; + + /// + /// A simple rectangle class. + /// + public class Rectangle + { + double xmin, ymin, xmax, ymax; + + /// + /// Initializes a new instance of the class. + /// + 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) + { + } + + /// + /// Initializes a new instance of the class + /// with predefined bounds. + /// + /// Minimum x value (left). + /// Minimum y value (bottom). + /// Width of the rectangle. + /// Height of the rectangle. + public Rectangle(double x, double y, double width, double height) + { + this.xmin = x; + this.ymin = y; + this.xmax = x + width; + this.ymax = y + height; + } + + /// + /// Gets the minimum x value (left boundary). + /// + public double Left + { + get { return xmin; } + } + + /// + /// Gets the maximum x value (right boundary). + /// + public double Right + { + get { return xmax; } + } + + /// + /// Gets the minimum y value (bottom boundary). + /// + public double Bottom + { + get { return ymin; } + } + + /// + /// Gets the maximum y value (top boundary). + /// + public double Top + { + get { return ymax; } + } + + /// + /// Gets the width of the rectangle. + /// + public double Width + { + get { return xmax - xmin; } + } + + /// + /// Gets the height of the rectangle. + /// + public double Height + { + get { return ymax - ymin; } + } + + /// + /// Update bounds. + /// + /// Add dx to left and right bounds. + /// Add dy to top and bottom bounds. + public void Resize(double dx, double dy) + { + xmin -= dx; + xmax += dx; + ymin -= dy; + ymax += dy; + } + + /// + /// Expand rectangle to include given point. + /// + /// Point. + 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); + } + + /// + /// Expand rectangle to include a list of points. + /// + public void Expand(IEnumerable points) + { + foreach (var p in points) + { + Expand(p); + } + } + + /// + /// Expand rectangle to include given rectangle. + /// + /// X coordinate. + /// Y coordinate. + 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); + } + + /// + /// Check if given point is inside rectangle. + /// + /// Point to check. + /// Point to check. + /// Return true, if rectangle contains given point. + public bool Contains(double x, double y) + { + return ((x >= xmin) && (x <= xmax) && (y >= ymin) && (y <= ymax)); + } + + /// + /// Check if given point is inside rectangle. + /// + /// Point to check. + /// Return true, if rectangle contains given point. + public bool Contains(Point pt) + { + return Contains(pt.x, pt.y); + } + + /// + /// Check if this rectangle contains other rectangle. + /// + /// Rectangle to check. + /// Return true, if this rectangle contains given rectangle. + public bool Contains(Rectangle other) + { + return (xmin <= other.Left && other.Right <= xmax + && ymin <= other.Bottom && other.Top <= ymax); + } + + /// + /// Check if this rectangle intersects other rectangle. + /// + /// Rectangle to check. + /// Return true, if given rectangle intersects this rectangle. + public bool Intersects(Rectangle other) + { + return (other.Left < xmax && xmin < other.Right + && other.Bottom < ymax && ymin < other.Top); + } + } +} diff --git a/External/Triangle.NET/Triangle/Geometry/RegionPointer.cs b/External/Triangle.NET/Triangle/Geometry/RegionPointer.cs new file mode 100644 index 0000000..2db73a3 --- /dev/null +++ b/External/Triangle.NET/Triangle/Geometry/RegionPointer.cs @@ -0,0 +1,63 @@ +// ----------------------------------------------------------------------- +// +// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/ +// +// ----------------------------------------------------------------------- + +namespace TriangleNet.Geometry +{ + using System; + using System.Collections.Generic; + + /// + /// Pointer to a region in the mesh geometry. A region is a well-defined + /// subset of the geomerty (enclosed by subsegments). + /// + public class RegionPointer + { + internal Point point; + internal int id; + internal double area; + + /// + /// Gets or sets a region area constraint. + /// + public double Area + { + get { return area; } + set + { + if (value < 0.0) + { + throw new ArgumentException("Area constraints must not be negative."); + } + area = value; + } + } + + /// + /// Initializes a new instance of the class. + /// + /// X coordinate of the region. + /// Y coordinate of the region. + /// Region id. + public RegionPointer(double x, double y, int id) + : this(x, y, id, 0.0) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// X coordinate of the region. + /// Y coordinate of the region. + /// Region id. + /// Area constraint. + public RegionPointer(double x, double y, int id, double area) + { + this.point = new Point(x, y); + this.id = id; + this.area = area; + } + } +} diff --git a/External/Triangle.NET/Triangle/Geometry/Segment.cs b/External/Triangle.NET/Triangle/Geometry/Segment.cs new file mode 100644 index 0000000..c521709 --- /dev/null +++ b/External/Triangle.NET/Triangle/Geometry/Segment.cs @@ -0,0 +1,92 @@ +// ----------------------------------------------------------------------- +// +// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/ +// +// ----------------------------------------------------------------------- + +namespace TriangleNet.Geometry +{ + using System; + + /// + /// Represents a straight line segment in 2D space. + /// + public class Segment : ISegment + { + Vertex v0; + Vertex v1; + + int label; + + /// + /// Gets or sets the segments boundary mark. + /// + public int Label + { + get { return label; } + set { label = value; } + } + /// + /// Gets the first endpoints index. + /// + public int P0 + { + get { return v0.id; } + } + + /// + /// Gets the second endpoints index. + /// + public int P1 + { + get { return v1.id; } + } + + /// + /// Initializes a new instance of the class. + /// + public Segment(Vertex v0, Vertex v1) + : this (v0, v1, 0) + { + } + + /// + /// Initializes a new instance of the class. + /// + public Segment(Vertex v0, Vertex v1, int label) + { + this.v0 = v0; + this.v1 = v1; + + this.label = label; + } + + /// + /// Gets the specified segment endpoint. + /// + /// The endpoint index (0 or 1). + /// + public Vertex GetVertex(int index) + { + if (index == 0) + { + return v0; + } + + if (index == 1) + { + return v1; + } + + throw new IndexOutOfRangeException(); + } + + /// + /// WARNING: not implemented. + /// + public ITriangle GetTriangle(int index) + { + throw new NotImplementedException(); + } + } +} diff --git a/External/Triangle.NET/Triangle/Geometry/Vertex.cs b/External/Triangle.NET/Triangle/Geometry/Vertex.cs new file mode 100644 index 0000000..49f1b58 --- /dev/null +++ b/External/Triangle.NET/Triangle/Geometry/Vertex.cs @@ -0,0 +1,125 @@ +// ----------------------------------------------------------------------- +// +// 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/ +// +// ----------------------------------------------------------------------- + +namespace TriangleNet.Geometry +{ + using System; + using TriangleNet.Topology; + + /// + /// The vertex data structure. + /// + 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; + + /// + /// Initializes a new instance of the class. + /// + public Vertex() + : this(0, 0, 0) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The x coordinate of the vertex. + /// The y coordinate of the vertex. + public Vertex(double x, double y) + : this(x, y, 0) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The x coordinate of the vertex. + /// The y coordinate of the vertex. + /// The boundary mark. + public Vertex(double x, double y, int mark) + : base(x, y, mark) + { + this.type = VertexType.InputVertex; + } + +#if USE_ATTRIBS + /// + /// Initializes a new instance of the class. + /// + /// The x coordinate of the vertex. + /// The y coordinate of the vertex. + /// The boundary mark. + /// The number of point attributes. + 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 + /// + /// Gets the vertex attributes (may be null). + /// + public double[] Attributes + { + get { return this.attributes; } + } +#endif + + /// + /// Gets the vertex type. + /// + public VertexType Type + { + get { return this.type; } + } + + /// + /// Gets the specified coordinate of the vertex. + /// + /// Coordinate index. + /// X coordinate, if index is 0, Y coordinate, if index is 1. + 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; + } + } +} diff --git a/External/Triangle.NET/Triangle/IO/DebugWriter.cs b/External/Triangle.NET/Triangle/IO/DebugWriter.cs new file mode 100644 index 0000000..717c5ad --- /dev/null +++ b/External/Triangle.NET/Triangle/IO/DebugWriter.cs @@ -0,0 +1,263 @@ +// ----------------------------------------------------------------------- +// +// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/ +// +// ----------------------------------------------------------------------- + +namespace TriangleNet.IO +{ + using System; + using System.Globalization; + using System.IO; + using System.IO.Compression; + using System.Text; + using TriangleNet.Topology; + using TriangleNet.Geometry; + + /// + /// Writes a the current mesh into a text file. + /// + /// + /// 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 + /// + 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 + + /// + /// Start a new session with given name. + /// + /// Name of the session (and output files). + 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); + } + + /// + /// Write complete mesh to file. + /// + public void Write(Mesh mesh, bool skip = false) + { + this.WriteMesh(mesh, skip); + + this.triangles = mesh.Triangles.Count; + } + + /// + /// Finish this session. + /// + 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; + } + } + } +} diff --git a/External/Triangle.NET/Triangle/IO/FileProcessor.cs b/External/Triangle.NET/Triangle/IO/FileProcessor.cs new file mode 100644 index 0000000..d2f1b70 --- /dev/null +++ b/External/Triangle.NET/Triangle/IO/FileProcessor.cs @@ -0,0 +1,126 @@ +// ----------------------------------------------------------------------- +// +// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/ +// +// ----------------------------------------------------------------------- + +namespace TriangleNet.IO +{ + using System; + using System.Collections.Generic; + using TriangleNet.Geometry; + using TriangleNet.Meshing; + + public static class FileProcessor + { + static List formats; + + static FileProcessor() + { + formats = new List(); + + // 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 + + /// + /// Read a file containing polygon geometry. + /// + /// The path of the file to read. + /// An instance of the class. + 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."); + } + + /// + /// Save a polygon geometry to disk. + /// + /// An instance of the class. + /// The path of the file to save. + 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 + + /// + /// Read a file containing a mesh. + /// + /// The path of the file to read. + /// An instance of the interface. + 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."); + } + + /// + /// Save a mesh to disk. + /// + /// An instance of the interface. + /// The path of the file to save. + 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 + } +} diff --git a/External/Triangle.NET/Triangle/IO/IFileFormat.cs b/External/Triangle.NET/Triangle/IO/IFileFormat.cs new file mode 100644 index 0000000..cd61998 --- /dev/null +++ b/External/Triangle.NET/Triangle/IO/IFileFormat.cs @@ -0,0 +1,13 @@ +// ----------------------------------------------------------------------- +// +// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/ +// +// ----------------------------------------------------------------------- + +namespace TriangleNet.IO +{ + public interface IFileFormat + { + bool IsSupported(string file); + } +} diff --git a/External/Triangle.NET/Triangle/IO/IMeshFormat.cs b/External/Triangle.NET/Triangle/IO/IMeshFormat.cs new file mode 100644 index 0000000..99a2a13 --- /dev/null +++ b/External/Triangle.NET/Triangle/IO/IMeshFormat.cs @@ -0,0 +1,38 @@ +// ----------------------------------------------------------------------- +// +// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/ +// +// ----------------------------------------------------------------------- + +namespace TriangleNet.IO +{ + using System.IO; + using TriangleNet.Meshing; + + /// + /// Interface for mesh I/O. + /// + public interface IMeshFormat : IFileFormat + { + /// + /// Read a file containing a mesh. + /// + /// The path of the file to read. + /// An instance of the interface. + IMesh Import(string filename); + + /// + /// Save a mesh to disk. + /// + /// An instance of the interface. + /// The path of the file to save. + void Write(IMesh mesh, string filename); + + /// + /// Save a mesh to a . + /// + /// An instance of the interface. + /// The stream to save to. + void Write(IMesh mesh, Stream stream); + } +} diff --git a/External/Triangle.NET/Triangle/IO/IPolygonFormat.cs b/External/Triangle.NET/Triangle/IO/IPolygonFormat.cs new file mode 100644 index 0000000..7394243 --- /dev/null +++ b/External/Triangle.NET/Triangle/IO/IPolygonFormat.cs @@ -0,0 +1,38 @@ +// ----------------------------------------------------------------------- +// +// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/ +// +// ----------------------------------------------------------------------- + +namespace TriangleNet.IO +{ + using System.IO; + using TriangleNet.Geometry; + + /// + /// Interface for geometry input. + /// + public interface IPolygonFormat : IFileFormat + { + /// + /// Read a file containing polygon geometry. + /// + /// The path of the file to read. + /// An instance of the class. + IPolygon Read(string filename); + + /// + /// Save a polygon geometry to disk. + /// + /// An instance of the class. + /// The path of the file to save. + void Write(IPolygon polygon, string filename); + + /// + /// Save a polygon geometry to a . + /// + /// An instance of the class. + /// The stream to save to. + void Write(IPolygon polygon, Stream stream); + } +} diff --git a/External/Triangle.NET/Triangle/IO/InputTriangle.cs b/External/Triangle.NET/Triangle/IO/InputTriangle.cs new file mode 100644 index 0000000..5f46353 --- /dev/null +++ b/External/Triangle.NET/Triangle/IO/InputTriangle.cs @@ -0,0 +1,85 @@ +// ----------------------------------------------------------------------- +// +// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/ +// +// ----------------------------------------------------------------------- + +namespace TriangleNet.IO +{ + using TriangleNet.Topology; + using TriangleNet.Geometry; + + /// + /// Simple triangle class for input. + /// + 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 + + /// + /// Gets the triangle id. + /// + public int ID + { + get { return 0; } + set { } + } + + /// + /// Region ID the triangle belongs to. + /// + public int Label + { + get { return label; } + set { label = value; } + } + + /// + /// Gets the triangle area constraint. + /// + public double Area + { + get { return area; } + set { area = value; } + } + + /// + /// Gets the specified corners vertex. + /// + 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 + } +} diff --git a/External/Triangle.NET/Triangle/IO/TriangleFormat.cs b/External/Triangle.NET/Triangle/IO/TriangleFormat.cs new file mode 100644 index 0000000..9fd7965 --- /dev/null +++ b/External/Triangle.NET/Triangle/IO/TriangleFormat.cs @@ -0,0 +1,92 @@ +// ----------------------------------------------------------------------- +// +// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/ +// +// ----------------------------------------------------------------------- + +namespace TriangleNet.IO +{ + using System; + using System.Collections.Generic; + using System.IO; + using TriangleNet.Geometry; + using TriangleNet.Meshing; + + /// + /// Implements geometry and mesh file formats of the the original Triangle code. + /// + 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 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(); + } + } +} diff --git a/External/Triangle.NET/Triangle/IO/TriangleReader.cs b/External/Triangle.NET/Triangle/IO/TriangleReader.cs new file mode 100644 index 0000000..6d15213 --- /dev/null +++ b/External/Triangle.NET/Triangle/IO/TriangleReader.cs @@ -0,0 +1,756 @@ +// ----------------------------------------------------------------------- +// +// 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/ +// +// ----------------------------------------------------------------------- + +namespace TriangleNet.IO +{ + using System; + using System.Collections.Generic; + using System.Globalization; + using System.IO; + using TriangleNet.Geometry; + + /// + /// Helper methods for reading Triangle file formats. + /// + 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; + } + + /// + /// Read vertex information of the given line. + /// + /// The input geometry. + /// The current vertex index. + /// The current line. + /// Number of point attributes + /// Number of point markers (0 or 1) + private void ReadVertex(List 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 + + /// + /// Reads geometry information from .node or .poly files. + /// + 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); + } + } + + /// + /// Reads a mesh from .node, .poly or .ele files. + /// + public void Read(string filename, out Polygon geometry, out List triangles) + { + triangles = null; + + Read(filename, out geometry); + + string path = Path.ChangeExtension(filename, ".ele"); + + if (File.Exists(path) && geometry != null) + { + triangles = ReadEleFile(path); + } + } + + /// + /// Reads geometry information from .node or .poly files. + /// + public IPolygon Read(string filename) + { + Polygon geometry = null; + + Read(filename, out geometry); + + return geometry; + } + + #endregion + + /// + /// Read the vertices from a file, which may be a .node or .poly file. + /// + /// + /// Will NOT read associated .ele by default. + public Polygon ReadNodeFile(string nodefilename) + { + return ReadNodeFile(nodefilename, false); + } + + /// + /// Read the vertices from a file, which may be a .node or .poly file. + /// + /// + /// + 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; + } + + /// + /// Read the vertices and segments from a .poly file. + /// + /// + /// Will NOT read associated .ele by default. + public Polygon ReadPolyFile(string polyfilename) + { + return ReadPolyFile(polyfilename, false, false); + } + + /// + /// Read the vertices and segments from a .poly file. + /// + /// + /// If true, look for an associated .ele file. + /// Will NOT read associated .area by default. + public Polygon ReadPolyFile(string polyfilename, bool readElements) + { + return ReadPolyFile(polyfilename, readElements, false); + } + + /// + /// Read the vertices and segments from a .poly file. + /// + /// + /// If true, look for an associated .ele file. + /// If true, look for an associated .area file. + 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; + } + + /// + /// Read elements from an .ele file. + /// + /// The file name. + /// A list of triangles. + public List ReadEleFile(string elefilename) + { + return ReadEleFile(elefilename, false); + } + + /// + /// Read the elements from an .ele file. + /// + /// + /// + /// + private List ReadEleFile(string elefilename, bool readArea) + { + int intriangles = 0, attributes = 0; + + List 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(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; + } + + /// + /// Read the area constraints from an .area file. + /// + /// + /// + /// + 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; + } + + /// + /// Read an .edge file. + /// + /// The file name. + /// The number of input vertices (read from a .node or .poly file). + /// A List of edges. + public List ReadEdgeFile(string edgeFile, int invertices) + { + // Read poly file + List 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(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; + } + } +} diff --git a/External/Triangle.NET/Triangle/IO/TriangleWriter.cs b/External/Triangle.NET/Triangle/IO/TriangleWriter.cs new file mode 100644 index 0000000..c0a0d3a --- /dev/null +++ b/External/Triangle.NET/Triangle/IO/TriangleWriter.cs @@ -0,0 +1,459 @@ +// ----------------------------------------------------------------------- +// +// 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/ +// +// ----------------------------------------------------------------------- + +namespace TriangleNet.IO +{ + using System.Collections.Generic; + using System.Globalization; + using System.IO; + using TriangleNet.Geometry; + using TriangleNet.Topology; + + /// + /// Helper methods for writing Triangle file formats. + /// + public class TriangleWriter + { + static NumberFormatInfo nfi = NumberFormatInfo.InvariantInfo; + + /// + /// Number the vertices and write them to a .node file. + /// + /// + /// + public void Write(Mesh mesh, string filename) + { + WritePoly(mesh, Path.ChangeExtension(filename, ".poly")); + WriteElements(mesh, Path.ChangeExtension(filename, ".ele")); + } + + /// + /// Number the vertices and write them to a .node file. + /// + /// + /// + public void WriteNodes(Mesh mesh, string filename) + { + using (var writer = new StreamWriter(filename)) + { + WriteNodes(writer, mesh); + } + } + + /// + /// Number the vertices and write them to a .node file. + /// + 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); + } + } + } + + /// + /// Write the vertices to a stream. + /// + /// + /// + private void WriteNodes(StreamWriter writer, IEnumerable 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++; + } + } + } + + /// + /// Write the triangles to an .ele file. + /// + /// + /// + 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++; + } + } + } + + /// + /// Write the segments and holes to a .poly file. + /// + /// Data source. + /// File name. + /// Write nodes into this file. + /// 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. + 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++; + } + } + } + } + + /// + /// Write the segments and holes to a .poly file. + /// + /// + /// + public void WritePoly(Mesh mesh, string filename) + { + WritePoly(mesh, filename, true); + } + + /// + /// Write the segments and holes to a .poly file. + /// + /// Data source. + /// File name. + /// Write nodes into this file. + /// 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. + 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++; + } + } + } + } + + /// + /// Write the edges to an .edge file. + /// + /// + /// + 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++; + } + } + } + } + } + + /// + /// Write the triangle neighbors to a .neigh file. + /// + /// + /// + /// WARNING: Be sure WriteElements has been called before, + /// so the elements are numbered right! + 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); + } + } + } + } +} diff --git a/External/Triangle.NET/Triangle/IPredicates.cs b/External/Triangle.NET/Triangle/IPredicates.cs new file mode 100644 index 0000000..895690d --- /dev/null +++ b/External/Triangle.NET/Triangle/IPredicates.cs @@ -0,0 +1,22 @@ +// ----------------------------------------------------------------------- +// +// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/ +// +// ----------------------------------------------------------------------- + +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); + } +} diff --git a/External/Triangle.NET/Triangle/Log.cs b/External/Triangle.NET/Triangle/Log.cs new file mode 100644 index 0000000..b5e577b --- /dev/null +++ b/External/Triangle.NET/Triangle/Log.cs @@ -0,0 +1,84 @@ +// ----------------------------------------------------------------------- +// +// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/ +// +// ----------------------------------------------------------------------- + +namespace TriangleNet +{ + using System.Collections.Generic; + using TriangleNet.Logging; + + /// + /// A simple logger, which logs messages to a List. + /// + /// Using singleton pattern as proposed by Jon Skeet. + /// http://csharpindepth.com/Articles/General/Singleton.aspx + /// + public sealed class Log : ILog + { + /// + /// Log detailed information. + /// + public static bool Verbose { get; set; } + + private List log = new List(); + + 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 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 Data + { + get { return log; } + } + + public LogLevel Level + { + get { return level; } + } + } +} diff --git a/External/Triangle.NET/Triangle/Logging/ILog.cs b/External/Triangle.NET/Triangle/Logging/ILog.cs new file mode 100644 index 0000000..f0184a9 --- /dev/null +++ b/External/Triangle.NET/Triangle/Logging/ILog.cs @@ -0,0 +1,34 @@ +// ----------------------------------------------------------------------- +// +// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/ +// +// ----------------------------------------------------------------------- + +namespace TriangleNet.Logging +{ + using System.Collections.Generic; + + public enum LogLevel + { + Info = 0, + Warning = 1, + Error = 2 + } + + /// + /// A basic log interface. + /// + public interface ILog 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 Data { get; } + + LogLevel Level { get; } + } +} diff --git a/External/Triangle.NET/Triangle/Logging/ILogItem.cs b/External/Triangle.NET/Triangle/Logging/ILogItem.cs new file mode 100644 index 0000000..7ed0761 --- /dev/null +++ b/External/Triangle.NET/Triangle/Logging/ILogItem.cs @@ -0,0 +1,21 @@ +// ----------------------------------------------------------------------- +// +// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/ +// +// ----------------------------------------------------------------------- + +namespace TriangleNet.Logging +{ + using System; + + /// + /// A basic log item interface. + /// + public interface ILogItem + { + DateTime Time { get; } + LogLevel Level { get; } + string Message { get; } + string Info { get; } + } +} diff --git a/External/Triangle.NET/Triangle/Logging/LogItem.cs b/External/Triangle.NET/Triangle/Logging/LogItem.cs new file mode 100644 index 0000000..1be74bc --- /dev/null +++ b/External/Triangle.NET/Triangle/Logging/LogItem.cs @@ -0,0 +1,53 @@ +// ----------------------------------------------------------------------- +// +// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/ +// +// ----------------------------------------------------------------------- + +namespace TriangleNet.Logging +{ + using System; + + /// + /// Represents an item stored in the log. + /// + 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; + } + } +} diff --git a/External/Triangle.NET/Triangle/Mesh.cs b/External/Triangle.NET/Triangle/Mesh.cs new file mode 100644 index 0000000..ad2deb2 --- /dev/null +++ b/External/Triangle.NET/Triangle/Mesh.cs @@ -0,0 +1,1768 @@ +// ----------------------------------------------------------------------- +// +// 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/ +// +// ----------------------------------------------------------------------- + +namespace TriangleNet +{ + using System; + using System.Collections.Generic; + using TriangleNet.Geometry; + using TriangleNet.Logging; + using TriangleNet.Meshing; + using TriangleNet.Meshing.Data; + using TriangleNet.Meshing.Iterators; + using TriangleNet.Tools; + using TriangleNet.Topology; + + /// + /// Mesh data structure. + /// + public class Mesh : IMesh + { + #region Variables + + IPredicates predicates; + + ILog logger; + + QualityMesher qualityMesher; + + // Stack that maintains a list of recently flipped triangles. + Stack flipstack; + + // TODO: Check if custom hashmap implementation could be faster. + + // Using hashsets for memory management should quite fast. + internal TrianglePool triangles; + internal Dictionary subsegs; + internal Dictionary vertices; + + // Hash seeds (should belong to mesh instance) + internal int hash_vtx = 0; + internal int hash_seg = 0; + internal int hash_tri = 0; + + internal List holes; + internal List regions; + + // TODO: remove mesh_dim, invertices and insegments + + // Other variables. + internal Rectangle bounds; // x and y bounds. + internal int invertices; // Number of input vertices. + internal int insegments; // Number of input segments. + internal int undeads; // Number of input vertices that don't appear in the mesh. + internal int mesh_dim; // Dimension (ought to be 2). + internal int nextras; // Number of attributes per vertex. + //internal int eextras; // Number of attributes per triangle. + internal int hullsize; // Number of edges in convex hull. + internal int steinerleft; // Number of Steiner points not yet used. + internal bool checksegments; // Are there segments in the triangulation yet? + internal bool checkquality; // Has quality triangulation begun yet? + + // Triangular bounding box vertices. + internal Vertex infvertex1, infvertex2, infvertex3; + + internal TriangleLocator locator; + + // Controls the behavior of the mesh instance. + internal Behavior behavior; + + // The current node numbering + internal NodeNumbering numbering; + + #endregion + + #region Public properties + + /// + /// Gets the mesh bounding box. + /// + public Rectangle Bounds + { + get { return this.bounds; } + } + + /// + /// Gets the mesh vertices. + /// + public ICollection Vertices + { + get { return this.vertices.Values; } + } + + /// + /// Gets the mesh holes. + /// + public IList Holes + { + get { return this.holes; } + } + + /// + /// Gets the mesh triangles. + /// + public ICollection Triangles + { + get { return this.triangles; } + } + + /// + /// Gets the mesh segments. + /// + public ICollection Segments + { + get { return this.subsegs.Values; } + } + + /// + /// Gets the mesh edges. + /// + public IEnumerable Edges + { + get + { + var e = new EdgeIterator(this); + while (e.MoveNext()) + { + yield return e.Current; + } + } + } + + /// + /// Gets the number of input vertices. + /// + public int NumberOfInputPoints + { + get { return invertices; } + } + + /// + /// Gets the number of mesh edges. + /// + public int NumberOfEdges + { + get { return (3 * triangles.Count + hullsize) / 2; } + } + + /// + /// Indicates whether the input is a PSLG or a point set. + /// + public bool IsPolygon + { + get { return this.insegments > 0; } + } + + /// + /// Gets the current node numbering. + /// + public NodeNumbering CurrentNumbering + { + get { return numbering; } + } + + #endregion + + #region "Outer space" variables + + internal const int DUMMY = -1; + + // The triangle that fills "outer space," called 'dummytri', is pointed to + // by every triangle and subsegment on a boundary (be it outer or inner) of + // the triangulation. Also, 'dummytri' points to one of the triangles on + // the convex hull (until the holes and concavities are carved), making it + // possible to find a starting triangle for point location. + + // 'dummytri' and 'dummysub' are generally required to fulfill only a few + // invariants: their vertices must remain NULL and 'dummytri' must always + // be bonded (at offset zero) to some triangle on the convex hull of the + // mesh, via a boundary edge. Otherwise, the connections of 'dummytri' and + // 'dummysub' may change willy-nilly. This makes it possible to avoid + // writing a good deal of special-case code (in the edge flip, for example) + // for dealing with the boundary of the mesh, places where no subsegment is + // present, and so forth. Other entities are frequently bonded to + // 'dummytri' and 'dummysub' as if they were real mesh entities, with no + // harm done. + + internal Triangle dummytri; + + // Set up 'dummysub', the omnipresent subsegment pointed to by any + // triangle side or subsegment end that isn't attached to a real + // subsegment. + + internal SubSegment dummysub; + + private void Initialize() + { + dummysub = new SubSegment(); + dummysub.hash = DUMMY; + + // Initialize the two adjoining subsegments to be the omnipresent + // subsegment. These will eventually be changed by various bonding + // operations, but their values don't really matter, as long as they + // can legally be dereferenced. + dummysub.subsegs[0].seg = dummysub; + dummysub.subsegs[1].seg = dummysub; + + // Set up 'dummytri', the 'triangle' that occupies "outer space." + dummytri = new Triangle(); + dummytri.hash = dummytri.id = DUMMY; + + // Initialize the three adjoining triangles to be "outer space." These + // will eventually be changed by various bonding operations, but their + // values don't really matter, as long as they can legally be + // dereferenced. + dummytri.neighbors[0].tri = dummytri; + dummytri.neighbors[1].tri = dummytri; + dummytri.neighbors[2].tri = dummytri; + + // Initialize the three adjoining subsegments of 'dummytri' to be + // the omnipresent subsegment. + dummytri.subsegs[0].seg = dummysub; + dummytri.subsegs[1].seg = dummysub; + dummytri.subsegs[2].seg = dummysub; + } + + #endregion + + /// + /// Initializes a new instance of the class. + /// + public Mesh(Configuration config) + { + Initialize(); + + logger = Log.Instance; + + behavior = new Behavior(); + + vertices = new Dictionary(); + subsegs = new Dictionary(); + + triangles = config.TrianglePool(); + + flipstack = new Stack(); + + holes = new List(); + regions = new List(); + + steinerleft = -1; + + this.predicates = config.Predicates(); + + this.locator = new TriangleLocator(this, predicates); + } + + public void Refine(QualityOptions quality, bool delaunay = false) + { + invertices = vertices.Count; + + if (behavior.Poly) + { + insegments = behavior.useSegments ? subsegs.Count : hullsize; + } + + Reset(); + + if (qualityMesher == null) + { + qualityMesher = new QualityMesher(this, new Configuration()); + } + + // Enforce angle and area constraints. + qualityMesher.Apply(quality, delaunay); + } + + /// + /// Renumber vertex and triangle id's. + /// + public void Renumber() + { + this.Renumber(NodeNumbering.Linear); + } + + /// + /// Renumber vertex and triangle id's. + /// + public void Renumber(NodeNumbering num) + { + // Don't need to do anything if the nodes are already numbered. + if (num == this.numbering) + { + return; + } + + int id; + + if (num == NodeNumbering.Linear) + { + id = 0; + foreach (var node in this.vertices.Values) + { + node.id = id++; + } + } + else if (num == NodeNumbering.CuthillMcKee) + { + var rcm = new CuthillMcKee(); + var iperm = rcm.Renumber(this); + + // Permute the node indices. + foreach (var node in this.vertices.Values) + { + node.id = iperm[node.id]; + } + } + + // Remember the current numbering. + numbering = num; + + // Triangles will always be numbered from 0 to n-1 + id = 0; + foreach (var item in this.triangles) + { + item.id = id++; + } + } + + #region Misc + + /// + /// Set QualityMesher for mesh refinement. + /// + /// + internal void SetQualityMesher(QualityMesher qmesher) + { + qualityMesher = qmesher; + } + + internal void CopyTo(Mesh target) + { + target.vertices = this.vertices; + target.triangles = this.triangles; + target.subsegs = this.subsegs; + + target.holes = this.holes; + target.regions = this.regions; + + target.hash_vtx = this.hash_vtx; + target.hash_seg = this.hash_seg; + target.hash_tri = this.hash_tri; + + target.numbering = this.numbering; + target.hullsize = this.hullsize; + } + + /// + /// Reset all the mesh data. This method will also wipe + /// out all mesh data. + /// + private void ResetData() + { + vertices.Clear(); + triangles.Restart(); + subsegs.Clear(); + + holes.Clear(); + regions.Clear(); + + this.hash_vtx = 0; + this.hash_seg = 0; + this.hash_tri = 0; + + flipstack.Clear(); + + hullsize = 0; + + Reset(); + + locator.Reset(); + } + + /// + /// Reset the mesh triangulation state. + /// + private void Reset() + { + numbering = NodeNumbering.None; + + undeads = 0; // No eliminated input vertices yet. + checksegments = false; // There are no segments in the triangulation yet. + checkquality = false; // The quality triangulation stage has not begun. + + Statistic.InCircleCount = 0; + Statistic.CounterClockwiseCount = 0; + Statistic.InCircleAdaptCount = 0; + Statistic.CounterClockwiseAdaptCount = 0; + Statistic.Orient3dCount = 0; + Statistic.HyperbolaCount = 0; + Statistic.CircleTopCount = 0; + Statistic.CircumcenterCount = 0; + } + + /// + /// Read the vertices from memory. + /// + /// The input data. + internal void TransferNodes(IList points) + { + this.invertices = points.Count; + this.mesh_dim = 2; + this.bounds = new Rectangle(); + + if (this.invertices < 3) + { + logger.Error("Input must have at least three input vertices.", "Mesh.TransferNodes()"); + throw new Exception("Input must have at least three input vertices."); + } + + var v = points[0]; + +#if USE_ATTRIBS + // Check attributes. + this.nextras = v.attributes == null ? 0 : v.attributes.Length; +#endif + + // Simple heuristic to check if ids are already set. We assume that if the + // first two vertex ids are distinct, then all input vertices have pairwise + // distinct ids. + bool userId = (v.id != points[1].id); + + foreach (var p in points) + { + if (userId) + { + p.hash = p.id; + + // Make sure the hash counter gets updated. + hash_vtx = Math.Max(p.hash + 1, hash_vtx); + } + else + { + p.hash = p.id = hash_vtx++; + } + + this.vertices.Add(p.hash, p); + this.bounds.Expand(p); + } + } + + /// + /// Construct a mapping from vertices to triangles to improve the speed of + /// point location for segment insertion. + /// + /// + /// Traverses all the triangles, and provides each corner of each triangle + /// with a pointer to that triangle. Of course, pointers will be overwritten + /// by other pointers because (almost) each vertex is a corner of several + /// triangles, but in the end every vertex will point to some triangle + /// that contains it. + /// + internal void MakeVertexMap() + { + Otri tri = default(Otri); + Vertex triorg; + + foreach (var t in this.triangles) + { + tri.tri = t; + // Check all three vertices of the triangle. + for (tri.orient = 0; tri.orient < 3; tri.orient++) + { + triorg = tri.Org(); + triorg.tri = tri; + } + } + } + + #endregion + + #region Factory + + /// + /// Create a new triangle with orientation zero. + /// + /// Reference to the new triangle. + internal void MakeTriangle(ref Otri newotri) + { + Triangle tri = triangles.Get(); + + //tri.id = tri.hash; + + tri.subsegs[0].seg = dummysub; + tri.subsegs[1].seg = dummysub; + tri.subsegs[2].seg = dummysub; + + tri.neighbors[0].tri = dummytri; + tri.neighbors[1].tri = dummytri; + tri.neighbors[2].tri = dummytri; + + newotri.tri = tri; + newotri.orient = 0; + } + + /// + /// Create a new subsegment with orientation zero. + /// + /// Reference to the new subseg. + internal void MakeSegment(ref Osub newsubseg) + { + var seg = new SubSegment(); + + seg.hash = this.hash_seg++; + + seg.subsegs[0].seg = dummysub; + seg.subsegs[1].seg = dummysub; + + seg.triangles[0].tri = dummytri; + seg.triangles[1].tri = dummytri; + + newsubseg.seg = seg; + newsubseg.orient = 0; + + subsegs.Add(seg.hash, seg); + } + + #endregion + + #region Manipulation + + /// + /// Insert a vertex into a Delaunay triangulation, performing flips as necessary + /// to maintain the Delaunay property. + /// + /// The point to be inserted. + /// The triangle to start the search. + /// Segment to split. + /// Check for creation of encroached subsegments. + /// Check for creation of bad quality triangles. + /// If a duplicate vertex or violated segment does not prevent the + /// vertex from being inserted, the return value will be ENCROACHINGVERTEX if + /// the vertex encroaches upon a subsegment (and checking is enabled), or + /// SUCCESSFULVERTEX otherwise. In either case, 'searchtri' is set to a handle + /// whose origin is the newly inserted vertex. + /// + /// The point 'newvertex' is located. If 'searchtri.triangle' is not NULL, + /// the search for the containing triangle begins from 'searchtri'. If + /// 'searchtri.triangle' is NULL, a full point location procedure is called. + /// If 'insertvertex' is found inside a triangle, the triangle is split into + /// three; if 'insertvertex' lies on an edge, the edge is split in two, + /// thereby splitting the two adjacent triangles into four. Edge flips are + /// used to restore the Delaunay property. If 'insertvertex' lies on an + /// existing vertex, no action is taken, and the value DUPLICATEVERTEX is + /// returned. On return, 'searchtri' is set to a handle whose origin is the + /// existing vertex. + /// + /// InsertVertex() does not use flip() for reasons of speed; some + /// information can be reused from edge flip to edge flip, like the + /// locations of subsegments. + /// + /// Param 'splitseg': Normally, the parameter 'splitseg' is set to NULL, + /// implying that no subsegment should be split. In this case, if 'insertvertex' + /// is found to lie on a segment, no action is taken, and the value VIOLATINGVERTEX + /// is returned. On return, 'searchtri' is set to a handle whose primary edge is the + /// violated subsegment. + /// If the calling routine wishes to split a subsegment by inserting a vertex in it, + /// the parameter 'splitseg' should be that subsegment. In this case, 'searchtri' + /// MUST be the triangle handle reached by pivoting from that subsegment; no point + /// location is done. + /// + /// Param 'segmentflaws': Flags that indicate whether or not there should + /// be checks for the creation of encroached subsegments. If a newly inserted + /// vertex encroaches upon subsegments, these subsegments are added to the list + /// of subsegments to be split if 'segmentflaws' is set. + /// + /// Param 'triflaws': Flags that indicate whether or not there should be + /// checks for the creation of bad quality triangles. If bad triangles are + /// created, these are added to the queue if 'triflaws' is set. + /// + internal InsertVertexResult InsertVertex(Vertex newvertex, ref Otri searchtri, + ref Osub splitseg, bool segmentflaws, bool triflaws) + { + Otri horiz = default(Otri); + Otri top = default(Otri); + Otri botleft = default(Otri), botright = default(Otri); + Otri topleft = default(Otri), topright = default(Otri); + Otri newbotleft = default(Otri), newbotright = default(Otri); + Otri newtopright = default(Otri); + Otri botlcasing = default(Otri), botrcasing = default(Otri); + Otri toplcasing = default(Otri), toprcasing = default(Otri); + Otri testtri = default(Otri); + Osub botlsubseg = default(Osub), botrsubseg = default(Osub); + Osub toplsubseg = default(Osub), toprsubseg = default(Osub); + Osub brokensubseg = default(Osub); + Osub checksubseg = default(Osub); + Osub rightsubseg = default(Osub); + Osub newsubseg = default(Osub); + BadSubseg encroached; + //FlipStacker newflip; + Vertex first; + Vertex leftvertex, rightvertex, botvertex, topvertex, farvertex; + Vertex segmentorg, segmentdest; + int region; + double area; + InsertVertexResult success; + LocateResult intersect; + bool doflip; + bool mirrorflag; + bool enq; + + if (splitseg.seg == null) + { + // Find the location of the vertex to be inserted. Check if a good + // starting triangle has already been provided by the caller. + if (searchtri.tri.id == DUMMY) + { + // Find a boundary triangle. + horiz.tri = dummytri; + horiz.orient = 0; + horiz.Sym(); + + // Search for a triangle containing 'newvertex'. + intersect = locator.Locate(newvertex, ref horiz); + } + else + { + // Start searching from the triangle provided by the caller. + searchtri.Copy(ref horiz); + intersect = locator.PreciseLocate(newvertex, ref horiz, true); + } + } + else + { + // The calling routine provides the subsegment in which + // the vertex is inserted. + searchtri.Copy(ref horiz); + intersect = LocateResult.OnEdge; + } + + if (intersect == LocateResult.OnVertex) + { + // There's already a vertex there. Return in 'searchtri' a triangle + // whose origin is the existing vertex. + horiz.Copy(ref searchtri); + locator.Update(ref horiz); + return InsertVertexResult.Duplicate; + } + if ((intersect == LocateResult.OnEdge) || (intersect == LocateResult.Outside)) + { + // The vertex falls on an edge or boundary. + if (checksegments && (splitseg.seg == null)) + { + // Check whether the vertex falls on a subsegment. + horiz.Pivot(ref brokensubseg); + if (brokensubseg.seg.hash != DUMMY) + { + // The vertex falls on a subsegment, and hence will not be inserted. + if (segmentflaws) + { + enq = behavior.NoBisect != 2; + if (enq && (behavior.NoBisect == 1)) + { + // This subsegment may be split only if it is an + // internal boundary. + horiz.Sym(ref testtri); + enq = testtri.tri.id != DUMMY; + } + if (enq) + { + // Add the subsegment to the list of encroached subsegments. + encroached = new BadSubseg(); + encroached.subseg = brokensubseg; + encroached.org = brokensubseg.Org(); + encroached.dest = brokensubseg.Dest(); + + qualityMesher.AddBadSubseg(encroached); + } + } + // Return a handle whose primary edge contains the vertex, + // which has not been inserted. + horiz.Copy(ref searchtri); + locator.Update(ref horiz); + return InsertVertexResult.Violating; + } + } + + // Insert the vertex on an edge, dividing one triangle into two (if + // the edge lies on a boundary) or two triangles into four. + horiz.Lprev(ref botright); + botright.Sym(ref botrcasing); + horiz.Sym(ref topright); + // Is there a second triangle? (Or does this edge lie on a boundary?) + mirrorflag = topright.tri.id != DUMMY; + if (mirrorflag) + { + topright.Lnext(); + topright.Sym(ref toprcasing); + MakeTriangle(ref newtopright); + } + else + { + // Splitting a boundary edge increases the number of boundary edges. + hullsize++; + } + MakeTriangle(ref newbotright); + + // Set the vertices of changed and new triangles. + rightvertex = horiz.Org(); + leftvertex = horiz.Dest(); + botvertex = horiz.Apex(); + newbotright.SetOrg(botvertex); + newbotright.SetDest(rightvertex); + newbotright.SetApex(newvertex); + horiz.SetOrg(newvertex); + + // Set the region of a new triangle. + newbotright.tri.label = botright.tri.label; + + if (behavior.VarArea) + { + // Set the area constraint of a new triangle. + newbotright.tri.area = botright.tri.area; + } + + if (mirrorflag) + { + topvertex = topright.Dest(); + newtopright.SetOrg(rightvertex); + newtopright.SetDest(topvertex); + newtopright.SetApex(newvertex); + topright.SetOrg(newvertex); + + // Set the region of another new triangle. + newtopright.tri.label = topright.tri.label; + + if (behavior.VarArea) + { + // Set the area constraint of another new triangle. + newtopright.tri.area = topright.tri.area; + } + } + + // There may be subsegments that need to be bonded + // to the new triangle(s). + if (checksegments) + { + botright.Pivot(ref botrsubseg); + + if (botrsubseg.seg.hash != DUMMY) + { + botright.SegDissolve(dummysub); + newbotright.SegBond(ref botrsubseg); + } + + if (mirrorflag) + { + topright.Pivot(ref toprsubseg); + if (toprsubseg.seg.hash != DUMMY) + { + topright.SegDissolve(dummysub); + newtopright.SegBond(ref toprsubseg); + } + } + } + + // Bond the new triangle(s) to the surrounding triangles. + newbotright.Bond(ref botrcasing); + newbotright.Lprev(); + newbotright.Bond(ref botright); + newbotright.Lprev(); + + if (mirrorflag) + { + newtopright.Bond(ref toprcasing); + newtopright.Lnext(); + newtopright.Bond(ref topright); + newtopright.Lnext(); + newtopright.Bond(ref newbotright); + } + + if (splitseg.seg != null) + { + // Split the subsegment into two. + splitseg.SetDest(newvertex); + segmentorg = splitseg.SegOrg(); + segmentdest = splitseg.SegDest(); + splitseg.Sym(); + splitseg.Pivot(ref rightsubseg); + InsertSubseg(ref newbotright, splitseg.seg.boundary); + newbotright.Pivot(ref newsubseg); + newsubseg.SetSegOrg(segmentorg); + newsubseg.SetSegDest(segmentdest); + splitseg.Bond(ref newsubseg); + newsubseg.Sym(); + newsubseg.Bond(ref rightsubseg); + splitseg.Sym(); + + // Transfer the subsegment's boundary marker to the vertex if required. + if (newvertex.label == 0) + { + newvertex.label = splitseg.seg.boundary; + } + } + + if (checkquality) + { + flipstack.Clear(); + + flipstack.Push(default(Otri)); // Dummy flip (see UndoVertex) + flipstack.Push(horiz); + } + + // Position 'horiz' on the first edge to check for + // the Delaunay property. + horiz.Lnext(); + } + else + { + // Insert the vertex in a triangle, splitting it into three. + horiz.Lnext(ref botleft); + horiz.Lprev(ref botright); + botleft.Sym(ref botlcasing); + botright.Sym(ref botrcasing); + MakeTriangle(ref newbotleft); + MakeTriangle(ref newbotright); + + // Set the vertices of changed and new triangles. + rightvertex = horiz.Org(); + leftvertex = horiz.Dest(); + botvertex = horiz.Apex(); + newbotleft.SetOrg(leftvertex); + newbotleft.SetDest(botvertex); + newbotleft.SetApex(newvertex); + newbotright.SetOrg(botvertex); + newbotright.SetDest(rightvertex); + newbotright.SetApex(newvertex); + horiz.SetApex(newvertex); + + // Set the region of the new triangles. + newbotleft.tri.label = horiz.tri.label; + newbotright.tri.label = horiz.tri.label; + + if (behavior.VarArea) + { + // Set the area constraint of the new triangles. + area = horiz.tri.area; + newbotleft.tri.area = area; + newbotright.tri.area = area; + } + + // There may be subsegments that need to be bonded + // to the new triangles. + if (checksegments) + { + botleft.Pivot(ref botlsubseg); + if (botlsubseg.seg.hash != DUMMY) + { + botleft.SegDissolve(dummysub); + newbotleft.SegBond(ref botlsubseg); + } + botright.Pivot(ref botrsubseg); + if (botrsubseg.seg.hash != DUMMY) + { + botright.SegDissolve(dummysub); + newbotright.SegBond(ref botrsubseg); + } + } + + // Bond the new triangles to the surrounding triangles. + newbotleft.Bond(ref botlcasing); + newbotright.Bond(ref botrcasing); + newbotleft.Lnext(); + newbotright.Lprev(); + newbotleft.Bond(ref newbotright); + newbotleft.Lnext(); + botleft.Bond(ref newbotleft); + newbotright.Lprev(); + botright.Bond(ref newbotright); + + if (checkquality) + { + flipstack.Clear(); + flipstack.Push(horiz); + } + } + + // The insertion is successful by default, unless an encroached + // subsegment is found. + success = InsertVertexResult.Successful; + + if (newvertex.tri.tri != null) + { + // Store the coordinates of the triangle that contains newvertex. + newvertex.tri.SetOrg(rightvertex); + newvertex.tri.SetDest(leftvertex); + newvertex.tri.SetApex(botvertex); + } + + // Circle around the newly inserted vertex, checking each edge opposite it + // for the Delaunay property. Non-Delaunay edges are flipped. 'horiz' is + // always the edge being checked. 'first' marks where to stop circling. + first = horiz.Org(); + rightvertex = first; + leftvertex = horiz.Dest(); + // Circle until finished. + while (true) + { + // By default, the edge will be flipped. + doflip = true; + + if (checksegments) + { + // Check for a subsegment, which cannot be flipped. + horiz.Pivot(ref checksubseg); + if (checksubseg.seg.hash != DUMMY) + { + // The edge is a subsegment and cannot be flipped. + doflip = false; + + if (segmentflaws) + { + // Does the new vertex encroach upon this subsegment? + if (qualityMesher.CheckSeg4Encroach(ref checksubseg) > 0) + { + success = InsertVertexResult.Encroaching; + } + } + } + } + + if (doflip) + { + // Check if the edge is a boundary edge. + horiz.Sym(ref top); + if (top.tri.id == DUMMY) + { + // The edge is a boundary edge and cannot be flipped. + doflip = false; + } + else + { + // Find the vertex on the other side of the edge. + farvertex = top.Apex(); + // In the incremental Delaunay triangulation algorithm, any of + // 'leftvertex', 'rightvertex', and 'farvertex' could be vertices + // of the triangular bounding box. These vertices must be + // treated as if they are infinitely distant, even though their + // "coordinates" are not. + if ((leftvertex == infvertex1) || (leftvertex == infvertex2) || + (leftvertex == infvertex3)) + { + // 'leftvertex' is infinitely distant. Check the convexity of + // the boundary of the triangulation. 'farvertex' might be + // infinite as well, but trust me, this same condition should + // be applied. + doflip = predicates.CounterClockwise(newvertex, rightvertex, farvertex) > 0.0; + } + else if ((rightvertex == infvertex1) || + (rightvertex == infvertex2) || + (rightvertex == infvertex3)) + { + // 'rightvertex' is infinitely distant. Check the convexity of + // the boundary of the triangulation. 'farvertex' might be + // infinite as well, but trust me, this same condition should + // be applied. + doflip = predicates.CounterClockwise(farvertex, leftvertex, newvertex) > 0.0; + } + else if ((farvertex == infvertex1) || + (farvertex == infvertex2) || + (farvertex == infvertex3)) + { + // 'farvertex' is infinitely distant and cannot be inside + // the circumcircle of the triangle 'horiz'. + doflip = false; + } + else + { + // Test whether the edge is locally Delaunay. + doflip = predicates.InCircle(leftvertex, newvertex, rightvertex, farvertex) > 0.0; + } + if (doflip) + { + // We made it! Flip the edge 'horiz' by rotating its containing + // quadrilateral (the two triangles adjacent to 'horiz'). + // Identify the casing of the quadrilateral. + top.Lprev(ref topleft); + topleft.Sym(ref toplcasing); + top.Lnext(ref topright); + topright.Sym(ref toprcasing); + horiz.Lnext(ref botleft); + botleft.Sym(ref botlcasing); + horiz.Lprev(ref botright); + botright.Sym(ref botrcasing); + // Rotate the quadrilateral one-quarter turn counterclockwise. + topleft.Bond(ref botlcasing); + botleft.Bond(ref botrcasing); + botright.Bond(ref toprcasing); + topright.Bond(ref toplcasing); + if (checksegments) + { + // Check for subsegments and rebond them to the quadrilateral. + topleft.Pivot(ref toplsubseg); + botleft.Pivot(ref botlsubseg); + botright.Pivot(ref botrsubseg); + topright.Pivot(ref toprsubseg); + if (toplsubseg.seg.hash == DUMMY) + { + topright.SegDissolve(dummysub); + } + else + { + topright.SegBond(ref toplsubseg); + } + if (botlsubseg.seg.hash == DUMMY) + { + topleft.SegDissolve(dummysub); + } + else + { + topleft.SegBond(ref botlsubseg); + } + if (botrsubseg.seg.hash == DUMMY) + { + botleft.SegDissolve(dummysub); + } + else + { + botleft.SegBond(ref botrsubseg); + } + if (toprsubseg.seg.hash == DUMMY) + { + botright.SegDissolve(dummysub); + } + else + { + botright.SegBond(ref toprsubseg); + } + } + // New vertex assignments for the rotated quadrilateral. + horiz.SetOrg(farvertex); + horiz.SetDest(newvertex); + horiz.SetApex(rightvertex); + top.SetOrg(newvertex); + top.SetDest(farvertex); + top.SetApex(leftvertex); + + // Assign region. + // TODO: check region ok (no Math.Min necessary) + region = Math.Min(top.tri.label, horiz.tri.label); + top.tri.label = region; + horiz.tri.label = region; + + if (behavior.VarArea) + { + if ((top.tri.area <= 0.0) || (horiz.tri.area <= 0.0)) + { + area = -1.0; + } + else + { + // Take the average of the two triangles' area constraints. + // This prevents small area constraints from migrating a + // long, long way from their original location due to flips. + area = 0.5 * (top.tri.area + horiz.tri.area); + } + + top.tri.area = area; + horiz.tri.area = area; + } + + if (checkquality) + { + flipstack.Push(horiz); + } + + // On the next iterations, consider the two edges that were exposed (this + // is, are now visible to the newly inserted vertex) by the edge flip. + horiz.Lprev(); + leftvertex = farvertex; + } + } + } + if (!doflip) + { + // The handle 'horiz' is accepted as locally Delaunay. + if (triflaws) + { + // Check the triangle 'horiz' for quality. + qualityMesher.TestTriangle(ref horiz); + } + + // Look for the next edge around the newly inserted vertex. + horiz.Lnext(); + horiz.Sym(ref testtri); + // Check for finishing a complete revolution about the new vertex, or + // falling outside of the triangulation. The latter will happen when + // a vertex is inserted at a boundary. + if ((leftvertex == first) || (testtri.tri.id == DUMMY)) + { + // We're done. Return a triangle whose origin is the new vertex. + horiz.Lnext(ref searchtri); + + Otri recenttri = default(Otri); + horiz.Lnext(ref recenttri); + locator.Update(ref recenttri); + + return success; + } + // Finish finding the next edge around the newly inserted vertex. + testtri.Lnext(ref horiz); + rightvertex = leftvertex; + leftvertex = horiz.Dest(); + } + } + } + + /// + /// Create a new subsegment and inserts it between two triangles. Its + /// vertices are properly initialized. + /// + /// The new subsegment is inserted at the edge + /// described by this handle. + /// The marker 'subsegmark' is applied to the + /// subsegment and, if appropriate, its vertices. + internal void InsertSubseg(ref Otri tri, int subsegmark) + { + Otri oppotri = default(Otri); + Osub newsubseg = default(Osub); + Vertex triorg, tridest; + + triorg = tri.Org(); + tridest = tri.Dest(); + // Mark vertices if possible. + if (triorg.label == 0) + { + triorg.label = subsegmark; + } + if (tridest.label == 0) + { + tridest.label = subsegmark; + } + // Check if there's already a subsegment here. + tri.Pivot(ref newsubseg); + if (newsubseg.seg.hash == DUMMY) + { + // Make new subsegment and initialize its vertices. + MakeSegment(ref newsubseg); + newsubseg.SetOrg(tridest); + newsubseg.SetDest(triorg); + newsubseg.SetSegOrg(tridest); + newsubseg.SetSegDest(triorg); + // Bond new subsegment to the two triangles it is sandwiched between. + // Note that the facing triangle 'oppotri' might be equal to 'dummytri' + // (outer space), but the new subsegment is bonded to it all the same. + tri.SegBond(ref newsubseg); + tri.Sym(ref oppotri); + newsubseg.Sym(); + oppotri.SegBond(ref newsubseg); + newsubseg.seg.boundary = subsegmark; + } + else if (newsubseg.seg.boundary == 0) + { + newsubseg.seg.boundary = subsegmark; + } + } + + /// + /// Transform two triangles to two different triangles by flipping an edge + /// counterclockwise within a quadrilateral. + /// + /// Handle to the edge that will be flipped. + /// Imagine the original triangles, abc and bad, oriented so that the + /// shared edge ab lies in a horizontal plane, with the vertex b on the left + /// and the vertex a on the right. The vertex c lies below the edge, and + /// the vertex d lies above the edge. The 'flipedge' handle holds the edge + /// ab of triangle abc, and is directed left, from vertex a to vertex b. + /// + /// The triangles abc and bad are deleted and replaced by the triangles cdb + /// and dca. The triangles that represent abc and bad are NOT deallocated; + /// they are reused for dca and cdb, respectively. Hence, any handles that + /// may have held the original triangles are still valid, although not + /// directed as they were before. + /// + /// Upon completion of this routine, the 'flipedge' handle holds the edge + /// dc of triangle dca, and is directed down, from vertex d to vertex c. + /// (Hence, the two triangles have rotated counterclockwise.) + /// + /// WARNING: This transformation is geometrically valid only if the + /// quadrilateral adbc is convex. Furthermore, this transformation is + /// valid only if there is not a subsegment between the triangles abc and + /// bad. This routine does not check either of these preconditions, and + /// it is the responsibility of the calling routine to ensure that they are + /// met. If they are not, the streets shall be filled with wailing and + /// gnashing of teeth. + /// + /// Terminology + /// + /// A "local transformation" replaces a small set of triangles with another + /// set of triangles. This may or may not involve inserting or deleting a + /// vertex. + /// + /// The term "casing" is used to describe the set of triangles that are + /// attached to the triangles being transformed, but are not transformed + /// themselves. Think of the casing as a fixed hollow structure inside + /// which all the action happens. A "casing" is only defined relative to + /// a single transformation; each occurrence of a transformation will + /// involve a different casing. + /// + internal void Flip(ref Otri flipedge) + { + Otri botleft = default(Otri), botright = default(Otri); + Otri topleft = default(Otri), topright = default(Otri); + Otri top = default(Otri); + Otri botlcasing = default(Otri), botrcasing = default(Otri); + Otri toplcasing = default(Otri), toprcasing = default(Otri); + Osub botlsubseg = default(Osub), botrsubseg = default(Osub); + Osub toplsubseg = default(Osub), toprsubseg = default(Osub); + Vertex leftvertex, rightvertex, botvertex; + Vertex farvertex; + + // Identify the vertices of the quadrilateral. + rightvertex = flipedge.Org(); + leftvertex = flipedge.Dest(); + botvertex = flipedge.Apex(); + flipedge.Sym(ref top); + + // SELF CHECK + + //if (top.triangle.id == DUMMY) + //{ + // logger.Error("Attempt to flip on boundary.", "Mesh.Flip()"); + // flipedge.LnextSelf(); + // return; + //} + + //if (checksegments) + //{ + // flipedge.SegPivot(ref toplsubseg); + // if (toplsubseg.ss != Segment.Empty) + // { + // logger.Error("Attempt to flip a segment.", "Mesh.Flip()"); + // flipedge.LnextSelf(); + // return; + // } + //} + + farvertex = top.Apex(); + + // Identify the casing of the quadrilateral. + top.Lprev(ref topleft); + topleft.Sym(ref toplcasing); + top.Lnext(ref topright); + topright.Sym(ref toprcasing); + flipedge.Lnext(ref botleft); + botleft.Sym(ref botlcasing); + flipedge.Lprev(ref botright); + botright.Sym(ref botrcasing); + // Rotate the quadrilateral one-quarter turn counterclockwise. + topleft.Bond(ref botlcasing); + botleft.Bond(ref botrcasing); + botright.Bond(ref toprcasing); + topright.Bond(ref toplcasing); + + if (checksegments) + { + // Check for subsegments and rebond them to the quadrilateral. + topleft.Pivot(ref toplsubseg); + botleft.Pivot(ref botlsubseg); + botright.Pivot(ref botrsubseg); + topright.Pivot(ref toprsubseg); + + if (toplsubseg.seg.hash == DUMMY) + { + topright.SegDissolve(dummysub); + } + else + { + topright.SegBond(ref toplsubseg); + } + + if (botlsubseg.seg.hash == DUMMY) + { + topleft.SegDissolve(dummysub); + } + else + { + topleft.SegBond(ref botlsubseg); + } + + if (botrsubseg.seg.hash == DUMMY) + { + botleft.SegDissolve(dummysub); + } + else + { + botleft.SegBond(ref botrsubseg); + } + + if (toprsubseg.seg.hash == DUMMY) + { + botright.SegDissolve(dummysub); + } + else + { + botright.SegBond(ref toprsubseg); + } + } + + // New vertex assignments for the rotated quadrilateral. + flipedge.SetOrg(farvertex); + flipedge.SetDest(botvertex); + flipedge.SetApex(rightvertex); + top.SetOrg(botvertex); + top.SetDest(farvertex); + top.SetApex(leftvertex); + } + + /// + /// Transform two triangles to two different triangles by flipping an edge + /// clockwise within a quadrilateral. Reverses the flip() operation so that + /// the data structures representing the triangles are back where they were + /// before the flip(). + /// + /// + /// + /// See above Flip() remarks for more information. + /// + /// Upon completion of this routine, the 'flipedge' handle holds the edge + /// cd of triangle cdb, and is directed up, from vertex c to vertex d. + /// (Hence, the two triangles have rotated clockwise.) + /// + internal void Unflip(ref Otri flipedge) + { + Otri botleft = default(Otri), botright = default(Otri); + Otri topleft = default(Otri), topright = default(Otri); + Otri top = default(Otri); + Otri botlcasing = default(Otri), botrcasing = default(Otri); + Otri toplcasing = default(Otri), toprcasing = default(Otri); + Osub botlsubseg = default(Osub), botrsubseg = default(Osub); + Osub toplsubseg = default(Osub), toprsubseg = default(Osub); + Vertex leftvertex, rightvertex, botvertex; + Vertex farvertex; + + // Identify the vertices of the quadrilateral. + rightvertex = flipedge.Org(); + leftvertex = flipedge.Dest(); + botvertex = flipedge.Apex(); + flipedge.Sym(ref top); + + farvertex = top.Apex(); + + // Identify the casing of the quadrilateral. + top.Lprev(ref topleft); + topleft.Sym(ref toplcasing); + top.Lnext(ref topright); + topright.Sym(ref toprcasing); + flipedge.Lnext(ref botleft); + botleft.Sym(ref botlcasing); + flipedge.Lprev(ref botright); + botright.Sym(ref botrcasing); + // Rotate the quadrilateral one-quarter turn clockwise. + topleft.Bond(ref toprcasing); + botleft.Bond(ref toplcasing); + botright.Bond(ref botlcasing); + topright.Bond(ref botrcasing); + + if (checksegments) + { + // Check for subsegments and rebond them to the quadrilateral. + topleft.Pivot(ref toplsubseg); + botleft.Pivot(ref botlsubseg); + botright.Pivot(ref botrsubseg); + topright.Pivot(ref toprsubseg); + if (toplsubseg.seg.hash == DUMMY) + { + botleft.SegDissolve(dummysub); + } + else + { + botleft.SegBond(ref toplsubseg); + } + if (botlsubseg.seg.hash == DUMMY) + { + botright.SegDissolve(dummysub); + } + else + { + botright.SegBond(ref botlsubseg); + } + if (botrsubseg.seg.hash == DUMMY) + { + topright.SegDissolve(dummysub); + } + else + { + topright.SegBond(ref botrsubseg); + } + if (toprsubseg.seg.hash == DUMMY) + { + topleft.SegDissolve(dummysub); + } + else + { + topleft.SegBond(ref toprsubseg); + } + } + + // New vertex assignments for the rotated quadrilateral. + flipedge.SetOrg(botvertex); + flipedge.SetDest(farvertex); + flipedge.SetApex(leftvertex); + top.SetOrg(farvertex); + top.SetDest(botvertex); + top.SetApex(rightvertex); + } + + /// + /// Find the Delaunay triangulation of a polygon that has a certain "nice" shape. + /// This includes the polygons that result from deletion of a vertex or insertion + /// of a segment. + /// + /// The primary edge of the first triangle. + /// The primary edge of the last triangle. + /// The number of sides of the polygon, including its + /// base. + /// A flag, wether to perform the last flip. + /// A flag that determines whether the new triangles should + /// be tested for quality, and enqueued if they are bad. + /// + // This is a conceptually difficult routine. The starting assumption is + // that we have a polygon with n sides. n - 1 of these sides are currently + // represented as edges in the mesh. One side, called the "base", need not + // be. + // + // Inside the polygon is a structure I call a "fan", consisting of n - 1 + // triangles that share a common origin. For each of these triangles, the + // edge opposite the origin is one of the sides of the polygon. The + // primary edge of each triangle is the edge directed from the origin to + // the destination; note that this is not the same edge that is a side of + // the polygon. 'firstedge' is the primary edge of the first triangle. + // From there, the triangles follow in counterclockwise order about the + // polygon, until 'lastedge', the primary edge of the last triangle. + // 'firstedge' and 'lastedge' are probably connected to other triangles + // beyond the extremes of the fan, but their identity is not important, as + // long as the fan remains connected to them. + // + // Imagine the polygon oriented so that its base is at the bottom. This + // puts 'firstedge' on the far right, and 'lastedge' on the far left. + // The right vertex of the base is the destination of 'firstedge', and the + // left vertex of the base is the apex of 'lastedge'. + // + // The challenge now is to find the right sequence of edge flips to + // transform the fan into a Delaunay triangulation of the polygon. Each + // edge flip effectively removes one triangle from the fan, committing it + // to the polygon. The resulting polygon has one fewer edge. If 'doflip' + // is set, the final flip will be performed, resulting in a fan of one + // (useless?) triangle. If 'doflip' is not set, the final flip is not + // performed, resulting in a fan of two triangles, and an unfinished + // triangular polygon that is not yet filled out with a single triangle. + // On completion of the routine, 'lastedge' is the last remaining triangle, + // or the leftmost of the last two. + // + // Although the flips are performed in the order described above, the + // decisions about what flips to perform are made in precisely the reverse + // order. The recursive triangulatepolygon() procedure makes a decision, + // uses up to two recursive calls to triangulate the "subproblems" + // (polygons with fewer edges), and then performs an edge flip. + // + // The "decision" it makes is which vertex of the polygon should be + // connected to the base. This decision is made by testing every possible + // vertex. Once the best vertex is found, the two edges that connect this + // vertex to the base become the bases for two smaller polygons. These + // are triangulated recursively. Unfortunately, this approach can take + // O(n^2) time not only in the worst case, but in many common cases. It's + // rarely a big deal for vertex deletion, where n is rarely larger than + // ten, but it could be a big deal for segment insertion, especially if + // there's a lot of long segments that each cut many triangles. I ought to + // code a faster algorithm some day. + /// + private void TriangulatePolygon(Otri firstedge, Otri lastedge, + int edgecount, bool doflip, bool triflaws) + { + Otri testtri = default(Otri); + Otri besttri = default(Otri); + Otri tempedge = default(Otri); + Vertex leftbasevertex, rightbasevertex; + Vertex testvertex; + Vertex bestvertex; + + int bestnumber = 1; + + // Identify the base vertices. + leftbasevertex = lastedge.Apex(); + rightbasevertex = firstedge.Dest(); + + // Find the best vertex to connect the base to. + firstedge.Onext(ref besttri); + bestvertex = besttri.Dest(); + besttri.Copy(ref testtri); + + for (int i = 2; i <= edgecount - 2; i++) + { + testtri.Onext(); + testvertex = testtri.Dest(); + // Is this a better vertex? + if (predicates.InCircle(leftbasevertex, rightbasevertex, bestvertex, testvertex) > 0.0) + { + testtri.Copy(ref besttri); + bestvertex = testvertex; + bestnumber = i; + } + } + + if (bestnumber > 1) + { + // Recursively triangulate the smaller polygon on the right. + besttri.Oprev(ref tempedge); + TriangulatePolygon(firstedge, tempedge, bestnumber + 1, true, triflaws); + } + + if (bestnumber < edgecount - 2) + { + // Recursively triangulate the smaller polygon on the left. + besttri.Sym(ref tempedge); + TriangulatePolygon(besttri, lastedge, edgecount - bestnumber, true, triflaws); + // Find 'besttri' again; it may have been lost to edge flips. + tempedge.Sym(ref besttri); + } + + if (doflip) + { + // Do one final edge flip. + Flip(ref besttri); + if (triflaws) + { + // Check the quality of the newly committed triangle. + besttri.Sym(ref testtri); + qualityMesher.TestTriangle(ref testtri); + } + } + // Return the base triangle. + besttri.Copy(ref lastedge); + } + + /// + /// Delete a vertex from a Delaunay triangulation, ensuring that the + /// triangulation remains Delaunay. + /// + /// + /// The origin of 'deltri' is deleted. The union of the triangles + /// adjacent to this vertex is a polygon, for which the Delaunay triangulation + /// is found. Two triangles are removed from the mesh. + /// + /// Only interior vertices that do not lie on segments or boundaries + /// may be deleted. + /// + internal void DeleteVertex(ref Otri deltri) + { + Otri countingtri = default(Otri); + Otri firstedge = default(Otri), lastedge = default(Otri); + Otri deltriright = default(Otri); + Otri lefttri = default(Otri), righttri = default(Otri); + Otri leftcasing = default(Otri), rightcasing = default(Otri); + Osub leftsubseg = default(Osub), rightsubseg = default(Osub); + Vertex delvertex; + Vertex neworg; + int edgecount; + + delvertex = deltri.Org(); + + VertexDealloc(delvertex); + + // Count the degree of the vertex being deleted. + deltri.Onext(ref countingtri); + edgecount = 1; + while (!deltri.Equals(countingtri)) + { + edgecount++; + countingtri.Onext(); + } + + if (edgecount > 3) + { + // Triangulate the polygon defined by the union of all triangles + // adjacent to the vertex being deleted. Check the quality of + // the resulting triangles. + deltri.Onext(ref firstedge); + deltri.Oprev(ref lastedge); + TriangulatePolygon(firstedge, lastedge, edgecount, false, behavior.NoBisect == 0); + } + // Splice out two triangles. + deltri.Lprev(ref deltriright); + deltri.Dnext(ref lefttri); + lefttri.Sym(ref leftcasing); + deltriright.Oprev(ref righttri); + righttri.Sym(ref rightcasing); + deltri.Bond(ref leftcasing); + deltriright.Bond(ref rightcasing); + lefttri.Pivot(ref leftsubseg); + if (leftsubseg.seg.hash != DUMMY) + { + deltri.SegBond(ref leftsubseg); + } + righttri.Pivot(ref rightsubseg); + if (rightsubseg.seg.hash != DUMMY) + { + deltriright.SegBond(ref rightsubseg); + } + + // Set the new origin of 'deltri' and check its quality. + neworg = lefttri.Org(); + deltri.SetOrg(neworg); + if (behavior.NoBisect == 0) + { + qualityMesher.TestTriangle(ref deltri); + } + + // Delete the two spliced-out triangles. + TriangleDealloc(lefttri.tri); + TriangleDealloc(righttri.tri); + } + + /// + /// Undo the most recent vertex insertion. + /// + /// + /// Walks through the list of transformations (flips and a vertex insertion) + /// in the reverse of the order in which they were done, and undoes them. + /// The inserted vertex is removed from the triangulation and deallocated. + /// Two triangles (possibly just one) are also deallocated. + /// + internal void UndoVertex() + { + Otri fliptri; + + Otri botleft = default(Otri), botright = default(Otri), topright = default(Otri); + Otri botlcasing = default(Otri), botrcasing = default(Otri), toprcasing = default(Otri); + Otri gluetri = default(Otri); + Osub botlsubseg = default(Osub), botrsubseg = default(Osub), toprsubseg = default(Osub); + Vertex botvertex, rightvertex; + + // Walk through the list of transformations (flips and a vertex insertion) + // in the reverse of the order in which they were done, and undo them. + while (flipstack.Count > 0) + { + // Find a triangle involved in the last unreversed transformation. + fliptri = flipstack.Pop(); + + // We are reversing one of three transformations: a trisection of one + // triangle into three (by inserting a vertex in the triangle), a + // bisection of two triangles into four (by inserting a vertex in an + // edge), or an edge flip. + if (flipstack.Count == 0) + { + // Restore a triangle that was split into three triangles, + // so it is again one triangle. + fliptri.Dprev(ref botleft); + botleft.Lnext(); + fliptri.Onext(ref botright); + botright.Lprev(); + botleft.Sym(ref botlcasing); + botright.Sym(ref botrcasing); + botvertex = botleft.Dest(); + + fliptri.SetApex(botvertex); + fliptri.Lnext(); + fliptri.Bond(ref botlcasing); + botleft.Pivot(ref botlsubseg); + fliptri.SegBond(ref botlsubseg); + fliptri.Lnext(); + fliptri.Bond(ref botrcasing); + botright.Pivot(ref botrsubseg); + fliptri.SegBond(ref botrsubseg); + + // Delete the two spliced-out triangles. + TriangleDealloc(botleft.tri); + TriangleDealloc(botright.tri); + } + else if (flipstack.Peek().tri == null) // Dummy flip + { + // Restore two triangles that were split into four triangles, + // so they are again two triangles. + fliptri.Lprev(ref gluetri); + gluetri.Sym(ref botright); + botright.Lnext(); + botright.Sym(ref botrcasing); + rightvertex = botright.Dest(); + + fliptri.SetOrg(rightvertex); + gluetri.Bond(ref botrcasing); + botright.Pivot(ref botrsubseg); + gluetri.SegBond(ref botrsubseg); + + // Delete the spliced-out triangle. + TriangleDealloc(botright.tri); + + fliptri.Sym(ref gluetri); + if (gluetri.tri.id != DUMMY) + { + gluetri.Lnext(); + gluetri.Dnext(ref topright); + topright.Sym(ref toprcasing); + + gluetri.SetOrg(rightvertex); + gluetri.Bond(ref toprcasing); + topright.Pivot(ref toprsubseg); + gluetri.SegBond(ref toprsubseg); + + // Delete the spliced-out triangle. + TriangleDealloc(topright.tri); + } + + flipstack.Clear(); + } + else + { + // Undo an edge flip. + Unflip(ref fliptri); + } + } + } + + #endregion + + #region Dealloc + + /// + /// Deallocate space for a triangle, marking it dead. + /// + /// + internal void TriangleDealloc(Triangle dyingtriangle) + { + // Mark the triangle as dead. This makes it possible to detect dead + // triangles when traversing the list of all triangles. + Otri.Kill(dyingtriangle); + triangles.Release(dyingtriangle); + } + + /// + /// Deallocate space for a vertex, marking it dead. + /// + /// + internal void VertexDealloc(Vertex dyingvertex) + { + // Mark the vertex as dead. This makes it possible to detect dead + // vertices when traversing the list of all vertices. + dyingvertex.type = VertexType.DeadVertex; + vertices.Remove(dyingvertex.hash); + } + + /// + /// Deallocate space for a subsegment, marking it dead. + /// + /// + internal void SubsegDealloc(SubSegment dyingsubseg) + { + // Mark the subsegment as dead. This makes it possible to detect dead + // subsegments when traversing the list of all subsegments. + Osub.Kill(dyingsubseg); + subsegs.Remove(dyingsubseg.hash); + } + + #endregion + } +} diff --git a/External/Triangle.NET/Triangle/MeshValidator.cs b/External/Triangle.NET/Triangle/MeshValidator.cs new file mode 100644 index 0000000..d6e4982 --- /dev/null +++ b/External/Triangle.NET/Triangle/MeshValidator.cs @@ -0,0 +1,215 @@ +// ----------------------------------------------------------------------- +// +// 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/ +// +// ----------------------------------------------------------------------- + +namespace TriangleNet +{ + using System; + using TriangleNet.Topology; + using TriangleNet.Geometry; + + public static class MeshValidator + { + private static RobustPredicates predicates = RobustPredicates.Default; + + /// + /// Test the mesh for topological consistency. + /// + 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); + } + + /// + /// Check if the mesh is (conforming) Delaunay. + /// + public static bool IsDelaunay(Mesh mesh) + { + return IsDelaunay(mesh, false); + } + + /// + /// Check if that the mesh is (constrained) Delaunay. + /// + public static bool IsConstrainedDelaunay(Mesh mesh) + { + return IsDelaunay(mesh, true); + } + + /// + /// Ensure that the mesh is (constrained) Delaunay. + /// + 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); + } + } +} diff --git a/External/Triangle.NET/Triangle/Meshing/Algorithm/Dwyer.cs b/External/Triangle.NET/Triangle/Meshing/Algorithm/Dwyer.cs new file mode 100644 index 0000000..aa2e7fc --- /dev/null +++ b/External/Triangle.NET/Triangle/Meshing/Algorithm/Dwyer.cs @@ -0,0 +1,694 @@ +// ----------------------------------------------------------------------- +// +// 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/ +// +// ----------------------------------------------------------------------- + +namespace TriangleNet.Meshing.Algorithm +{ + using System; + using System.Collections.Generic; + using TriangleNet.Geometry; + using TriangleNet.Tools; + using TriangleNet.Topology; + + /// + /// Builds a delaunay triangulation using the divide-and-conquer algorithm. + /// + /// + /// 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. + /// + 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; + + /// + /// Form a Delaunay triangulation by the divide-and-conquer method. + /// + /// + /// + /// Sorts the vertices, calls a recursive procedure to triangulate them, and + /// removes the bounding box, setting boundary markers as appropriate. + /// + public IMesh Triangulate(IList 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; + } + + /// + /// Merge two adjacent Delaunay triangulations into a single Delaunay triangulation. + /// + /// Bounding triangles of the left triangulation. + /// Bounding triangles of the left triangulation. + /// Bounding triangles of the right triangulation. + /// Bounding triangles of the right triangulation. + /// + /// + /// 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. + /// + 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(); + } + } + } + + /// + /// Recursively form a Delaunay triangulation by the divide-and-conquer method. + /// + /// + /// + /// + /// + /// + /// + /// 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). + /// + 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); + } + } + + /// + /// Removes ghost triangles. + /// + /// + /// Number of vertices on the hull. + 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; + } + } +} diff --git a/External/Triangle.NET/Triangle/Meshing/Algorithm/Incremental.cs b/External/Triangle.NET/Triangle/Meshing/Algorithm/Incremental.cs new file mode 100644 index 0000000..3686fa4 --- /dev/null +++ b/External/Triangle.NET/Triangle/Meshing/Algorithm/Incremental.cs @@ -0,0 +1,191 @@ +// ----------------------------------------------------------------------- +// +// 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/ +// +// ----------------------------------------------------------------------- + +namespace TriangleNet.Meshing.Algorithm +{ + using System.Collections.Generic; + using TriangleNet.Topology; + using TriangleNet.Geometry; + + /// + /// Builds a delaunay triangulation using the incremental algorithm. + /// + public class Incremental : ITriangulator + { + Mesh mesh; + + /// + /// Form a Delaunay triangulation by incrementally inserting vertices. + /// + /// Returns the number of edges on the convex hull of the + /// triangulation. + public IMesh Triangulate(IList 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; + } + + /// + /// Form an "infinite" bounding triangle to insert vertices into. + /// + /// + /// 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. + /// + 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; + } + + /// + /// Remove the "infinite" bounding triangle, setting boundary markers as appropriate. + /// + /// Returns the number of edges on the convex hull of the triangulation. + /// + /// 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. + /// + 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; + } + } +} diff --git a/External/Triangle.NET/Triangle/Meshing/Algorithm/SweepLine.cs b/External/Triangle.NET/Triangle/Meshing/Algorithm/SweepLine.cs new file mode 100644 index 0000000..bce274d --- /dev/null +++ b/External/Triangle.NET/Triangle/Meshing/Algorithm/SweepLine.cs @@ -0,0 +1,808 @@ +// ----------------------------------------------------------------------- +// +// 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/ +// +// ----------------------------------------------------------------------- + +namespace TriangleNet.Meshing.Algorithm +{ + using System; + using System.Collections.Generic; + using TriangleNet.Topology; + using TriangleNet.Geometry; + using TriangleNet.Tools; + + /// + /// Builds a delaunay triangulation using the sweepline algorithm. + /// + 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 splaynodes; + + public IMesh Triangulate(IList 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(); + 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); + } + } + + /// + /// Removes ghost triangles. + /// + /// + /// Number of vertices on the hull. + 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 + + /// + /// A node in a heap used to store events for the sweepline Delaunay algorithm. + /// + /// + /// 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'. + /// + 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. + } + + /// + /// 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.). + /// + class SweepEventVertex : Vertex + { + public SweepEvent evt; + + public SweepEventVertex(SweepEvent e) + { + evt = e; + } + } + + /// + /// A node in the splay tree. + /// + /// + /// 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. + /// + 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 + } +} diff --git a/External/Triangle.NET/Triangle/Meshing/ConstraintMesher.cs b/External/Triangle.NET/Triangle/Meshing/ConstraintMesher.cs new file mode 100644 index 0000000..ee42aaf --- /dev/null +++ b/External/Triangle.NET/Triangle/Meshing/ConstraintMesher.cs @@ -0,0 +1,1228 @@ +// ----------------------------------------------------------------------- +// +// 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/ +// +// ----------------------------------------------------------------------- + +namespace TriangleNet.Meshing +{ + using System; + using System.Collections.Generic; + using TriangleNet.Geometry; + using TriangleNet.Logging; + using TriangleNet.Meshing.Iterators; + using TriangleNet.Topology; + + internal class ConstraintMesher + { + IPredicates predicates; + + Mesh mesh; + Behavior behavior; + TriangleLocator locator; + + List viri; + + ILog logger; + + public ConstraintMesher(Mesh mesh, Configuration config) + { + this.mesh = mesh; + this.predicates = config.Predicates(); + + this.behavior = mesh.behavior; + this.locator = mesh.locator; + + this.viri = new List(); + + logger = Log.Instance; + } + + + /// + /// Insert segments into the mesh. + /// + /// The polygon. + /// Constraint options. + public void Apply(IPolygon input, ConstraintOptions options) + { + behavior.Poly = input.Segments.Count > 0; + + // Copy constraint options + if (options != null) + { + behavior.ConformingDelaunay = options.ConformingDelaunay; + behavior.Convex = options.Convex; + behavior.NoBisect = options.SegmentSplitting; + + if (behavior.ConformingDelaunay) + { + behavior.Quality = true; + } + } + + //if (input.EdgeMarkers != null) + //{ + // behavior.UseBoundaryMarkers = true; + //} + + behavior.useRegions = input.Regions.Count > 0; + + // 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) + { + // Segments will be introduced next. + mesh.checksegments = true; + + // Insert PSLG segments and/or convex hull segments. + FormSkeleton(input); + } + + if (behavior.Poly && (mesh.triangles.Count > 0)) + { + // Copy holes and regions + mesh.holes.AddRange(input.Holes); + mesh.regions.AddRange(input.Regions); + + // Carve out holes and concavities. + CarveHoles(); + } + } + + /// + /// Find the holes and infect them. Find the area constraints and infect + /// them. Infect the convex hull. Spread the infection and kill triangles. + /// Spread the area constraints. + /// + private void CarveHoles() + { + Otri searchtri = default(Otri); + Vertex searchorg, searchdest; + LocateResult intersect; + + Triangle[] regionTris = null; + + var dummytri = mesh.dummytri; + + if (!mesh.behavior.Convex) + { + // Mark as infected any unprotected triangles on the boundary. + // This is one way by which concavities are created. + InfectHull(); + } + + if (!mesh.behavior.NoHoles) + { + // Infect each triangle in which a hole lies. + foreach (var hole in mesh.holes) + { + // Ignore holes that aren't within the bounds of the mesh. + if (mesh.bounds.Contains(hole)) + { + // Start searching from some triangle on the outer boundary. + searchtri.tri = dummytri; + searchtri.orient = 0; + searchtri.Sym(); + // Ensure that the hole is to the left of this boundary edge; + // otherwise, locate() will falsely report that the hole + // falls within the starting triangle. + searchorg = searchtri.Org(); + searchdest = searchtri.Dest(); + if (predicates.CounterClockwise(searchorg, searchdest, hole) > 0.0) + { + // Find a triangle that contains the hole. + intersect = mesh.locator.Locate(hole, ref searchtri); + if ((intersect != LocateResult.Outside) && (!searchtri.IsInfected())) + { + // Infect the triangle. This is done by marking the triangle + // as infected and including the triangle in the virus pool. + searchtri.Infect(); + viri.Add(searchtri.tri); + } + } + } + } + } + + // Now, we have to find all the regions BEFORE we carve the holes, because locate() won't + // work when the triangulation is no longer convex. (Incidentally, this is the reason why + // regional attributes and area constraints can't be used when refining a preexisting mesh, + // which might not be convex; they can only be used with a freshly triangulated PSLG.) + if (mesh.regions.Count > 0) + { + int i = 0; + + regionTris = new Triangle[mesh.regions.Count]; + + // Find the starting triangle for each region. + foreach (var region in mesh.regions) + { + regionTris[i] = dummytri; + // Ignore region points that aren't within the bounds of the mesh. + if (mesh.bounds.Contains(region.point)) + { + // Start searching from some triangle on the outer boundary. + searchtri.tri = dummytri; + searchtri.orient = 0; + searchtri.Sym(); + // Ensure that the region point is to the left of this boundary + // edge; otherwise, locate() will falsely report that the + // region point falls within the starting triangle. + searchorg = searchtri.Org(); + searchdest = searchtri.Dest(); + if (predicates.CounterClockwise(searchorg, searchdest, region.point) > 0.0) + { + // Find a triangle that contains the region point. + intersect = mesh.locator.Locate(region.point, ref searchtri); + if ((intersect != LocateResult.Outside) && (!searchtri.IsInfected())) + { + // Record the triangle for processing after the + // holes have been carved. + regionTris[i] = searchtri.tri; + regionTris[i].label = region.id; + regionTris[i].area = region.area; + } + } + } + + i++; + } + } + + if (viri.Count > 0) + { + // Carve the holes and concavities. + Plague(); + } + + if (regionTris != null) + { + var iterator = new RegionIterator(mesh); + + for (int i = 0; i < regionTris.Length; i++) + { + if (regionTris[i].id != Mesh.DUMMY) + { + // Make sure the triangle under consideration still exists. + // It may have been eaten by the virus. + if (!Otri.IsDead(regionTris[i])) + { + // Apply one region's attribute and/or area constraint. + iterator.Process(regionTris[i]); + } + } + } + } + + // Free up memory (virus pool should be empty anyway). + viri.Clear(); + } + + /// + /// Create the segments of a triangulation, including PSLG segments and edges + /// on the convex hull. + /// + private void FormSkeleton(IPolygon input) + { + // The segment endpoints. + Vertex p, q; + + mesh.insegments = 0; + + if (behavior.Poly) + { + // If the input vertices are collinear, there is no triangulation, + // so don't try to insert segments. + if (mesh.triangles.Count == 0) + { + return; + } + + // If segments are to be inserted, compute a mapping + // from vertices to triangles. + if (input.Segments.Count > 0) + { + mesh.MakeVertexMap(); + } + + // Read and insert the segments. + foreach (var seg in input.Segments) + { + mesh.insegments++; + + p = seg.GetVertex(0); + q = seg.GetVertex(1); + + if ((p.x == q.x) && (p.y == q.y)) + { + if (Log.Verbose) + { + logger.Warning("Endpoints of segment (IDs " + p.id + "/" + q.id + ") are coincident.", + "Mesh.FormSkeleton()"); + } + } + else + { + InsertSegment(p, q, seg.Label); + } + } + } + + if (behavior.Convex || !behavior.Poly) + { + // Enclose the convex hull with subsegments. + MarkHull(); + } + } + + #region Carving holes + + /// + /// Virally infect all of the triangles of the convex hull that are not + /// protected by subsegments. Where there are subsegments, set boundary + /// markers as appropriate. + /// + private void InfectHull() + { + Otri hulltri = default(Otri); + Otri nexttri = default(Otri); + Otri starttri = default(Otri); + Osub hullsubseg = default(Osub); + Vertex horg, hdest; + + var dummytri = mesh.dummytri; + + // Find a triangle handle on the hull. + hulltri.tri = dummytri; + hulltri.orient = 0; + hulltri.Sym(); + + // Remember where we started so we know when to stop. + hulltri.Copy(ref starttri); + // Go once counterclockwise around the convex hull. + do + { + // Ignore triangles that are already infected. + if (!hulltri.IsInfected()) + { + // Is the triangle protected by a subsegment? + hulltri.Pivot(ref hullsubseg); + if (hullsubseg.seg.hash == Mesh.DUMMY) + { + // The triangle is not protected; infect it. + if (!hulltri.IsInfected()) + { + hulltri.Infect(); + viri.Add(hulltri.tri); + } + } + else + { + // The triangle is protected; set boundary markers if appropriate. + if (hullsubseg.seg.boundary == 0) + { + hullsubseg.seg.boundary = 1; + horg = hulltri.Org(); + hdest = hulltri.Dest(); + if (horg.label == 0) + { + horg.label = 1; + } + if (hdest.label == 0) + { + hdest.label = 1; + } + } + } + } + // To find the next hull edge, go clockwise around the next vertex. + hulltri.Lnext(); + hulltri.Oprev(ref nexttri); + while (nexttri.tri.id != Mesh.DUMMY) + { + nexttri.Copy(ref hulltri); + hulltri.Oprev(ref nexttri); + } + + } while (!hulltri.Equals(starttri)); + } + + /// + /// Spread the virus from all infected triangles to any neighbors not + /// protected by subsegments. Delete all infected triangles. + /// + /// + /// This is the procedure that actually creates holes and concavities. + /// + /// This procedure operates in two phases. The first phase identifies all + /// the triangles that will die, and marks them as infected. They are + /// marked to ensure that each triangle is added to the virus pool only + /// once, so the procedure will terminate. + /// + /// The second phase actually eliminates the infected triangles. It also + /// eliminates orphaned vertices. + /// + void Plague() + { + Otri testtri = default(Otri); + Otri neighbor = default(Otri); + Osub neighborsubseg = default(Osub); + Vertex testvertex; + Vertex norg, ndest; + + var dummysub = mesh.dummysub; + var dummytri = mesh.dummytri; + + bool killorg; + + // Loop through all the infected triangles, spreading the virus to + // their neighbors, then to their neighbors' neighbors. + for (int i = 0; i < viri.Count; i++) + { + // WARNING: Don't use foreach, mesh.viri list may get modified. + + testtri.tri = viri[i]; + // A triangle is marked as infected by messing with one of its pointers + // to subsegments, setting it to an illegal value. Hence, we have to + // temporarily uninfect this triangle so that we can examine its + // adjacent subsegments. + // TODO: Not true in the C# version (so we could skip this). + testtri.Uninfect(); + + // 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); + // Check if the neighbor is nonexistent or already infected. + if ((neighbor.tri.id == Mesh.DUMMY) || neighbor.IsInfected()) + { + if (neighborsubseg.seg.hash != Mesh.DUMMY) + { + // There is a subsegment separating the triangle from its + // neighbor, but both triangles are dying, so the subsegment + // dies too. + mesh.SubsegDealloc(neighborsubseg.seg); + if (neighbor.tri.id != Mesh.DUMMY) + { + // Make sure the subsegment doesn't get deallocated again + // later when the infected neighbor is visited. + neighbor.Uninfect(); + neighbor.SegDissolve(dummysub); + neighbor.Infect(); + } + } + } + else + { // The neighbor exists and is not infected. + if (neighborsubseg.seg.hash == Mesh.DUMMY) + { + // There is no subsegment protecting the neighbor, so + // the neighbor becomes infected. + neighbor.Infect(); + // Ensure that the neighbor's neighbors will be infected. + viri.Add(neighbor.tri); + } + else + { + // The neighbor is protected by a subsegment. + // Remove this triangle from the subsegment. + neighborsubseg.TriDissolve(dummytri); + // The subsegment becomes a boundary. Set markers accordingly. + if (neighborsubseg.seg.boundary == 0) + { + neighborsubseg.seg.boundary = 1; + } + norg = neighbor.Org(); + ndest = neighbor.Dest(); + if (norg.label == 0) + { + norg.label = 1; + } + if (ndest.label == 0) + { + ndest.label = 1; + } + } + } + } + // Remark the triangle as infected, so it doesn't get added to the + // virus pool again. + testtri.Infect(); + } + + foreach (var virus in viri) + { + testtri.tri = virus; + + // Check each of the three corners of the triangle for elimination. + // This is done by walking around each vertex, checking if it is + // still connected to at least one live triangle. + for (testtri.orient = 0; testtri.orient < 3; testtri.orient++) + { + testvertex = testtri.Org(); + // Check if the vertex has already been tested. + if (testvertex != null) + { + killorg = true; + // Mark the corner of the triangle as having been tested. + testtri.SetOrg(null); + // Walk counterclockwise about the vertex. + testtri.Onext(ref neighbor); + // Stop upon reaching a boundary or the starting triangle. + while ((neighbor.tri.id != Mesh.DUMMY) && + (!neighbor.Equals(testtri))) + { + if (neighbor.IsInfected()) + { + // Mark the corner of this triangle as having been tested. + neighbor.SetOrg(null); + } + else + { + // A live triangle. The vertex survives. + killorg = false; + } + // Walk counterclockwise about the vertex. + neighbor.Onext(); + } + // If we reached a boundary, we must walk clockwise as well. + if (neighbor.tri.id == Mesh.DUMMY) + { + // Walk clockwise about the vertex. + testtri.Oprev(ref neighbor); + // Stop upon reaching a boundary. + while (neighbor.tri.id != Mesh.DUMMY) + { + if (neighbor.IsInfected()) + { + // Mark the corner of this triangle as having been tested. + neighbor.SetOrg(null); + } + else + { + // A live triangle. The vertex survives. + killorg = false; + } + // Walk clockwise about the vertex. + neighbor.Oprev(); + } + } + if (killorg) + { + // Deleting vertex + testvertex.type = VertexType.UndeadVertex; + mesh.undeads++; + } + } + } + + // Record changes in the number of boundary edges, and disconnect + // dead triangles from their neighbors. + for (testtri.orient = 0; testtri.orient < 3; testtri.orient++) + { + testtri.Sym(ref neighbor); + if (neighbor.tri.id == Mesh.DUMMY) + { + // There is no neighboring triangle on this edge, so this edge + // is a boundary edge. This triangle is being deleted, so this + // boundary edge is deleted. + mesh.hullsize--; + } + else + { + // Disconnect the triangle from its neighbor. + neighbor.Dissolve(dummytri); + // There is a neighboring triangle on this edge, so this edge + // becomes a boundary edge when this triangle is deleted. + mesh.hullsize++; + } + } + // Return the dead triangle to the pool of triangles. + mesh.TriangleDealloc(testtri.tri); + } + + // Empty the virus pool. + viri.Clear(); + } + + #endregion + + #region Segment insertion + + /// + /// Find the first triangle on the path from one point to another. + /// + /// + /// + /// + /// The return value notes whether the destination or apex of the found + /// triangle is collinear with the two points in question. + /// + /// Finds the triangle that intersects a line segment drawn from the + /// origin of 'searchtri' to the point 'searchpoint', and returns the result + /// in 'searchtri'. The origin of 'searchtri' does not change, even though + /// the triangle returned may differ from the one passed in. This routine + /// is used to find the direction to move in to get from one point to + /// another. + /// + private FindDirectionResult FindDirection(ref Otri searchtri, Vertex searchpoint) + { + Otri checktri = default(Otri); + Vertex startvertex; + Vertex leftvertex, rightvertex; + double leftccw, rightccw; + bool leftflag, rightflag; + + startvertex = searchtri.Org(); + rightvertex = searchtri.Dest(); + leftvertex = searchtri.Apex(); + // Is 'searchpoint' to the left? + leftccw = predicates.CounterClockwise(searchpoint, startvertex, leftvertex); + leftflag = leftccw > 0.0; + // Is 'searchpoint' to the right? + rightccw = predicates.CounterClockwise(startvertex, searchpoint, rightvertex); + rightflag = rightccw > 0.0; + if (leftflag && rightflag) + { + // 'searchtri' faces directly away from 'searchpoint'. We could go left + // or right. Ask whether it's a triangle or a boundary on the left. + searchtri.Onext(ref checktri); + if (checktri.tri.id == Mesh.DUMMY) + { + leftflag = false; + } + else + { + rightflag = false; + } + } + while (leftflag) + { + // Turn left until satisfied. + searchtri.Onext(); + if (searchtri.tri.id == Mesh.DUMMY) + { + logger.Error("Unable to find a triangle on path.", "Mesh.FindDirection().1"); + throw new Exception("Unable to find a triangle on path."); + } + leftvertex = searchtri.Apex(); + rightccw = leftccw; + leftccw = predicates.CounterClockwise(searchpoint, startvertex, leftvertex); + leftflag = leftccw > 0.0; + } + while (rightflag) + { + // Turn right until satisfied. + searchtri.Oprev(); + if (searchtri.tri.id == Mesh.DUMMY) + { + logger.Error("Unable to find a triangle on path.", "Mesh.FindDirection().2"); + throw new Exception("Unable to find a triangle on path."); + } + rightvertex = searchtri.Dest(); + leftccw = rightccw; + rightccw = predicates.CounterClockwise(startvertex, searchpoint, rightvertex); + rightflag = rightccw > 0.0; + } + if (leftccw == 0.0) + { + return FindDirectionResult.Leftcollinear; + } + else if (rightccw == 0.0) + { + return FindDirectionResult.Rightcollinear; + } + else + { + return FindDirectionResult.Within; + } + } + + /// + /// Find the intersection of an existing segment and a segment that is being + /// inserted. Insert a vertex at the intersection, splitting an existing subsegment. + /// + /// + /// + /// + /// + /// The segment being inserted connects the apex of splittri to endpoint2. + /// splitsubseg is the subsegment being split, and MUST adjoin splittri. + /// Hence, endpoints of the subsegment being split are the origin and + /// destination of splittri. + /// + /// On completion, splittri is a handle having the newly inserted + /// intersection point as its origin, and endpoint1 as its destination. + /// + private void SegmentIntersection(ref Otri splittri, ref Osub splitsubseg, Vertex endpoint2) + { + Osub opposubseg = default(Osub); + Vertex endpoint1; + Vertex torg, tdest; + Vertex leftvertex, rightvertex; + Vertex newvertex; + InsertVertexResult success; + + var dummysub = mesh.dummysub; + + double ex, ey; + double tx, ty; + double etx, ety; + double split, denom; + + // Find the other three segment endpoints. + endpoint1 = splittri.Apex(); + torg = splittri.Org(); + tdest = splittri.Dest(); + // Segment intersection formulae; see the Antonio reference. + tx = tdest.x - torg.x; + ty = tdest.y - torg.y; + ex = endpoint2.x - endpoint1.x; + ey = endpoint2.y - endpoint1.y; + etx = torg.x - endpoint2.x; + ety = torg.y - endpoint2.y; + denom = ty * ex - tx * ey; + if (denom == 0.0) + { + logger.Error("Attempt to find intersection of parallel segments.", + "Mesh.SegmentIntersection()"); + throw new Exception("Attempt to find intersection of parallel segments."); + } + split = (ey * etx - ex * ety) / denom; + + // Create the new vertex. + newvertex = new Vertex( + torg.x + split * (tdest.x - torg.x), + torg.y + split * (tdest.y - torg.y), + splitsubseg.seg.boundary +#if USE_ATTRIBS + , mesh.nextras +#endif + ); + + newvertex.hash = mesh.hash_vtx++; + newvertex.id = newvertex.hash; + +#if USE_ATTRIBS + // Interpolate its attributes. + for (int i = 0; i < mesh.nextras; i++) + { + newvertex.attributes[i] = torg.attributes[i] + split * (tdest.attributes[i] - torg.attributes[i]); + } +#endif +#if USE_Z + newvertex.z = torg.z + split * (tdest.z - torg.z); +#endif + + mesh.vertices.Add(newvertex.hash, newvertex); + + // Insert the intersection vertex. This should always succeed. + success = mesh.InsertVertex(newvertex, ref splittri, ref splitsubseg, false, false); + if (success != InsertVertexResult.Successful) + { + logger.Error("Failure to split a segment.", "Mesh.SegmentIntersection()"); + throw new Exception("Failure to split a segment."); + } + // Record a triangle whose origin is the new vertex. + newvertex.tri = splittri; + if (mesh.steinerleft > 0) + { + mesh.steinerleft--; + } + + // Divide the segment into two, and correct the segment endpoints. + splitsubseg.Sym(); + splitsubseg.Pivot(ref opposubseg); + splitsubseg.Dissolve(dummysub); + opposubseg.Dissolve(dummysub); + do + { + splitsubseg.SetSegOrg(newvertex); + splitsubseg.Next(); + } while (splitsubseg.seg.hash != Mesh.DUMMY); + do + { + opposubseg.SetSegOrg(newvertex); + opposubseg.Next(); + } while (opposubseg.seg.hash != Mesh.DUMMY); + + // Inserting the vertex may have caused edge flips. We wish to rediscover + // the edge connecting endpoint1 to the new intersection vertex. + FindDirection(ref splittri, endpoint1); + + rightvertex = splittri.Dest(); + leftvertex = splittri.Apex(); + if ((leftvertex.x == endpoint1.x) && (leftvertex.y == endpoint1.y)) + { + splittri.Onext(); + } + else if ((rightvertex.x != endpoint1.x) || (rightvertex.y != endpoint1.y)) + { + logger.Error("Topological inconsistency after splitting a segment.", "Mesh.SegmentIntersection()"); + throw new Exception("Topological inconsistency after splitting a segment."); + } + // 'splittri' should have destination endpoint1. + } + + /// + /// Scout the first triangle on the path from one endpoint to another, and check + /// for completion (reaching the second endpoint), a collinear vertex, or the + /// intersection of two segments. + /// + /// + /// + /// + /// Returns true if the entire segment is successfully inserted, and false + /// if the job must be finished by ConstrainedEdge(). + /// + /// If the first triangle on the path has the second endpoint as its + /// destination or apex, a subsegment is inserted and the job is done. + /// + /// If the first triangle on the path has a destination or apex that lies on + /// the segment, a subsegment is inserted connecting the first endpoint to + /// the collinear vertex, and the search is continued from the collinear + /// vertex. + /// + /// If the first triangle on the path has a subsegment opposite its origin, + /// then there is a segment that intersects the segment being inserted. + /// Their intersection vertex is inserted, splitting the subsegment. + /// + private bool ScoutSegment(ref Otri searchtri, Vertex endpoint2, int newmark) + { + Otri crosstri = default(Otri); + Osub crosssubseg = default(Osub); + Vertex leftvertex, rightvertex; + FindDirectionResult collinear; + + collinear = FindDirection(ref searchtri, endpoint2); + rightvertex = searchtri.Dest(); + leftvertex = searchtri.Apex(); + if (((leftvertex.x == endpoint2.x) && (leftvertex.y == endpoint2.y)) || + ((rightvertex.x == endpoint2.x) && (rightvertex.y == endpoint2.y))) + { + // The segment is already an edge in the mesh. + if ((leftvertex.x == endpoint2.x) && (leftvertex.y == endpoint2.y)) + { + searchtri.Lprev(); + } + // Insert a subsegment, if there isn't already one there. + mesh.InsertSubseg(ref searchtri, newmark); + return true; + } + else if (collinear == FindDirectionResult.Leftcollinear) + { + // We've collided with a vertex between the segment's endpoints. + // Make the collinear vertex be the triangle's origin. + searchtri.Lprev(); + mesh.InsertSubseg(ref searchtri, newmark); + // Insert the remainder of the segment. + return ScoutSegment(ref searchtri, endpoint2, newmark); + } + else if (collinear == FindDirectionResult.Rightcollinear) + { + // We've collided with a vertex between the segment's endpoints. + mesh.InsertSubseg(ref searchtri, newmark); + // Make the collinear vertex be the triangle's origin. + searchtri.Lnext(); + // Insert the remainder of the segment. + return ScoutSegment(ref searchtri, endpoint2, newmark); + } + else + { + searchtri.Lnext(ref crosstri); + crosstri.Pivot(ref crosssubseg); + // Check for a crossing segment. + if (crosssubseg.seg.hash == Mesh.DUMMY) + { + return false; + } + else + { + // Insert a vertex at the intersection. + SegmentIntersection(ref crosstri, ref crosssubseg, endpoint2); + crosstri.Copy(ref searchtri); + mesh.InsertSubseg(ref searchtri, newmark); + // Insert the remainder of the segment. + return ScoutSegment(ref searchtri, endpoint2, newmark); + } + } + } + + /// + /// Enforce the Delaunay condition at an edge, fanning out recursively from + /// an existing vertex. Pay special attention to stacking inverted triangles. + /// + /// + /// Indicates whether or not fixuptri is to the left of + /// the segment being inserted. (Imagine that the segment is pointing up from + /// endpoint1 to endpoint2.) + /// + /// This is a support routine for inserting segments into a constrained + /// Delaunay triangulation. + /// + /// The origin of fixuptri is treated as if it has just been inserted, and + /// the local Delaunay condition needs to be enforced. It is only enforced + /// in one sector, however, that being the angular range defined by + /// fixuptri. + /// + /// This routine also needs to make decisions regarding the "stacking" of + /// triangles. (Read the description of ConstrainedEdge() below before + /// reading on here, so you understand the algorithm.) If the position of + /// the new vertex (the origin of fixuptri) indicates that the vertex before + /// it on the polygon is a reflex vertex, then "stack" the triangle by + /// doing nothing. (fixuptri is an inverted triangle, which is how stacked + /// triangles are identified.) + /// + /// Otherwise, check whether the vertex before that was a reflex vertex. + /// If so, perform an edge flip, thereby eliminating an inverted triangle + /// (popping it off the stack). The edge flip may result in the creation + /// of a new inverted triangle, depending on whether or not the new vertex + /// is visible to the vertex three edges behind on the polygon. + /// + /// If neither of the two vertices behind the new vertex are reflex + /// vertices, fixuptri and fartri, the triangle opposite it, are not + /// inverted; hence, ensure that the edge between them is locally Delaunay. + /// + private void DelaunayFixup(ref Otri fixuptri, bool leftside) + { + Otri neartri = default(Otri); + Otri fartri = default(Otri); + Osub faredge = default(Osub); + Vertex nearvertex, leftvertex, rightvertex, farvertex; + + fixuptri.Lnext(ref neartri); + neartri.Sym(ref fartri); + // Check if the edge opposite the origin of fixuptri can be flipped. + if (fartri.tri.id == Mesh.DUMMY) + { + return; + } + neartri.Pivot(ref faredge); + if (faredge.seg.hash != Mesh.DUMMY) + { + return; + } + // Find all the relevant vertices. + nearvertex = neartri.Apex(); + leftvertex = neartri.Org(); + rightvertex = neartri.Dest(); + farvertex = fartri.Apex(); + // Check whether the previous polygon vertex is a reflex vertex. + if (leftside) + { + if (predicates.CounterClockwise(nearvertex, leftvertex, farvertex) <= 0.0) + { + // leftvertex is a reflex vertex too. Nothing can + // be done until a convex section is found. + return; + } + } + else + { + if (predicates.CounterClockwise(farvertex, rightvertex, nearvertex) <= 0.0) + { + // rightvertex is a reflex vertex too. Nothing can + // be done until a convex section is found. + return; + } + } + if (predicates.CounterClockwise(rightvertex, leftvertex, farvertex) > 0.0) + { + // fartri is not an inverted triangle, and farvertex is not a reflex + // vertex. As there are no reflex vertices, fixuptri isn't an + // inverted triangle, either. Hence, test the edge between the + // triangles to ensure it is locally Delaunay. + if (predicates.InCircle(leftvertex, farvertex, rightvertex, nearvertex) <= 0.0) + { + return; + } + // Not locally Delaunay; go on to an edge flip. + } + // else fartri is inverted; remove it from the stack by flipping. + mesh.Flip(ref neartri); + fixuptri.Lprev(); // Restore the origin of fixuptri after the flip. + // Recursively process the two triangles that result from the flip. + DelaunayFixup(ref fixuptri, leftside); + DelaunayFixup(ref fartri, leftside); + } + + /// + /// Force a segment into a constrained Delaunay triangulation by deleting the + /// triangles it intersects, and triangulating the polygons that form on each + /// side of it. + /// + /// + /// + /// + /// + /// Generates a single subsegment connecting 'endpoint1' to 'endpoint2'. + /// The triangle 'starttri' has 'endpoint1' as its origin. 'newmark' is the + /// boundary marker of the segment. + /// + /// To insert a segment, every triangle whose interior intersects the + /// segment is deleted. The union of these deleted triangles is a polygon + /// (which is not necessarily monotone, but is close enough), which is + /// divided into two polygons by the new segment. This routine's task is + /// to generate the Delaunay triangulation of these two polygons. + /// + /// You might think of this routine's behavior as a two-step process. The + /// first step is to walk from endpoint1 to endpoint2, flipping each edge + /// encountered. This step creates a fan of edges connected to endpoint1, + /// including the desired edge to endpoint2. The second step enforces the + /// Delaunay condition on each side of the segment in an incremental manner: + /// proceeding along the polygon from endpoint1 to endpoint2 (this is done + /// independently on each side of the segment), each vertex is "enforced" + /// as if it had just been inserted, but affecting only the previous + /// vertices. The result is the same as if the vertices had been inserted + /// in the order they appear on the polygon, so the result is Delaunay. + /// + /// In truth, ConstrainedEdge() interleaves these two steps. The procedure + /// walks from endpoint1 to endpoint2, and each time an edge is encountered + /// and flipped, the newly exposed vertex (at the far end of the flipped + /// edge) is "enforced" upon the previously flipped edges, usually affecting + /// only one side of the polygon (depending upon which side of the segment + /// the vertex falls on). + /// + /// The algorithm is complicated by the need to handle polygons that are not + /// convex. Although the polygon is not necessarily monotone, it can be + /// triangulated in a manner similar to the stack-based algorithms for + /// monotone polygons. For each reflex vertex (local concavity) of the + /// polygon, there will be an inverted triangle formed by one of the edge + /// flips. (An inverted triangle is one with negative area - that is, its + /// vertices are arranged in clockwise order - and is best thought of as a + /// wrinkle in the fabric of the mesh.) Each inverted triangle can be + /// thought of as a reflex vertex pushed on the stack, waiting to be fixed + /// later. + /// + /// A reflex vertex is popped from the stack when a vertex is inserted that + /// is visible to the reflex vertex. (However, if the vertex behind the + /// reflex vertex is not visible to the reflex vertex, a new inverted + /// triangle will take its place on the stack.) These details are handled + /// by the DelaunayFixup() routine above. + /// + private void ConstrainedEdge(ref Otri starttri, Vertex endpoint2, int newmark) + { + Otri fixuptri = default(Otri), fixuptri2 = default(Otri); + Osub crosssubseg = default(Osub); + Vertex endpoint1; + Vertex farvertex; + double area; + bool collision; + bool done; + + endpoint1 = starttri.Org(); + starttri.Lnext(ref fixuptri); + mesh.Flip(ref fixuptri); + // 'collision' indicates whether we have found a vertex directly + // between endpoint1 and endpoint2. + collision = false; + done = false; + do + { + farvertex = fixuptri.Org(); + // 'farvertex' is the extreme point of the polygon we are "digging" + // to get from endpoint1 to endpoint2. + if ((farvertex.x == endpoint2.x) && (farvertex.y == endpoint2.y)) + { + fixuptri.Oprev(ref fixuptri2); + // Enforce the Delaunay condition around endpoint2. + DelaunayFixup(ref fixuptri, false); + DelaunayFixup(ref fixuptri2, true); + done = true; + } + else + { + // Check whether farvertex is to the left or right of the segment being + // inserted, to decide which edge of fixuptri to dig through next. + area = predicates.CounterClockwise(endpoint1, endpoint2, farvertex); + if (area == 0.0) + { + // We've collided with a vertex between endpoint1 and endpoint2. + collision = true; + fixuptri.Oprev(ref fixuptri2); + // Enforce the Delaunay condition around farvertex. + DelaunayFixup(ref fixuptri, false); + DelaunayFixup(ref fixuptri2, true); + done = true; + } + else + { + if (area > 0.0) + { + // farvertex is to the left of the segment. + fixuptri.Oprev(ref fixuptri2); + // Enforce the Delaunay condition around farvertex, on the + // left side of the segment only. + DelaunayFixup(ref fixuptri2, true); + // Flip the edge that crosses the segment. After the edge is + // flipped, one of its endpoints is the fan vertex, and the + // destination of fixuptri is the fan vertex. + fixuptri.Lprev(); + } + else + { + // farvertex is to the right of the segment. + DelaunayFixup(ref fixuptri, false); + // Flip the edge that crosses the segment. After the edge is + // flipped, one of its endpoints is the fan vertex, and the + // destination of fixuptri is the fan vertex. + fixuptri.Oprev(); + } + // Check for two intersecting segments. + fixuptri.Pivot(ref crosssubseg); + if (crosssubseg.seg.hash == Mesh.DUMMY) + { + mesh.Flip(ref fixuptri); // May create inverted triangle at left. + } + else + { + // We've collided with a segment between endpoint1 and endpoint2. + collision = true; + // Insert a vertex at the intersection. + SegmentIntersection(ref fixuptri, ref crosssubseg, endpoint2); + done = true; + } + } + } + } while (!done); + // Insert a subsegment to make the segment permanent. + mesh.InsertSubseg(ref fixuptri, newmark); + // If there was a collision with an interceding vertex, install another + // segment connecting that vertex with endpoint2. + if (collision) + { + // Insert the remainder of the segment. + if (!ScoutSegment(ref fixuptri, endpoint2, newmark)) + { + ConstrainedEdge(ref fixuptri, endpoint2, newmark); + } + } + } + + /// + /// Insert a PSLG segment into a triangulation. + /// + /// + /// + /// + private void InsertSegment(Vertex endpoint1, Vertex endpoint2, int newmark) + { + Otri searchtri1 = default(Otri), searchtri2 = default(Otri); + Vertex checkvertex = null; + + var dummytri = mesh.dummytri; + + // Find a triangle whose origin is the segment's first endpoint. + searchtri1 = endpoint1.tri; + if (searchtri1.tri != null) + { + checkvertex = searchtri1.Org(); + } + + if (checkvertex != endpoint1) + { + // Find a boundary triangle to search from. + searchtri1.tri = dummytri; + searchtri1.orient = 0; + searchtri1.Sym(); + // Search for the segment's first endpoint by point location. + if (locator.Locate(endpoint1, ref searchtri1) != LocateResult.OnVertex) + { + logger.Error("Unable to locate PSLG vertex in triangulation.", "Mesh.InsertSegment().1"); + throw new Exception("Unable to locate PSLG vertex in triangulation."); + } + } + // Remember this triangle to improve subsequent point location. + locator.Update(ref searchtri1); + + // Scout the beginnings of a path from the first endpoint + // toward the second. + if (ScoutSegment(ref searchtri1, endpoint2, newmark)) + { + // The segment was easily inserted. + return; + } + // The first endpoint may have changed if a collision with an intervening + // vertex on the segment occurred. + endpoint1 = searchtri1.Org(); + + // Find a triangle whose origin is the segment's second endpoint. + checkvertex = null; + searchtri2 = endpoint2.tri; + if (searchtri2.tri != null) + { + checkvertex = searchtri2.Org(); + } + if (checkvertex != endpoint2) + { + // Find a boundary triangle to search from. + searchtri2.tri = dummytri; + searchtri2.orient = 0; + searchtri2.Sym(); + // Search for the segment's second endpoint by point location. + if (locator.Locate(endpoint2, ref searchtri2) != LocateResult.OnVertex) + { + logger.Error("Unable to locate PSLG vertex in triangulation.", "Mesh.InsertSegment().2"); + throw new Exception("Unable to locate PSLG vertex in triangulation."); + } + } + // Remember this triangle to improve subsequent point location. + locator.Update(ref searchtri2); + // Scout the beginnings of a path from the second endpoint + // toward the first. + if (ScoutSegment(ref searchtri2, endpoint1, newmark)) + { + // The segment was easily inserted. + return; + } + // The second endpoint may have changed if a collision with an intervening + // vertex on the segment occurred. + endpoint2 = searchtri2.Org(); + + // Insert the segment directly into the triangulation. + ConstrainedEdge(ref searchtri1, endpoint2, newmark); + } + + /// + /// Cover the convex hull of a triangulation with subsegments. + /// + private void MarkHull() + { + Otri hulltri = default(Otri); + Otri nexttri = default(Otri); + Otri starttri = default(Otri); + + // Find a triangle handle on the hull. + hulltri.tri = mesh.dummytri; + hulltri.orient = 0; + hulltri.Sym(); + // Remember where we started so we know when to stop. + hulltri.Copy(ref starttri); + // Go once counterclockwise around the convex hull. + do + { + // Create a subsegment if there isn't already one here. + mesh.InsertSubseg(ref hulltri, 1); + // To find the next hull edge, go clockwise around the next vertex. + hulltri.Lnext(); + hulltri.Oprev(ref nexttri); + while (nexttri.tri.id != Mesh.DUMMY) + { + nexttri.Copy(ref hulltri); + hulltri.Oprev(ref nexttri); + } + } while (!hulltri.Equals(starttri)); + } + + #endregion + } +} diff --git a/External/Triangle.NET/Triangle/Meshing/ConstraintOptions.cs b/External/Triangle.NET/Triangle/Meshing/ConstraintOptions.cs new file mode 100644 index 0000000..4325b3a --- /dev/null +++ b/External/Triangle.NET/Triangle/Meshing/ConstraintOptions.cs @@ -0,0 +1,40 @@ + +namespace TriangleNet.Meshing +{ + /// + /// Mesh constraint options for polygon triangulation. + /// + public class ConstraintOptions + { + // TODO: remove ConstraintOptions.UseRegions + + /// + /// Gets or sets a value indicating whether to use regions. + /// + [System.Obsolete("Not used anywhere, will be removed in beta 4.")] + public bool UseRegions { get; set; } + + /// + /// Gets or sets a value indicating whether to create a Conforming + /// Delaunay triangulation. + /// + public bool ConformingDelaunay { get; set; } + + /// + /// Gets or sets a value indicating whether to enclose the convex + /// hull with segments. + /// + public bool Convex { get; set; } + + /// + /// Gets or sets a flag indicating whether to suppress boundary + /// segment splitting. + /// + /// + /// 0 = split segments (default) + /// 1 = no new vertices on the boundary + /// 2 = prevent all segment splitting, including internal boundaries + /// + public int SegmentSplitting { get; set; } + } +} diff --git a/External/Triangle.NET/Triangle/Meshing/Converter.cs b/External/Triangle.NET/Triangle/Meshing/Converter.cs new file mode 100644 index 0000000..e8cba91 --- /dev/null +++ b/External/Triangle.NET/Triangle/Meshing/Converter.cs @@ -0,0 +1,487 @@ +// ----------------------------------------------------------------------- +// +// 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/ +// +// ----------------------------------------------------------------------- + +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; + + /// + /// The Converter class provides methods for mesh reconstruction and conversion. + /// + public static class Converter + { + #region Triangle mesh conversion + + /// + /// Reconstruct a triangulation from its raw data representation. + /// + public static Mesh ToMesh(Polygon polygon, IList triangles) + { + return ToMesh(polygon, triangles.ToArray()); + } + + /// + /// Reconstruct a triangulation from its raw data representation. + /// + 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; + } + + /// + /// Finds the adjacencies between triangles by forming a stack of triangles for + /// each vertex. Each triangle is on three different stacks simultaneously. + /// + private static List[] 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[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(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; + } + + /// + /// Finds the adjacencies between triangles and subsegments. + /// + private static void SetSegments(Mesh mesh, Polygon polygon, List[] 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[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(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(); + + 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 + } +} diff --git a/External/Triangle.NET/Triangle/Meshing/Data/BadSubseg.cs b/External/Triangle.NET/Triangle/Meshing/Data/BadSubseg.cs new file mode 100644 index 0000000..85f23a2 --- /dev/null +++ b/External/Triangle.NET/Triangle/Meshing/Data/BadSubseg.cs @@ -0,0 +1,36 @@ +// ----------------------------------------------------------------------- +// +// 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/ +// +// ----------------------------------------------------------------------- + +namespace TriangleNet.Meshing.Data +{ + using System; + using TriangleNet.Geometry; + using TriangleNet.Topology; + + /// + /// A queue used to store encroached subsegments. + /// + /// + /// Each subsegment's vertices are stored so that we can check whether a + /// subsegment is still the same. + /// + 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); + } + } +} diff --git a/External/Triangle.NET/Triangle/Meshing/Data/BadTriQueue.cs b/External/Triangle.NET/Triangle/Meshing/Data/BadTriQueue.cs new file mode 100644 index 0000000..87d5d18 --- /dev/null +++ b/External/Triangle.NET/Triangle/Meshing/Data/BadTriQueue.cs @@ -0,0 +1,194 @@ +// ----------------------------------------------------------------------- +// +// 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/ +// +// ----------------------------------------------------------------------- + +namespace TriangleNet.Meshing.Data +{ + using TriangleNet.Geometry; + using TriangleNet.Topology; + + /// + /// A (priority) queue for bad triangles. + /// + /// + // 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. + /// + 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; + } + + /// + /// Add a bad triangle data structure to the end of a queue. + /// + /// The bad triangle to enqueue. + 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; + } + + /// + /// Add a bad triangle to the end of a queue. + /// + /// + /// + /// + /// + /// + 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); + } + + /// + /// Remove a triangle from the front of the queue. + /// + /// + 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; + } + } +} diff --git a/External/Triangle.NET/Triangle/Meshing/Data/BadTriangle.cs b/External/Triangle.NET/Triangle/Meshing/Data/BadTriangle.cs new file mode 100644 index 0000000..40cc398 --- /dev/null +++ b/External/Triangle.NET/Triangle/Meshing/Data/BadTriangle.cs @@ -0,0 +1,34 @@ +// ----------------------------------------------------------------------- +// +// 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/ +// +// ----------------------------------------------------------------------- + +namespace TriangleNet.Meshing.Data +{ + using System; + using TriangleNet.Geometry; + using TriangleNet.Topology; + + /// + /// A queue used to store bad triangles. + /// + /// + /// 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. + /// + 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); + } + } +} diff --git a/External/Triangle.NET/Triangle/Meshing/GenericMesher.cs b/External/Triangle.NET/Triangle/Meshing/GenericMesher.cs new file mode 100644 index 0000000..75c6dfc --- /dev/null +++ b/External/Triangle.NET/Triangle/Meshing/GenericMesher.cs @@ -0,0 +1,233 @@ +// ----------------------------------------------------------------------- +// +// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/ +// +// ----------------------------------------------------------------------- + +namespace TriangleNet.Meshing +{ + using System; + using System.Collections.Generic; + using TriangleNet.Geometry; + using TriangleNet.IO; + using TriangleNet.Meshing.Algorithm; + + /// + /// Create meshes of point sets or polygons. + /// + 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; + } + + /// + public IMesh Triangulate(IList points) + { + return triangulator.Triangulate(points, config); + } + + /// + public IMesh Triangulate(IPolygon polygon) + { + return Triangulate(polygon, null, null); + } + + /// + public IMesh Triangulate(IPolygon polygon, ConstraintOptions options) + { + return Triangulate(polygon, options, null); + } + + /// + public IMesh Triangulate(IPolygon polygon, QualityOptions quality) + { + return Triangulate(polygon, null, quality); + } + + /// + 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; + } + + /// + /// Generates a structured mesh with bounds [0, 0, width, height]. + /// + /// Width of the mesh (must be > 0). + /// Height of the mesh (must be > 0). + /// Number of segments in x direction. + /// Number of segments in y direction. + /// Mesh + 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); + } + + /// + /// Generates a structured mesh. + /// + /// Bounds of the mesh. + /// Number of segments in x direction. + /// Number of segments in y direction. + /// Mesh + 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); + } + } +} diff --git a/External/Triangle.NET/Triangle/Meshing/IConstraintMesher.cs b/External/Triangle.NET/Triangle/Meshing/IConstraintMesher.cs new file mode 100644 index 0000000..1336417 --- /dev/null +++ b/External/Triangle.NET/Triangle/Meshing/IConstraintMesher.cs @@ -0,0 +1,26 @@ + +namespace TriangleNet.Meshing +{ + using TriangleNet.Geometry; + + /// + /// Interface for polygon triangulation. + /// + public interface IConstraintMesher + { + /// + /// Triangulates a polygon. + /// + /// The polygon. + /// Mesh + IMesh Triangulate(IPolygon polygon); + + /// + /// Triangulates a polygon, applying constraint options. + /// + /// The polygon. + /// Constraint options. + /// Mesh + IMesh Triangulate(IPolygon polygon, ConstraintOptions options); + } +} diff --git a/External/Triangle.NET/Triangle/Meshing/IMesh.cs b/External/Triangle.NET/Triangle/Meshing/IMesh.cs new file mode 100644 index 0000000..f5fb484 --- /dev/null +++ b/External/Triangle.NET/Triangle/Meshing/IMesh.cs @@ -0,0 +1,57 @@ + +namespace TriangleNet.Meshing +{ + using System.Collections.Generic; + using TriangleNet.Topology; + using TriangleNet.Geometry; + + /// + /// Mesh interface. + /// + public interface IMesh + { + /// + /// Gets the vertices of the mesh. + /// + ICollection Vertices { get; } + + /// + /// Gets the edges of the mesh. + /// + IEnumerable Edges { get; } + + /// + /// Gets the segments (constraint edges) of the mesh. + /// + ICollection Segments { get; } + + /// + /// Gets the triangles of the mesh. + /// + ICollection Triangles { get; } + + /// + /// Gets the holes of the mesh. + /// + IList Holes { get; } + + /// + /// Gets the bounds of the mesh. + /// + Rectangle Bounds { get; } + + /// + /// Renumber mesh vertices and triangles. + /// + void Renumber(); + + /// + /// Refine the mesh. + /// + /// The quality constraints. + /// + /// A value indicating, if the refined mesh should be Conforming Delaunay. + /// + void Refine(QualityOptions quality, bool delaunay); + } +} diff --git a/External/Triangle.NET/Triangle/Meshing/IQualityMesher.cs b/External/Triangle.NET/Triangle/Meshing/IQualityMesher.cs new file mode 100644 index 0000000..8d8a1ff --- /dev/null +++ b/External/Triangle.NET/Triangle/Meshing/IQualityMesher.cs @@ -0,0 +1,28 @@ + +namespace TriangleNet.Meshing +{ + using TriangleNet.Geometry; + + /// + /// Interface for polygon triangulation with quality constraints. + /// + public interface IQualityMesher + { + /// + /// Triangulates a polygon, applying quality options. + /// + /// The polygon. + /// Quality options. + /// Mesh + IMesh Triangulate(IPolygon polygon, QualityOptions quality); + + /// + /// Triangulates a polygon, applying quality and constraint options. + /// + /// The polygon. + /// Constraint options. + /// Quality options. + /// Mesh + IMesh Triangulate(IPolygon polygon, ConstraintOptions options, QualityOptions quality); + } +} diff --git a/External/Triangle.NET/Triangle/Meshing/ITriangulator.cs b/External/Triangle.NET/Triangle/Meshing/ITriangulator.cs new file mode 100644 index 0000000..5bc1541 --- /dev/null +++ b/External/Triangle.NET/Triangle/Meshing/ITriangulator.cs @@ -0,0 +1,25 @@ +// ----------------------------------------------------------------------- +// +// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/ +// +// ----------------------------------------------------------------------- + +namespace TriangleNet.Meshing +{ + using System.Collections.Generic; + using TriangleNet.Geometry; + + /// + /// Interface for point set triangulation. + /// + public interface ITriangulator + { + /// + /// Triangulates a point set. + /// + /// Collection of points. + /// + /// Mesh + IMesh Triangulate(IList points, Configuration config); + } +} diff --git a/External/Triangle.NET/Triangle/Meshing/Iterators/EdgeIterator.cs b/External/Triangle.NET/Triangle/Meshing/Iterators/EdgeIterator.cs new file mode 100644 index 0000000..1b247f4 --- /dev/null +++ b/External/Triangle.NET/Triangle/Meshing/Iterators/EdgeIterator.cs @@ -0,0 +1,101 @@ +// ----------------------------------------------------------------------- +// +// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/ +// +// ----------------------------------------------------------------------- + +namespace TriangleNet.Meshing.Iterators +{ + using System.Collections.Generic; + using TriangleNet.Topology; + using TriangleNet.Geometry; + + /// + /// Enumerates the edges of a triangulation. + /// + public class EdgeIterator : IEnumerator + { + IEnumerator triangles; + Otri tri = default(Otri); + Otri neighbor = default(Otri); + Osub sub = default(Osub); + Edge current; + Vertex p1, p2; + + /// + /// Initializes a new instance of the class. + /// + 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(); + } + } +} diff --git a/External/Triangle.NET/Triangle/Meshing/Iterators/RegionIterator.cs b/External/Triangle.NET/Triangle/Meshing/Iterators/RegionIterator.cs new file mode 100644 index 0000000..0ae494b --- /dev/null +++ b/External/Triangle.NET/Triangle/Meshing/Iterators/RegionIterator.cs @@ -0,0 +1,135 @@ +// ----------------------------------------------------------------------- +// +// Original Matlab code by John Burkardt, Florida State University +// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/ +// +// ----------------------------------------------------------------------- + +namespace TriangleNet.Meshing.Iterators +{ + using System; + using System.Collections.Generic; + using TriangleNet.Topology; + + /// + /// Iterates the region a given triangle belongs to and applies an action + /// to each connected trianlge in that region. + /// + /// + /// The default action is to set the region id and area constraint. + /// + public class RegionIterator + { + List region; + + public RegionIterator(Mesh mesh) + { + this.region = new List(); + } + + /// + /// Set the region attribute of all trianlges connected to given triangle. + /// + /// The triangle seed. + /// If non-zero, process all triangles of the + /// region that is enclosed by segments with given boundary label. + 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); + } + + /// + /// Process all trianlges connected to given triangle and apply given action. + /// + /// The seeding triangle. + /// The action to apply to each triangle. + /// If non-zero, process all triangles of the + /// region that is enclosed by segments with given boundary label. + public void Process(Triangle triangle, Action 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(); + } + + /// + /// Apply given action to each triangle of selected region. + /// + /// + /// + void ProcessRegion(Action action, Func 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(); + } + } +} diff --git a/External/Triangle.NET/Triangle/Meshing/Iterators/VertexCirculator.cs b/External/Triangle.NET/Triangle/Meshing/Iterators/VertexCirculator.cs new file mode 100644 index 0000000..44821a6 --- /dev/null +++ b/External/Triangle.NET/Triangle/Meshing/Iterators/VertexCirculator.cs @@ -0,0 +1,100 @@ + +namespace TriangleNet.Meshing.Iterators +{ + using System.Collections.Generic; + using TriangleNet.Geometry; + using TriangleNet.Topology; + + public class VertexCirculator + { + List cache = new List(); + + public VertexCirculator(Mesh mesh) + { + mesh.MakeVertexMap(); + } + + /// + /// Enumerate all vertices adjacent to given vertex. + /// + /// The center vertex. + /// + public IEnumerable EnumerateVertices(Vertex vertex) + { + BuildCache(vertex, true); + + foreach (var item in cache) + { + yield return item.Dest(); + } + } + + /// + /// Enumerate all triangles adjacent to given vertex. + /// + /// The center vertex. + /// + public IEnumerable 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; + } + } + } + } + } +} diff --git a/External/Triangle.NET/Triangle/Meshing/QualityMesher.cs b/External/Triangle.NET/Triangle/Meshing/QualityMesher.cs new file mode 100644 index 0000000..edeef48 --- /dev/null +++ b/External/Triangle.NET/Triangle/Meshing/QualityMesher.cs @@ -0,0 +1,898 @@ +// ----------------------------------------------------------------------- +// +// 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/ +// +// ----------------------------------------------------------------------- + +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; + + /// + /// Provides methods for mesh quality enforcement and testing. + /// + class QualityMesher + { + IPredicates predicates; + + Queue badsubsegs; + BadTriQueue queue; + Mesh mesh; + Behavior behavior; + + NewLocation newLocation; + + ILog 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(); + queue = new BadTriQueue(); + + this.mesh = mesh; + this.predicates = config.Predicates(); + + this.behavior = mesh.behavior; + + newLocation = new NewLocation(mesh, predicates); + + newvertex_tri = new Triangle(); + } + + /// + /// Apply quality constraints to a mesh. + /// + /// The quality constraints. + /// A value indicating, if the refined mesh should be Conforming Delaunay. + 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(); + } + } + + /// + /// Add a bad subsegment to the queue. + /// + /// Bad subsegment. + public void AddBadSubseg(BadSubseg badseg) + { + badsubsegs.Enqueue(badseg); + } + + #region Check + + /// + /// Check a subsegment to see if it is encroached; add it to the list if it is. + /// + /// The subsegment to check. + /// Returns a nonzero value if the subsegment is encroached. + /// + /// 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. + /// + 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; + } + + /// + /// Test a triangle for quality and size. + /// + /// Triangle to check. + /// + /// 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. + /// + 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 + + /// + /// Traverse the entire list of subsegments, and check each to see if it + /// is encroached. If so, add it to the list. + /// + 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); + } + } + + /// + /// Split all the encroached subsegments. + /// + /// A flag that specifies whether one should take + /// note of new bad triangles that result from inserting vertices to repair + /// encroached subsegments. + /// + /// 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. + /// + 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; + } + } + + /// + /// Test every triangle in the mesh for quality measures. + /// + 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); + } + } + + /// + /// Inserts a vertex at the circumcenter of a triangle. Deletes + /// the newly inserted vertex if it encroaches upon a segment. + /// + /// + 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."); + } + } + } + + /// + /// Remove all the encroached subsegments and bad triangles from the triangulation. + /// + 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 + } +} diff --git a/External/Triangle.NET/Triangle/Meshing/QualityOptions.cs b/External/Triangle.NET/Triangle/Meshing/QualityOptions.cs new file mode 100644 index 0000000..bf9f8b8 --- /dev/null +++ b/External/Triangle.NET/Triangle/Meshing/QualityOptions.cs @@ -0,0 +1,55 @@ + +namespace TriangleNet.Meshing +{ + using System; + using TriangleNet.Geometry; + + /// + /// Mesh constraint options for quality triangulation. + /// + public class QualityOptions + { + /// + /// Gets or sets a maximum angle constraint. + /// + public double MaximumAngle { get; set; } + + /// + /// Gets or sets a minimum angle constraint. + /// + public double MinimumAngle { get; set; } + + /// + /// Gets or sets a maximum triangle area constraint. + /// + public double MaximumArea { get; set; } + + /// + /// Gets or sets a user-defined triangle constraint. + /// + /// + /// 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. + /// + public Func UserTest { get; set; } + + /// + /// Gets or sets an area constraint per triangle. + /// + /// + /// If this flag is set to true, the value will + /// be used to check if a triangle needs refinement. + /// + public bool VariableArea { get; set; } + + /// + /// Gets or sets the maximum number of Steiner points to be inserted into the mesh. + /// + /// + /// If the value is 0 (default), an unknown number of Steiner points may be inserted + /// to meet the other quality constraints. + /// + public int SteinerPoints { get; set; } + } +} diff --git a/External/Triangle.NET/Triangle/NewLocation.cs b/External/Triangle.NET/Triangle/NewLocation.cs new file mode 100644 index 0000000..4755b21 --- /dev/null +++ b/External/Triangle.NET/Triangle/NewLocation.cs @@ -0,0 +1,4115 @@ +// ----------------------------------------------------------------------- +// +// Original code by Hale Erten and Alper Ãœngör, http://www.cise.ufl.edu/~ungor/aCute/index.html +// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/ +// +// ----------------------------------------------------------------------- + +namespace TriangleNet +{ + using System; + using TriangleNet.Topology; + using TriangleNet.Geometry; + using TriangleNet.Tools; + + /// + /// Find new Steiner point locations. + /// + /// + /// http://www.cise.ufl.edu/~ungor/aCute/index.html + /// + class NewLocation + { + const double EPS = 1e-50; + + IPredicates predicates; + + Mesh mesh; + Behavior behavior; + + // Work arrays for wegde intersection + double[] petalx = new double[20]; + double[] petaly = new double[20]; + double[] petalr = new double[20]; + double[] wedges = new double[500]; + double[] initialConvexPoly = new double[500]; + + // Work arrays for smoothing + double[] points_p = new double[500]; + double[] points_q = new double[500]; + double[] points_r = new double[500]; + + // Work arrays for convex polygon split + double[] poly1 = new double[100]; + double[] poly2 = new double[100]; + double[][] polys = new double[3][]; + + public NewLocation(Mesh mesh, IPredicates predicates) + { + this.mesh = mesh; + this.predicates = predicates; + + this.behavior = mesh.behavior; + } + + /// + /// Find a new location for a Steiner point. + /// + /// + /// + /// + /// + /// + /// + /// + /// + public Point FindLocation(Vertex org, Vertex dest, Vertex apex, + ref double xi, ref double eta, bool offcenter, Otri badotri) + { + // Based on using -U switch, call the corresponding function + if (behavior.MaxAngle == 0.0) + { + // Disable the "no max angle" code. It may return weired vertex locations. + return FindNewLocationWithoutMaxAngle(org, dest, apex, ref xi, ref eta, true, badotri); + } + + // With max angle + return FindNewLocation(org, dest, apex, ref xi, ref eta, true, badotri); + } + + /// + /// Find a new location for a Steiner point. + /// + /// + /// + /// + /// + /// + /// + /// + /// + private Point FindNewLocationWithoutMaxAngle(Vertex torg, Vertex tdest, Vertex tapex, + ref double xi, ref double eta, bool offcenter, Otri badotri) + { + double offconstant = behavior.offconstant; + + // for calculating the distances of the edges + double xdo, ydo, xao, yao, xda, yda; + double dodist, aodist, dadist; + // for exact calculation + double denominator; + double dx, dy, dxoff, dyoff; + + ////////////////////////////// HALE'S VARIABLES ////////////////////////////// + // keeps the difference of coordinates edge + double xShortestEdge = 0, yShortestEdge = 0; + + // keeps the square of edge lengths + double shortestEdgeDist = 0, middleEdgeDist = 0, longestEdgeDist = 0; + + // keeps the vertices according to the angle incident to that vertex in a triangle + Point smallestAngleCorner, middleAngleCorner, largestAngleCorner; + + // keeps the type of orientation if the triangle + int orientation = 0; + // keeps the coordinates of circumcenter of itself and neighbor triangle circumcenter + Point myCircumcenter, neighborCircumcenter; + + // keeps if bad triangle is almost good or not + int almostGood = 0; + // keeps the cosine of the largest angle + double cosMaxAngle; + bool isObtuse; // 1: obtuse 0: nonobtuse + // keeps the radius of petal + double petalRadius; + // for calculating petal center + double xPetalCtr_1, yPetalCtr_1, xPetalCtr_2, yPetalCtr_2, xPetalCtr, yPetalCtr, xMidOfShortestEdge, yMidOfShortestEdge; + double dxcenter1, dycenter1, dxcenter2, dycenter2; + // for finding neighbor + Otri neighborotri = default(Otri); + double[] thirdPoint = new double[2]; + //int neighborNotFound = -1; + bool neighborNotFound; + // for keeping the vertices of the neighbor triangle + Vertex neighborvertex_1; + Vertex neighborvertex_2; + Vertex neighborvertex_3; + // dummy variables + double xi_tmp = 0, eta_tmp = 0; + //vertex thirdVertex; + // for petal intersection + double vector_x, vector_y, xMidOfLongestEdge, yMidOfLongestEdge, inter_x, inter_y; + double[] p = new double[5], voronoiOrInter = new double[4]; + bool isCorrect; + + // for vector calculations in perturbation + double ax, ay, d; + double pertConst = 0.06; // perturbation constant + + double lengthConst = 1; // used at comparing circumcenter's distance to proposed point's distance + double justAcute = 1; // used for making the program working for one direction only + // for smoothing + int relocated = 0;// used to differentiate between calling the deletevertex and just proposing a steiner point + double[] newloc = new double[2]; // new location suggested by smoothing + double origin_x = 0, origin_y = 0; // for keeping torg safe + Otri delotri; // keeping the original orientation for relocation process + // keeps the first and second direction suggested points + double dxFirstSuggestion, dyFirstSuggestion, dxSecondSuggestion, dySecondSuggestion; + // second direction variables + double xMidOfMiddleEdge, yMidOfMiddleEdge; + ////////////////////////////// END OF HALE'S VARIABLES ////////////////////////////// + + Statistic.CircumcenterCount++; + + // Compute the circumcenter of the triangle. + xdo = tdest.x - torg.x; + ydo = tdest.y - torg.y; + xao = tapex.x - torg.x; + yao = tapex.y - torg.y; + xda = tapex.x - tdest.x; + yda = tapex.y - tdest.y; + // keeps the square of the distances + dodist = xdo * xdo + ydo * ydo; + aodist = xao * xao + yao * yao; + dadist = (tdest.x - tapex.x) * (tdest.x - tapex.x) + + (tdest.y - tapex.y) * (tdest.y - tapex.y); + // checking if the user wanted exact arithmetic or not + if (Behavior.NoExact) + { + denominator = 0.5 / (xdo * yao - xao * ydo); + } + else + { + // Use the counterclockwise() routine to ensure a positive (and + // reasonably accurate) result, avoiding any possibility of + // division by zero. + denominator = 0.5 / predicates.CounterClockwise(tdest, tapex, torg); + // Don't count the above as an orientation test. + Statistic.CounterClockwiseCount--; + } + // calculate the circumcenter in terms of distance to origin point + dx = (yao * dodist - ydo * aodist) * denominator; + dy = (xdo * aodist - xao * dodist) * denominator; + // for debugging and for keeping circumcenter to use later + // coordinate value of the circumcenter + myCircumcenter = new Point(torg.x + dx, torg.y + dy); + + delotri = badotri; // save for later + ///////////////// FINDING THE ORIENTATION OF TRIANGLE ////////////////// + // Find the (squared) length of the triangle's shortest edge. This + // serves as a conservative estimate of the insertion radius of the + // circumcenter's parent. The estimate is used to ensure that + // the algorithm terminates even if very small angles appear in + // the input PSLG. + // find the orientation of the triangle, basically shortest and longest edges + orientation = LongestShortestEdge(aodist, dadist, dodist); + //printf("org: (%f,%f), dest: (%f,%f), apex: (%f,%f)\n",torg[0],torg[1],tdest[0],tdest[1],tapex[0],tapex[1]); + ///////////////////////////////////////////////////////////////////////////////////////////// + // 123: shortest: aodist // 213: shortest: dadist // 312: shortest: dodist // + // middle: dadist // middle: aodist // middle: aodist // + // longest: dodist // longest: dodist // longest: dadist // + // 132: shortest: aodist // 231: shortest: dadist // 321: shortest: dodist // + // middle: dodist // middle: dodist // middle: dadist // + // longest: dadist // longest: aodist // longest: aodist // + ///////////////////////////////////////////////////////////////////////////////////////////// + + switch (orientation) + { + case 123: // assign necessary information + /// smallest angle corner: dest + /// largest angle corner: apex + xShortestEdge = xao; yShortestEdge = yao; + + shortestEdgeDist = aodist; + middleEdgeDist = dadist; + longestEdgeDist = dodist; + + smallestAngleCorner = tdest; + middleAngleCorner = torg; + largestAngleCorner = tapex; + break; + + case 132: // assign necessary information + /// smallest angle corner: dest + /// largest angle corner: org + xShortestEdge = xao; yShortestEdge = yao; + + shortestEdgeDist = aodist; + middleEdgeDist = dodist; + longestEdgeDist = dadist; + + smallestAngleCorner = tdest; + middleAngleCorner = tapex; + largestAngleCorner = torg; + + break; + case 213: // assign necessary information + /// smallest angle corner: org + /// largest angle corner: apex + xShortestEdge = xda; yShortestEdge = yda; + + shortestEdgeDist = dadist; + middleEdgeDist = aodist; + longestEdgeDist = dodist; + + smallestAngleCorner = torg; + middleAngleCorner = tdest; + largestAngleCorner = tapex; + break; + case 231: // assign necessary information + /// smallest angle corner: org + /// largest angle corner: dest + xShortestEdge = xda; yShortestEdge = yda; + + shortestEdgeDist = dadist; + middleEdgeDist = dodist; + longestEdgeDist = aodist; + + smallestAngleCorner = torg; + middleAngleCorner = tapex; + largestAngleCorner = tdest; + break; + case 312: // assign necessary information + /// smallest angle corner: apex + /// largest angle corner: org + xShortestEdge = xdo; yShortestEdge = ydo; + + shortestEdgeDist = dodist; + middleEdgeDist = aodist; + longestEdgeDist = dadist; + + smallestAngleCorner = tapex; + middleAngleCorner = tdest; + largestAngleCorner = torg; + break; + case 321: // assign necessary information + default: // TODO: is this safe? + /// smallest angle corner: apex + /// largest angle corner: dest + xShortestEdge = xdo; yShortestEdge = ydo; + + shortestEdgeDist = dodist; + middleEdgeDist = dadist; + longestEdgeDist = aodist; + + smallestAngleCorner = tapex; + middleAngleCorner = torg; + largestAngleCorner = tdest; + break; + + }// end of switch + // check for offcenter condition + if (offcenter && (offconstant > 0.0)) + { + // origin has the smallest angle + if (orientation == 213 || orientation == 231) + { + // Find the position of the off-center, as described by Alper Ungor. + dxoff = 0.5 * xShortestEdge - offconstant * yShortestEdge; + dyoff = 0.5 * yShortestEdge + offconstant * xShortestEdge; + // If the off-center is closer to destination than the + // circumcenter, use the off-center instead. + /// doubleLY BAD CASE /// + if (dxoff * dxoff + dyoff * dyoff < + (dx - xdo) * (dx - xdo) + (dy - ydo) * (dy - ydo)) + { + dx = xdo + dxoff; + dy = ydo + dyoff; + } + /// ALMOST GOOD CASE /// + else + { + almostGood = 1; + } + // destination has the smallest angle + } + else if (orientation == 123 || orientation == 132) + { + // Find the position of the off-center, as described by Alper Ungor. + dxoff = 0.5 * xShortestEdge + offconstant * yShortestEdge; + dyoff = 0.5 * yShortestEdge - offconstant * xShortestEdge; + // If the off-center is closer to the origin than the + // circumcenter, use the off-center instead. + /// doubleLY BAD CASE /// + if (dxoff * dxoff + dyoff * dyoff < dx * dx + dy * dy) + { + dx = dxoff; + dy = dyoff; + } + /// ALMOST GOOD CASE /// + else + { + almostGood = 1; + } + // apex has the smallest angle + } + else + {//orientation == 312 || orientation == 321 + // Find the position of the off-center, as described by Alper Ungor. + dxoff = 0.5 * xShortestEdge - offconstant * yShortestEdge; + dyoff = 0.5 * yShortestEdge + offconstant * xShortestEdge; + // If the off-center is closer to the origin than the + // circumcenter, use the off-center instead. + /// doubleLY BAD CASE /// + if (dxoff * dxoff + dyoff * dyoff < dx * dx + dy * dy) + { + dx = dxoff; + dy = dyoff; + } + /// ALMOST GOOD CASE /// + else + { + almostGood = 1; + } + } + } + // if the bad triangle is almost good, apply our approach + if (almostGood == 1) + { + + /// calculate cosine of largest angle /// + cosMaxAngle = (middleEdgeDist + shortestEdgeDist - longestEdgeDist) / (2 * Math.Sqrt(middleEdgeDist) * Math.Sqrt(shortestEdgeDist)); + if (cosMaxAngle < 0.0) + { + // obtuse + isObtuse = true; + } + else if (Math.Abs(cosMaxAngle - 0.0) <= EPS) + { + // right triangle (largest angle is 90 degrees) + isObtuse = true; + } + else + { + // nonobtuse + isObtuse = false; + } + /// RELOCATION (LOCAL SMOOTHING) /// + /// check for possible relocation of one of triangle's points /// + relocated = DoSmoothing(delotri, torg, tdest, tapex, ref newloc); + /// if relocation is possible, delete that vertex and insert a vertex at the new location /// + if (relocated > 0) + { + Statistic.RelocationCount++; + + dx = newloc[0] - torg.x; + dy = newloc[1] - torg.y; + origin_x = torg.x; // keep for later use + origin_y = torg.y; + switch (relocated) + { + case 1: + //printf("Relocate: (%f,%f)\n", torg[0],torg[1]); + mesh.DeleteVertex(ref delotri); + break; + case 2: + //printf("Relocate: (%f,%f)\n", tdest[0],tdest[1]); + delotri.Lnext(); + mesh.DeleteVertex(ref delotri); + break; + case 3: + //printf("Relocate: (%f,%f)\n", tapex[0],tapex[1]); + delotri.Lprev(); + mesh.DeleteVertex(ref delotri); + break; + + } + } + else + { + // calculate radius of the petal according to angle constraint + // first find the visible region, PETAL + // find the center of the circle and radius + petalRadius = Math.Sqrt(shortestEdgeDist) / (2 * Math.Sin(behavior.MinAngle * Math.PI / 180.0)); + /// compute two possible centers of the petal /// + // finding the center + // first find the middle point of smallest edge + xMidOfShortestEdge = (middleAngleCorner.x + largestAngleCorner.x) / 2.0; + yMidOfShortestEdge = (middleAngleCorner.y + largestAngleCorner.y) / 2.0; + // two possible centers + xPetalCtr_1 = xMidOfShortestEdge + Math.Sqrt(petalRadius * petalRadius - (shortestEdgeDist / 4)) * (middleAngleCorner.y - + largestAngleCorner.y) / Math.Sqrt(shortestEdgeDist); + yPetalCtr_1 = yMidOfShortestEdge + Math.Sqrt(petalRadius * petalRadius - (shortestEdgeDist / 4)) * (largestAngleCorner.x - + middleAngleCorner.x) / Math.Sqrt(shortestEdgeDist); + + xPetalCtr_2 = xMidOfShortestEdge - Math.Sqrt(petalRadius * petalRadius - (shortestEdgeDist / 4)) * (middleAngleCorner.y - + largestAngleCorner.y) / Math.Sqrt(shortestEdgeDist); + yPetalCtr_2 = yMidOfShortestEdge - Math.Sqrt(petalRadius * petalRadius - (shortestEdgeDist / 4)) * (largestAngleCorner.x - + middleAngleCorner.x) / Math.Sqrt(shortestEdgeDist); + // find the correct circle since there will be two possible circles + // calculate the distance to smallest angle corner + dxcenter1 = (xPetalCtr_1 - smallestAngleCorner.x) * (xPetalCtr_1 - smallestAngleCorner.x); + dycenter1 = (yPetalCtr_1 - smallestAngleCorner.y) * (yPetalCtr_1 - smallestAngleCorner.y); + dxcenter2 = (xPetalCtr_2 - smallestAngleCorner.x) * (xPetalCtr_2 - smallestAngleCorner.x); + dycenter2 = (yPetalCtr_2 - smallestAngleCorner.y) * (yPetalCtr_2 - smallestAngleCorner.y); + + // whichever is closer to smallest angle corner, it must be the center + if (dxcenter1 + dycenter1 <= dxcenter2 + dycenter2) + { + xPetalCtr = xPetalCtr_1; yPetalCtr = yPetalCtr_1; + } + else + { + xPetalCtr = xPetalCtr_2; yPetalCtr = yPetalCtr_2; + } + + /// find the third point of the neighbor triangle /// + neighborNotFound = GetNeighborsVertex(badotri, middleAngleCorner.x, middleAngleCorner.y, + smallestAngleCorner.x, smallestAngleCorner.y, ref thirdPoint, ref neighborotri); + /// find the circumcenter of the neighbor triangle /// + dxFirstSuggestion = dx; // if we cannot find any appropriate suggestion, we use circumcenter + dyFirstSuggestion = dy; + // if there is a neighbor triangle + if (!neighborNotFound) + { + neighborvertex_1 = neighborotri.Org(); + neighborvertex_2 = neighborotri.Dest(); + neighborvertex_3 = neighborotri.Apex(); + // now calculate neighbor's circumcenter which is the voronoi site + neighborCircumcenter = predicates.FindCircumcenter(neighborvertex_1, neighborvertex_2, neighborvertex_3, + ref xi_tmp, ref eta_tmp); + + /// compute petal and Voronoi edge intersection /// + // in order to avoid degenerate cases, we need to do a vector based calculation for line + vector_x = (middleAngleCorner.y - smallestAngleCorner.y);//(-y, x) + vector_y = smallestAngleCorner.x - middleAngleCorner.x; + vector_x = myCircumcenter.x + vector_x; + vector_y = myCircumcenter.y + vector_y; + + + // by intersecting bisectors you will end up with the one you want to walk on + // then this line and circle should be intersected + CircleLineIntersection(myCircumcenter.x, myCircumcenter.y, vector_x, vector_y, + xPetalCtr, yPetalCtr, petalRadius, ref p); + /// choose the correct intersection point /// + // calculate middle point of the longest edge(bisector) + xMidOfLongestEdge = (middleAngleCorner.x + smallestAngleCorner.x) / 2.0; + yMidOfLongestEdge = (middleAngleCorner.y + smallestAngleCorner.y) / 2.0; + // we need to find correct intersection point, since line intersects circle twice + isCorrect = ChooseCorrectPoint(xMidOfLongestEdge, yMidOfLongestEdge, p[3], p[4], + myCircumcenter.x, myCircumcenter.y, isObtuse); + // make sure which point is the correct one to be considered + if (isCorrect) + { + inter_x = p[3]; + inter_y = p[4]; + } + else + { + inter_x = p[1]; + inter_y = p[2]; + } + /// check if there is a Voronoi vertex between before intersection /// + // check if the voronoi vertex is between the intersection and circumcenter + PointBetweenPoints(inter_x, inter_y, myCircumcenter.x, myCircumcenter.y, + neighborCircumcenter.x, neighborCircumcenter.y, ref voronoiOrInter); + + /// determine the point to be suggested /// + if (p[0] > 0.0) + { // there is at least one intersection point + // if it is between circumcenter and intersection + // if it returns 1.0 this means we have a voronoi vertex within feasible region + if (Math.Abs(voronoiOrInter[0] - 1.0) <= EPS) + { + if (IsBadTriangleAngle(middleAngleCorner.x, middleAngleCorner.y, largestAngleCorner.x, largestAngleCorner.y, neighborCircumcenter.x, neighborCircumcenter.y)) + { + // go back to circumcenter + dxFirstSuggestion = dx; + dyFirstSuggestion = dy; + + } + else + { // we are not creating a bad triangle + // neighbor's circumcenter is suggested + dxFirstSuggestion = voronoiOrInter[2] - torg.x; + dyFirstSuggestion = voronoiOrInter[3] - torg.y; + } + + } + else + { // there is no voronoi vertex between intersection point and circumcenter + if (IsBadTriangleAngle(largestAngleCorner.x, largestAngleCorner.y, middleAngleCorner.x, middleAngleCorner.y, inter_x, inter_y)) + { + // if it is inside feasible region, then insert v2 + // apply perturbation + // find the distance between circumcenter and intersection point + d = Math.Sqrt((inter_x - myCircumcenter.x) * (inter_x - myCircumcenter.x) + + (inter_y - myCircumcenter.y) * (inter_y - myCircumcenter.y)); + // then find the vector going from intersection point to circumcenter + ax = myCircumcenter.x - inter_x; + ay = myCircumcenter.y - inter_y; + + ax = ax / d; + ay = ay / d; + // now calculate the new intersection point which is perturbated towards the circumcenter + inter_x = inter_x + ax * pertConst * Math.Sqrt(shortestEdgeDist); + inter_y = inter_y + ay * pertConst * Math.Sqrt(shortestEdgeDist); + if (IsBadTriangleAngle(middleAngleCorner.x, middleAngleCorner.y, largestAngleCorner.x, largestAngleCorner.y, inter_x, inter_y)) + { + // go back to circumcenter + dxFirstSuggestion = dx; + dyFirstSuggestion = dy; + + } + else + { + // intersection point is suggested + dxFirstSuggestion = inter_x - torg.x; + dyFirstSuggestion = inter_y - torg.y; + + } + } + else + { + // intersection point is suggested + dxFirstSuggestion = inter_x - torg.x; + dyFirstSuggestion = inter_y - torg.y; + } + } + /// if it is an acute triangle, check if it is a good enough location /// + // for acute triangle case, we need to check if it is ok to use either of them + if ((smallestAngleCorner.x - myCircumcenter.x) * (smallestAngleCorner.x - myCircumcenter.x) + + (smallestAngleCorner.y - myCircumcenter.y) * (smallestAngleCorner.y - myCircumcenter.y) > + lengthConst * ((smallestAngleCorner.x - (dxFirstSuggestion + torg.x)) * + (smallestAngleCorner.x - (dxFirstSuggestion + torg.x)) + + (smallestAngleCorner.y - (dyFirstSuggestion + torg.y)) * + (smallestAngleCorner.y - (dyFirstSuggestion + torg.y)))) + { + // use circumcenter + dxFirstSuggestion = dx; + dyFirstSuggestion = dy; + }// else we stick to what we have found + }// intersection point + + }// if it is on the boundary, meaning no neighbor triangle in this direction, try other direction + + /// DO THE SAME THING FOR THE OTHER DIRECTION /// + /// find the third point of the neighbor triangle /// + neighborNotFound = GetNeighborsVertex(badotri, largestAngleCorner.x, largestAngleCorner.y, + smallestAngleCorner.x, smallestAngleCorner.y, ref thirdPoint, ref neighborotri); + /// find the circumcenter of the neighbor triangle /// + dxSecondSuggestion = dx; // if we cannot find any appropriate suggestion, we use circumcenter + dySecondSuggestion = dy; + // if there is a neighbor triangle + if (!neighborNotFound) + { + neighborvertex_1 = neighborotri.Org(); + neighborvertex_2 = neighborotri.Dest(); + neighborvertex_3 = neighborotri.Apex(); + // now calculate neighbor's circumcenter which is the voronoi site + neighborCircumcenter = predicates.FindCircumcenter(neighborvertex_1, neighborvertex_2, neighborvertex_3, + ref xi_tmp, ref eta_tmp); + + /// compute petal and Voronoi edge intersection /// + // in order to avoid degenerate cases, we need to do a vector based calculation for line + vector_x = (largestAngleCorner.y - smallestAngleCorner.y);//(-y, x) + vector_y = smallestAngleCorner.x - largestAngleCorner.x; + vector_x = myCircumcenter.x + vector_x; + vector_y = myCircumcenter.y + vector_y; + + + // by intersecting bisectors you will end up with the one you want to walk on + // then this line and circle should be intersected + CircleLineIntersection(myCircumcenter.x, myCircumcenter.y, vector_x, vector_y, + xPetalCtr, yPetalCtr, petalRadius, ref p); + + /// choose the correct intersection point /// + // calcuwedgeslate middle point of the longest edge(bisector) + xMidOfMiddleEdge = (largestAngleCorner.x + smallestAngleCorner.x) / 2.0; + yMidOfMiddleEdge = (largestAngleCorner.y + smallestAngleCorner.y) / 2.0; + // we need to find correct intersection point, since line intersects circle twice + // this direction is always ACUTE + isCorrect = ChooseCorrectPoint(xMidOfMiddleEdge, yMidOfMiddleEdge, p[3], p[4], + myCircumcenter.x, myCircumcenter.y, false/*(isObtuse+1)%2*/); + // make sure which point is the correct one to be considered + if (isCorrect) + { + inter_x = p[3]; + inter_y = p[4]; + } + else + { + inter_x = p[1]; + inter_y = p[2]; + } + + /// check if there is a Voronoi vertex between before intersection /// + // check if the voronoi vertex is between the intersection and circumcenter + PointBetweenPoints(inter_x, inter_y, myCircumcenter.x, myCircumcenter.y, + neighborCircumcenter.x, neighborCircumcenter.y, ref voronoiOrInter); + + /// determine the point to be suggested /// + if (p[0] > 0.0) + { // there is at least one intersection point + // if it is between circumcenter and intersection + // if it returns 1.0 this means we have a voronoi vertex within feasible region + if (Math.Abs(voronoiOrInter[0] - 1.0) <= EPS) + { + if (IsBadTriangleAngle(middleAngleCorner.x, middleAngleCorner.y, largestAngleCorner.x, largestAngleCorner.y, neighborCircumcenter.x, neighborCircumcenter.y)) + { + // go back to circumcenter + dxSecondSuggestion = dx; + dySecondSuggestion = dy; + + } + else + { // we are not creating a bad triangle + // neighbor's circumcenter is suggested + dxSecondSuggestion = voronoiOrInter[2] - torg.x; + dySecondSuggestion = voronoiOrInter[3] - torg.y; + + } + + } + else + { // there is no voronoi vertex between intersection point and circumcenter + if (IsBadTriangleAngle(middleAngleCorner.x, middleAngleCorner.y, largestAngleCorner.x, largestAngleCorner.y, inter_x, inter_y)) + { + // if it is inside feasible region, then insert v2 + // apply perturbation + // find the distance between circumcenter and intersection point + d = Math.Sqrt((inter_x - myCircumcenter.x) * (inter_x - myCircumcenter.x) + + (inter_y - myCircumcenter.y) * (inter_y - myCircumcenter.y)); + // then find the vector going from intersection point to circumcenter + ax = myCircumcenter.x - inter_x; + ay = myCircumcenter.y - inter_y; + + ax = ax / d; + ay = ay / d; + // now calculate the new intersection point which is perturbated towards the circumcenter + inter_x = inter_x + ax * pertConst * Math.Sqrt(shortestEdgeDist); + inter_y = inter_y + ay * pertConst * Math.Sqrt(shortestEdgeDist); + if (IsBadTriangleAngle(middleAngleCorner.x, middleAngleCorner.y, largestAngleCorner.x, largestAngleCorner.y, inter_x, inter_y)) + { + // go back to circumcenter + dxSecondSuggestion = dx; + dySecondSuggestion = dy; + + } + else + { + // intersection point is suggested + dxSecondSuggestion = inter_x - torg.x; + dySecondSuggestion = inter_y - torg.y; + } + } + else + { + + // intersection point is suggested + dxSecondSuggestion = inter_x - torg.x; + dySecondSuggestion = inter_y - torg.y; + } + } + /// if it is an acute triangle, check if it is a good enough location /// + // for acute triangle case, we need to check if it is ok to use either of them + if ((smallestAngleCorner.x - myCircumcenter.x) * (smallestAngleCorner.x - myCircumcenter.x) + + (smallestAngleCorner.y - myCircumcenter.y) * (smallestAngleCorner.y - myCircumcenter.y) > + lengthConst * ((smallestAngleCorner.x - (dxSecondSuggestion + torg.x)) * + (smallestAngleCorner.x - (dxSecondSuggestion + torg.x)) + + (smallestAngleCorner.y - (dySecondSuggestion + torg.y)) * + (smallestAngleCorner.y - (dySecondSuggestion + torg.y)))) + { + // use circumcenter + dxSecondSuggestion = dx; + dySecondSuggestion = dy; + }// else we stick on what we have found + } + }// if it is on the boundary, meaning no neighbor triangle in this direction, the other direction might be ok + if (isObtuse) + { + //obtuse: do nothing + dx = dxFirstSuggestion; + dy = dyFirstSuggestion; + } + else + { // acute : consider other direction + if (justAcute * ((smallestAngleCorner.x - (dxSecondSuggestion + torg.x)) * + (smallestAngleCorner.x - (dxSecondSuggestion + torg.x)) + + (smallestAngleCorner.y - (dySecondSuggestion + torg.y)) * + (smallestAngleCorner.y - (dySecondSuggestion + torg.y))) > + (smallestAngleCorner.x - (dxFirstSuggestion + torg.x)) * + (smallestAngleCorner.x - (dxFirstSuggestion + torg.x)) + + (smallestAngleCorner.y - (dyFirstSuggestion + torg.y)) * + (smallestAngleCorner.y - (dyFirstSuggestion + torg.y))) + { + dx = dxSecondSuggestion; + dy = dySecondSuggestion; + } + else + { + dx = dxFirstSuggestion; + dy = dyFirstSuggestion; + } + + }// end if obtuse + }// end of relocation + }// end of almostGood + + Point circumcenter = new Point(); + + if (relocated <= 0) + { + circumcenter.x = torg.x + dx; + circumcenter.y = torg.y + dy; + } + else + { + circumcenter.x = origin_x + dx; + circumcenter.y = origin_y + dy; + } + + xi = (yao * dx - xao * dy) * (2.0 * denominator); + eta = (xdo * dy - ydo * dx) * (2.0 * denominator); + + return circumcenter; + } + + /// + /// Find a new location for a Steiner point. + /// + /// + /// + /// + /// + /// + /// + /// + /// + private Point FindNewLocation(Vertex torg, Vertex tdest, Vertex tapex, + ref double xi, ref double eta, bool offcenter, Otri badotri) + { + double offconstant = behavior.offconstant; + + // for calculating the distances of the edges + double xdo, ydo, xao, yao, xda, yda; + double dodist, aodist, dadist; + // for exact calculation + double denominator; + double dx, dy, dxoff, dyoff; + + ////////////////////////////// HALE'S VARIABLES ////////////////////////////// + // keeps the difference of coordinates edge + double xShortestEdge = 0, yShortestEdge = 0; + + // keeps the square of edge lengths + double shortestEdgeDist = 0, middleEdgeDist = 0, longestEdgeDist = 0; + + // keeps the vertices according to the angle incident to that vertex in a triangle + Point smallestAngleCorner, middleAngleCorner, largestAngleCorner; + + // keeps the type of orientation if the triangle + int orientation = 0; + // keeps the coordinates of circumcenter of itself and neighbor triangle circumcenter + Point myCircumcenter, neighborCircumcenter; + + // keeps if bad triangle is almost good or not + int almostGood = 0; + // keeps the cosine of the largest angle + double cosMaxAngle; + bool isObtuse; // 1: obtuse 0: nonobtuse + // keeps the radius of petal + double petalRadius; + // for calculating petal center + double xPetalCtr_1, yPetalCtr_1, xPetalCtr_2, yPetalCtr_2, xPetalCtr, yPetalCtr, xMidOfShortestEdge, yMidOfShortestEdge; + double dxcenter1, dycenter1, dxcenter2, dycenter2; + // for finding neighbor + Otri neighborotri = default(Otri); + double[] thirdPoint = new double[2]; + //int neighborNotFound = -1; + // for keeping the vertices of the neighbor triangle + Vertex neighborvertex_1; + Vertex neighborvertex_2; + Vertex neighborvertex_3; + // dummy variables + double xi_tmp = 0, eta_tmp = 0; + //vertex thirdVertex; + // for petal intersection + double vector_x, vector_y, xMidOfLongestEdge, yMidOfLongestEdge, inter_x, inter_y; + double[] p = new double[5], voronoiOrInter = new double[4]; + bool isCorrect; + + // for vector calculations in perturbation + double ax, ay, d; + double pertConst = 0.06; // perturbation constant + + double lengthConst = 1; // used at comparing circumcenter's distance to proposed point's distance + double justAcute = 1; // used for making the program working for one direction only + // for smoothing + int relocated = 0;// used to differentiate between calling the deletevertex and just proposing a steiner point + double[] newloc = new double[2]; // new location suggested by smoothing + double origin_x = 0, origin_y = 0; // for keeping torg safe + Otri delotri; // keeping the original orientation for relocation process + // keeps the first and second direction suggested points + double dxFirstSuggestion, dyFirstSuggestion, dxSecondSuggestion, dySecondSuggestion; + // second direction variables + double xMidOfMiddleEdge, yMidOfMiddleEdge; + + double minangle; // in order to make sure that the circumcircle of the bad triangle is greater than petal + // for calculating the slab + double linepnt1_x, linepnt1_y, linepnt2_x, linepnt2_y; // two points of the line + double line_inter_x = 0, line_inter_y = 0; + double line_vector_x, line_vector_y; + double[] line_p = new double[3]; // used for getting the return values of functions related to line intersection + double[] line_result = new double[4]; + // intersection of slab and the petal + double petal_slab_inter_x_first, petal_slab_inter_y_first, petal_slab_inter_x_second, petal_slab_inter_y_second, x_1, y_1, x_2, y_2; + double petal_bisector_x, petal_bisector_y, dist; + double alpha; + bool neighborNotFound_first; + bool neighborNotFound_second; + ////////////////////////////// END OF HALE'S VARIABLES ////////////////////////////// + + Statistic.CircumcenterCount++; + + // Compute the circumcenter of the triangle. + xdo = tdest.x - torg.x; + ydo = tdest.y - torg.y; + xao = tapex.x - torg.x; + yao = tapex.y - torg.y; + xda = tapex.x - tdest.x; + yda = tapex.y - tdest.y; + // keeps the square of the distances + dodist = xdo * xdo + ydo * ydo; + aodist = xao * xao + yao * yao; + dadist = (tdest.x - tapex.x) * (tdest.x - tapex.x) + + (tdest.y - tapex.y) * (tdest.y - tapex.y); + // checking if the user wanted exact arithmetic or not + if (Behavior.NoExact) + { + denominator = 0.5 / (xdo * yao - xao * ydo); + } + else + { + // Use the counterclockwise() routine to ensure a positive (and + // reasonably accurate) result, avoiding any possibility of + // division by zero. + denominator = 0.5 / predicates.CounterClockwise(tdest, tapex, torg); + // Don't count the above as an orientation test. + Statistic.CounterClockwiseCount--; + } + // calculate the circumcenter in terms of distance to origin point + dx = (yao * dodist - ydo * aodist) * denominator; + dy = (xdo * aodist - xao * dodist) * denominator; + // for debugging and for keeping circumcenter to use later + // coordinate value of the circumcenter + myCircumcenter = new Point(torg.x + dx, torg.y + dy); + + delotri = badotri; // save for later + ///////////////// FINDING THE ORIENTATION OF TRIANGLE ////////////////// + // Find the (squared) length of the triangle's shortest edge. This + // serves as a conservative estimate of the insertion radius of the + // circumcenter's parent. The estimate is used to ensure that + // the algorithm terminates even if very small angles appear in + // the input PSLG. + // find the orientation of the triangle, basically shortest and longest edges + orientation = LongestShortestEdge(aodist, dadist, dodist); + //printf("org: (%f,%f), dest: (%f,%f), apex: (%f,%f)\n",torg[0],torg[1],tdest[0],tdest[1],tapex[0],tapex[1]); + ///////////////////////////////////////////////////////////////////////////////////////////// + // 123: shortest: aodist // 213: shortest: dadist // 312: shortest: dodist // + // middle: dadist // middle: aodist // middle: aodist // + // longest: dodist // longest: dodist // longest: dadist // + // 132: shortest: aodist // 231: shortest: dadist // 321: shortest: dodist // + // middle: dodist // middle: dodist // middle: dadist // + // longest: dadist // longest: aodist // longest: aodist // + ///////////////////////////////////////////////////////////////////////////////////////////// + + switch (orientation) + { + case 123: // assign necessary information + /// smallest angle corner: dest + /// largest angle corner: apex + xShortestEdge = xao; yShortestEdge = yao; + + shortestEdgeDist = aodist; + middleEdgeDist = dadist; + longestEdgeDist = dodist; + + smallestAngleCorner = tdest; + middleAngleCorner = torg; + largestAngleCorner = tapex; + break; + + case 132: // assign necessary information + /// smallest angle corner: dest + /// largest angle corner: org + xShortestEdge = xao; yShortestEdge = yao; + + shortestEdgeDist = aodist; + middleEdgeDist = dodist; + longestEdgeDist = dadist; + + smallestAngleCorner = tdest; + middleAngleCorner = tapex; + largestAngleCorner = torg; + + break; + case 213: // assign necessary information + /// smallest angle corner: org + /// largest angle corner: apex + xShortestEdge = xda; yShortestEdge = yda; + + shortestEdgeDist = dadist; + middleEdgeDist = aodist; + longestEdgeDist = dodist; + + smallestAngleCorner = torg; + middleAngleCorner = tdest; + largestAngleCorner = tapex; + break; + case 231: // assign necessary information + /// smallest angle corner: org + /// largest angle corner: dest + xShortestEdge = xda; yShortestEdge = yda; + + shortestEdgeDist = dadist; + middleEdgeDist = dodist; + longestEdgeDist = aodist; + + smallestAngleCorner = torg; + middleAngleCorner = tapex; + largestAngleCorner = tdest; + break; + case 312: // assign necessary information + /// smallest angle corner: apex + /// largest angle corner: org + xShortestEdge = xdo; yShortestEdge = ydo; + + shortestEdgeDist = dodist; + middleEdgeDist = aodist; + longestEdgeDist = dadist; + + smallestAngleCorner = tapex; + middleAngleCorner = tdest; + largestAngleCorner = torg; + break; + case 321: // assign necessary information + default: // TODO: is this safe? + /// smallest angle corner: apex + /// largest angle corner: dest + xShortestEdge = xdo; yShortestEdge = ydo; + + shortestEdgeDist = dodist; + middleEdgeDist = dadist; + longestEdgeDist = aodist; + + smallestAngleCorner = tapex; + middleAngleCorner = torg; + largestAngleCorner = tdest; + break; + + }// end of switch + // check for offcenter condition + if (offcenter && (offconstant > 0.0)) + { + // origin has the smallest angle + if (orientation == 213 || orientation == 231) + { + // Find the position of the off-center, as described by Alper Ungor. + dxoff = 0.5 * xShortestEdge - offconstant * yShortestEdge; + dyoff = 0.5 * yShortestEdge + offconstant * xShortestEdge; + // If the off-center is closer to destination than the + // circumcenter, use the off-center instead. + /// doubleLY BAD CASE /// + if (dxoff * dxoff + dyoff * dyoff < + (dx - xdo) * (dx - xdo) + (dy - ydo) * (dy - ydo)) + { + dx = xdo + dxoff; + dy = ydo + dyoff; + } + /// ALMOST GOOD CASE /// + else + { + almostGood = 1; + } + // destination has the smallest angle + } + else if (orientation == 123 || orientation == 132) + { + // Find the position of the off-center, as described by Alper Ungor. + dxoff = 0.5 * xShortestEdge + offconstant * yShortestEdge; + dyoff = 0.5 * yShortestEdge - offconstant * xShortestEdge; + // If the off-center is closer to the origin than the + // circumcenter, use the off-center instead. + /// doubleLY BAD CASE /// + if (dxoff * dxoff + dyoff * dyoff < dx * dx + dy * dy) + { + dx = dxoff; + dy = dyoff; + } + /// ALMOST GOOD CASE /// + else + { + almostGood = 1; + } + // apex has the smallest angle + } + else + {//orientation == 312 || orientation == 321 + // Find the position of the off-center, as described by Alper Ungor. + dxoff = 0.5 * xShortestEdge - offconstant * yShortestEdge; + dyoff = 0.5 * yShortestEdge + offconstant * xShortestEdge; + // If the off-center is closer to the origin than the + // circumcenter, use the off-center instead. + /// doubleLY BAD CASE /// + if (dxoff * dxoff + dyoff * dyoff < dx * dx + dy * dy) + { + dx = dxoff; + dy = dyoff; + } + /// ALMOST GOOD CASE /// + else + { + almostGood = 1; + } + } + } + // if the bad triangle is almost good, apply our approach + if (almostGood == 1) + { + + /// calculate cosine of largest angle /// + cosMaxAngle = (middleEdgeDist + shortestEdgeDist - longestEdgeDist) / (2 * Math.Sqrt(middleEdgeDist) * Math.Sqrt(shortestEdgeDist)); + if (cosMaxAngle < 0.0) + { + // obtuse + isObtuse = true; + } + else if (Math.Abs(cosMaxAngle - 0.0) <= EPS) + { + // right triangle (largest angle is 90 degrees) + isObtuse = true; + } + else + { + // nonobtuse + isObtuse = false; + } + /// RELOCATION (LOCAL SMOOTHING) /// + /// check for possible relocation of one of triangle's points /// + relocated = DoSmoothing(delotri, torg, tdest, tapex, ref newloc); + /// if relocation is possible, delete that vertex and insert a vertex at the new location /// + if (relocated > 0) + { + Statistic.RelocationCount++; + + dx = newloc[0] - torg.x; + dy = newloc[1] - torg.y; + origin_x = torg.x; // keep for later use + origin_y = torg.y; + switch (relocated) + { + case 1: + //printf("Relocate: (%f,%f)\n", torg[0],torg[1]); + mesh.DeleteVertex(ref delotri); + break; + case 2: + //printf("Relocate: (%f,%f)\n", tdest[0],tdest[1]); + delotri.Lnext(); + mesh.DeleteVertex(ref delotri); + break; + case 3: + //printf("Relocate: (%f,%f)\n", tapex[0],tapex[1]); + delotri.Lprev(); + mesh.DeleteVertex(ref delotri); + break; + } + } + else + { + // calculate radius of the petal according to angle constraint + // first find the visible region, PETAL + // find the center of the circle and radius + // choose minimum angle as the maximum of quality angle and the minimum angle of the bad triangle + minangle = Math.Acos((middleEdgeDist + longestEdgeDist - shortestEdgeDist) / (2 * Math.Sqrt(middleEdgeDist) * Math.Sqrt(longestEdgeDist))) * 180.0 / Math.PI; + if (behavior.MinAngle > minangle) + { + minangle = behavior.MinAngle; + } + else + { + minangle = minangle + 0.5; + } + petalRadius = Math.Sqrt(shortestEdgeDist) / (2 * Math.Sin(minangle * Math.PI / 180.0)); + /// compute two possible centers of the petal /// + // finding the center + // first find the middle point of smallest edge + xMidOfShortestEdge = (middleAngleCorner.x + largestAngleCorner.x) / 2.0; + yMidOfShortestEdge = (middleAngleCorner.y + largestAngleCorner.y) / 2.0; + // two possible centers + xPetalCtr_1 = xMidOfShortestEdge + Math.Sqrt(petalRadius * petalRadius - (shortestEdgeDist / 4)) * (middleAngleCorner.y - + largestAngleCorner.y) / Math.Sqrt(shortestEdgeDist); + yPetalCtr_1 = yMidOfShortestEdge + Math.Sqrt(petalRadius * petalRadius - (shortestEdgeDist / 4)) * (largestAngleCorner.x - + middleAngleCorner.x) / Math.Sqrt(shortestEdgeDist); + + xPetalCtr_2 = xMidOfShortestEdge - Math.Sqrt(petalRadius * petalRadius - (shortestEdgeDist / 4)) * (middleAngleCorner.y - + largestAngleCorner.y) / Math.Sqrt(shortestEdgeDist); + yPetalCtr_2 = yMidOfShortestEdge - Math.Sqrt(petalRadius * petalRadius - (shortestEdgeDist / 4)) * (largestAngleCorner.x - + middleAngleCorner.x) / Math.Sqrt(shortestEdgeDist); + // find the correct circle since there will be two possible circles + // calculate the distance to smallest angle corner + dxcenter1 = (xPetalCtr_1 - smallestAngleCorner.x) * (xPetalCtr_1 - smallestAngleCorner.x); + dycenter1 = (yPetalCtr_1 - smallestAngleCorner.y) * (yPetalCtr_1 - smallestAngleCorner.y); + dxcenter2 = (xPetalCtr_2 - smallestAngleCorner.x) * (xPetalCtr_2 - smallestAngleCorner.x); + dycenter2 = (yPetalCtr_2 - smallestAngleCorner.y) * (yPetalCtr_2 - smallestAngleCorner.y); + + // whichever is closer to smallest angle corner, it must be the center + if (dxcenter1 + dycenter1 <= dxcenter2 + dycenter2) + { + xPetalCtr = xPetalCtr_1; yPetalCtr = yPetalCtr_1; + } + else + { + xPetalCtr = xPetalCtr_2; yPetalCtr = yPetalCtr_2; + } + /// find the third point of the neighbor triangle /// + neighborNotFound_first = GetNeighborsVertex(badotri, middleAngleCorner.x, middleAngleCorner.y, + smallestAngleCorner.x, smallestAngleCorner.y, ref thirdPoint, ref neighborotri); + /// find the circumcenter of the neighbor triangle /// + dxFirstSuggestion = dx; // if we cannot find any appropriate suggestion, we use circumcenter + dyFirstSuggestion = dy; + /// before checking the neighbor, find the petal and slab intersections /// + // calculate the intersection point of the petal and the slab lines + // first find the vector + // distance between xmid and petal center + dist = Math.Sqrt((xPetalCtr - xMidOfShortestEdge) * (xPetalCtr - xMidOfShortestEdge) + (yPetalCtr - yMidOfShortestEdge) * (yPetalCtr - yMidOfShortestEdge)); + // find the unit vector goes from mid point to petal center + line_vector_x = (xPetalCtr - xMidOfShortestEdge) / dist; + line_vector_y = (yPetalCtr - yMidOfShortestEdge) / dist; + // find the third point other than p and q + petal_bisector_x = xPetalCtr + line_vector_x * petalRadius; + petal_bisector_y = yPetalCtr + line_vector_y * petalRadius; + alpha = (2.0 * behavior.MaxAngle + minangle - 180.0) * Math.PI / 180.0; + // rotate the vector cw around the petal center + x_1 = petal_bisector_x * Math.Cos(alpha) + petal_bisector_y * Math.Sin(alpha) + xPetalCtr - xPetalCtr * Math.Cos(alpha) - yPetalCtr * Math.Sin(alpha); + y_1 = -petal_bisector_x * Math.Sin(alpha) + petal_bisector_y * Math.Cos(alpha) + yPetalCtr + xPetalCtr * Math.Sin(alpha) - yPetalCtr * Math.Cos(alpha); + // rotate the vector ccw around the petal center + x_2 = petal_bisector_x * Math.Cos(alpha) - petal_bisector_y * Math.Sin(alpha) + xPetalCtr - xPetalCtr * Math.Cos(alpha) + yPetalCtr * Math.Sin(alpha); + y_2 = petal_bisector_x * Math.Sin(alpha) + petal_bisector_y * Math.Cos(alpha) + yPetalCtr - xPetalCtr * Math.Sin(alpha) - yPetalCtr * Math.Cos(alpha); + // we need to find correct intersection point, since there are two possibilities + // weather it is obtuse/acute the one closer to the minimum angle corner is the first direction + isCorrect = ChooseCorrectPoint(x_2, y_2, middleAngleCorner.x, middleAngleCorner.y, x_1, y_1, true); + // make sure which point is the correct one to be considered + if (isCorrect) + { + petal_slab_inter_x_first = x_1; + petal_slab_inter_y_first = y_1; + petal_slab_inter_x_second = x_2; + petal_slab_inter_y_second = y_2; + } + else + { + petal_slab_inter_x_first = x_2; + petal_slab_inter_y_first = y_2; + petal_slab_inter_x_second = x_1; + petal_slab_inter_y_second = y_1; + } + /// choose the correct intersection point /// + // calculate middle point of the longest edge(bisector) + xMidOfLongestEdge = (middleAngleCorner.x + smallestAngleCorner.x) / 2.0; + yMidOfLongestEdge = (middleAngleCorner.y + smallestAngleCorner.y) / 2.0; + // if there is a neighbor triangle + if (!neighborNotFound_first) + { + neighborvertex_1 = neighborotri.Org(); + neighborvertex_2 = neighborotri.Dest(); + neighborvertex_3 = neighborotri.Apex(); + // now calculate neighbor's circumcenter which is the voronoi site + neighborCircumcenter = predicates.FindCircumcenter(neighborvertex_1, neighborvertex_2, neighborvertex_3, + ref xi_tmp, ref eta_tmp); + + /// compute petal and Voronoi edge intersection /// + // in order to avoid degenerate cases, we need to do a vector based calculation for line + vector_x = (middleAngleCorner.y - smallestAngleCorner.y);//(-y, x) + vector_y = smallestAngleCorner.x - middleAngleCorner.x; + vector_x = myCircumcenter.x + vector_x; + vector_y = myCircumcenter.y + vector_y; + // by intersecting bisectors you will end up with the one you want to walk on + // then this line and circle should be intersected + CircleLineIntersection(myCircumcenter.x, myCircumcenter.y, vector_x, vector_y, + xPetalCtr, yPetalCtr, petalRadius, ref p); + // we need to find correct intersection point, since line intersects circle twice + isCorrect = ChooseCorrectPoint(xMidOfLongestEdge, yMidOfLongestEdge, p[3], p[4], + myCircumcenter.x, myCircumcenter.y, isObtuse); + // make sure which point is the correct one to be considered + if (isCorrect) + { + inter_x = p[3]; + inter_y = p[4]; + } + else + { + inter_x = p[1]; + inter_y = p[2]; + } + //----------------------hale new first direction: for slab calculation---------------// + // calculate the intersection of angle lines and Voronoi + linepnt1_x = middleAngleCorner.x; + linepnt1_y = middleAngleCorner.y; + // vector from middleAngleCorner to largestAngleCorner + line_vector_x = largestAngleCorner.x - middleAngleCorner.x; + line_vector_y = largestAngleCorner.y - middleAngleCorner.y; + // rotate the vector around middleAngleCorner in cw by maxangle degrees + linepnt2_x = petal_slab_inter_x_first; + linepnt2_y = petal_slab_inter_y_first; + // now calculate the intersection of two lines + LineLineIntersection(myCircumcenter.x, myCircumcenter.y, vector_x, vector_y, linepnt1_x, linepnt1_y, linepnt2_x, linepnt2_y, ref line_p); + // check if there is a suitable intersection + if (line_p[0] > 0.0) + { + line_inter_x = line_p[1]; + line_inter_y = line_p[2]; + } + else + { + // for debugging (to make sure) + //printf("1) No intersection between two lines!!!\n"); + //printf("(%.14f,%.14f) (%.14f,%.14f) (%.14f,%.14f) (%.14f,%.14f)\n",myCircumcenter.x,myCircumcenter.y,vector_x,vector_y,linepnt1_x,linepnt1_y,linepnt2_x,linepnt2_y); + } + + //---------------------------------------------------------------------// + /// check if there is a Voronoi vertex between before intersection /// + // check if the voronoi vertex is between the intersection and circumcenter + PointBetweenPoints(inter_x, inter_y, myCircumcenter.x, myCircumcenter.y, + neighborCircumcenter.x, neighborCircumcenter.y, ref voronoiOrInter); + + /// determine the point to be suggested /// + if (p[0] > 0.0) + { // there is at least one intersection point + // if it is between circumcenter and intersection + // if it returns 1.0 this means we have a voronoi vertex within feasible region + if (Math.Abs(voronoiOrInter[0] - 1.0) <= EPS) + { + //-----------------hale new continues 1------------------// + // now check if the line intersection is between cc and voronoi + PointBetweenPoints(voronoiOrInter[2], voronoiOrInter[3], myCircumcenter.x, myCircumcenter.y, line_inter_x, line_inter_y, ref line_result); + if (Math.Abs(line_result[0] - 1.0) <= EPS && line_p[0] > 0.0) + { + // check if we can go further by picking the slab line and petal intersection + // calculate the distance to the smallest angle corner + // check if we create a bad triangle or not + if (((smallestAngleCorner.x - petal_slab_inter_x_first) * (smallestAngleCorner.x - petal_slab_inter_x_first) + + (smallestAngleCorner.y - petal_slab_inter_y_first) * (smallestAngleCorner.y - petal_slab_inter_y_first) > + lengthConst * ((smallestAngleCorner.x - line_inter_x) * + (smallestAngleCorner.x - line_inter_x) + + (smallestAngleCorner.y - line_inter_y) * + (smallestAngleCorner.y - line_inter_y))) + && (IsBadTriangleAngle(middleAngleCorner.x, middleAngleCorner.y, largestAngleCorner.x, largestAngleCorner.y, petal_slab_inter_x_first, petal_slab_inter_y_first)) + && MinDistanceToNeighbor(petal_slab_inter_x_first, petal_slab_inter_y_first, ref neighborotri) > MinDistanceToNeighbor(line_inter_x, line_inter_y, ref neighborotri)) + { + // check the neighbor's vertices also, which one if better + //slab and petal intersection is advised + dxFirstSuggestion = petal_slab_inter_x_first - torg.x; + dyFirstSuggestion = petal_slab_inter_y_first - torg.y; + } + else + { // slab intersection point is further away + if (IsBadTriangleAngle(middleAngleCorner.x, middleAngleCorner.y, largestAngleCorner.x, largestAngleCorner.y, line_inter_x, line_inter_y)) + { + // apply perturbation + // find the distance between circumcenter and intersection point + d = Math.Sqrt((line_inter_x - myCircumcenter.x) * (line_inter_x - myCircumcenter.x) + + (line_inter_y - myCircumcenter.y) * (line_inter_y - myCircumcenter.y)); + // then find the vector going from intersection point to circumcenter + ax = myCircumcenter.x - line_inter_x; + ay = myCircumcenter.y - line_inter_y; + + ax = ax / d; + ay = ay / d; + // now calculate the new intersection point which is perturbated towards the circumcenter + line_inter_x = line_inter_x + ax * pertConst * Math.Sqrt(shortestEdgeDist); + line_inter_y = line_inter_y + ay * pertConst * Math.Sqrt(shortestEdgeDist); + if (IsBadTriangleAngle(middleAngleCorner.x, middleAngleCorner.y, largestAngleCorner.x, largestAngleCorner.y, line_inter_x, line_inter_y)) + { + // go back to circumcenter + dxFirstSuggestion = dx; + dyFirstSuggestion = dy; + } + else + { + // intersection point is suggested + dxFirstSuggestion = line_inter_x - torg.x; + dyFirstSuggestion = line_inter_y - torg.y; + } + } + else + {// we are not creating a bad triangle + // slab intersection is advised + dxFirstSuggestion = line_result[2] - torg.x; + dyFirstSuggestion = line_result[3] - torg.y; + } + } + //------------------------------------------------------// + } + else + { + /// NOW APPLY A BREADTH-FIRST SEARCH ON THE VORONOI + if (IsBadTriangleAngle(middleAngleCorner.x, middleAngleCorner.y, largestAngleCorner.x, largestAngleCorner.y, neighborCircumcenter.x, neighborCircumcenter.y)) + { + // go back to circumcenter + dxFirstSuggestion = dx; + dyFirstSuggestion = dy; + } + else + { + // we are not creating a bad triangle + // neighbor's circumcenter is suggested + dxFirstSuggestion = voronoiOrInter[2] - torg.x; + dyFirstSuggestion = voronoiOrInter[3] - torg.y; + } + } + } + else + { // there is no voronoi vertex between intersection point and circumcenter + //-----------------hale new continues 2-----------------// + // now check if the line intersection is between cc and intersection point + PointBetweenPoints(inter_x, inter_y, myCircumcenter.x, myCircumcenter.y, line_inter_x, line_inter_y, ref line_result); + if (Math.Abs(line_result[0] - 1.0) <= EPS && line_p[0] > 0.0) + { + // check if we can go further by picking the slab line and petal intersection + // calculate the distance to the smallest angle corner + if (((smallestAngleCorner.x - petal_slab_inter_x_first) * (smallestAngleCorner.x - petal_slab_inter_x_first) + + (smallestAngleCorner.y - petal_slab_inter_y_first) * (smallestAngleCorner.y - petal_slab_inter_y_first) > + lengthConst * ((smallestAngleCorner.x - line_inter_x) * + (smallestAngleCorner.x - line_inter_x) + + (smallestAngleCorner.y - line_inter_y) * + (smallestAngleCorner.y - line_inter_y))) + && (IsBadTriangleAngle(middleAngleCorner.x, middleAngleCorner.y, largestAngleCorner.x, largestAngleCorner.y, petal_slab_inter_x_first, petal_slab_inter_y_first)) + && MinDistanceToNeighbor(petal_slab_inter_x_first, petal_slab_inter_y_first, ref neighborotri) > MinDistanceToNeighbor(line_inter_x, line_inter_y, ref neighborotri)) + { + //slab and petal intersection is advised + dxFirstSuggestion = petal_slab_inter_x_first - torg.x; + dyFirstSuggestion = petal_slab_inter_y_first - torg.y; + } + else + { // slab intersection point is further away + if (IsBadTriangleAngle(largestAngleCorner.x, largestAngleCorner.y, middleAngleCorner.x, middleAngleCorner.y, line_inter_x, line_inter_y)) + { + // apply perturbation + // find the distance between circumcenter and intersection point + d = Math.Sqrt((line_inter_x - myCircumcenter.x) * (line_inter_x - myCircumcenter.x) + + (line_inter_y - myCircumcenter.y) * (line_inter_y - myCircumcenter.y)); + // then find the vector going from intersection point to circumcenter + ax = myCircumcenter.x - line_inter_x; + ay = myCircumcenter.y - line_inter_y; + + ax = ax / d; + ay = ay / d; + // now calculate the new intersection point which is perturbated towards the circumcenter + line_inter_x = line_inter_x + ax * pertConst * Math.Sqrt(shortestEdgeDist); + line_inter_y = line_inter_y + ay * pertConst * Math.Sqrt(shortestEdgeDist); + if (IsBadTriangleAngle(middleAngleCorner.x, middleAngleCorner.y, largestAngleCorner.x, largestAngleCorner.y, line_inter_x, line_inter_y)) + { + // go back to circumcenter + dxFirstSuggestion = dx; + dyFirstSuggestion = dy; + } + else + { + // intersection point is suggested + dxFirstSuggestion = line_inter_x - torg.x; + dyFirstSuggestion = line_inter_y - torg.y; + } + } + else + {// we are not creating a bad triangle + // slab intersection is advised + dxFirstSuggestion = line_result[2] - torg.x; + dyFirstSuggestion = line_result[3] - torg.y; + } + } + //------------------------------------------------------// + } + else + { + if (IsBadTriangleAngle(largestAngleCorner.x, largestAngleCorner.y, middleAngleCorner.x, middleAngleCorner.y, inter_x, inter_y)) + { + //printf("testtriangle returned false! bad triangle\n"); + // if it is inside feasible region, then insert v2 + // apply perturbation + // find the distance between circumcenter and intersection point + d = Math.Sqrt((inter_x - myCircumcenter.x) * (inter_x - myCircumcenter.x) + + (inter_y - myCircumcenter.y) * (inter_y - myCircumcenter.y)); + // then find the vector going from intersection point to circumcenter + ax = myCircumcenter.x - inter_x; + ay = myCircumcenter.y - inter_y; + + ax = ax / d; + ay = ay / d; + // now calculate the new intersection point which is perturbated towards the circumcenter + inter_x = inter_x + ax * pertConst * Math.Sqrt(shortestEdgeDist); + inter_y = inter_y + ay * pertConst * Math.Sqrt(shortestEdgeDist); + if (IsBadTriangleAngle(middleAngleCorner.x, middleAngleCorner.y, largestAngleCorner.x, largestAngleCorner.y, inter_x, inter_y)) + { + // go back to circumcenter + dxFirstSuggestion = dx; + dyFirstSuggestion = dy; + } + else + { + // intersection point is suggested + dxFirstSuggestion = inter_x - torg.x; + dyFirstSuggestion = inter_y - torg.y; + } + } + else + { + // intersection point is suggested + dxFirstSuggestion = inter_x - torg.x; + dyFirstSuggestion = inter_y - torg.y; + } + } + } + /// if it is an acute triangle, check if it is a good enough location /// + // for acute triangle case, we need to check if it is ok to use either of them + if ((smallestAngleCorner.x - myCircumcenter.x) * (smallestAngleCorner.x - myCircumcenter.x) + + (smallestAngleCorner.y - myCircumcenter.y) * (smallestAngleCorner.y - myCircumcenter.y) > + lengthConst * ((smallestAngleCorner.x - (dxFirstSuggestion + torg.x)) * + (smallestAngleCorner.x - (dxFirstSuggestion + torg.x)) + + (smallestAngleCorner.y - (dyFirstSuggestion + torg.y)) * + (smallestAngleCorner.y - (dyFirstSuggestion + torg.y)))) + { + // use circumcenter + dxFirstSuggestion = dx; + dyFirstSuggestion = dy; + + }// else we stick to what we have found + }// intersection point + + }// if it is on the boundary, meaning no neighbor triangle in this direction, try other direction + + /// DO THE SAME THING FOR THE OTHER DIRECTION /// + /// find the third point of the neighbor triangle /// + neighborNotFound_second = GetNeighborsVertex(badotri, largestAngleCorner.x, largestAngleCorner.y, + smallestAngleCorner.x, smallestAngleCorner.y, ref thirdPoint, ref neighborotri); + /// find the circumcenter of the neighbor triangle /// + dxSecondSuggestion = dx; // if we cannot find any appropriate suggestion, we use circumcenter + dySecondSuggestion = dy; + + /// choose the correct intersection point /// + // calculate middle point of the longest edge(bisector) + xMidOfMiddleEdge = (largestAngleCorner.x + smallestAngleCorner.x) / 2.0; + yMidOfMiddleEdge = (largestAngleCorner.y + smallestAngleCorner.y) / 2.0; + // if there is a neighbor triangle + if (!neighborNotFound_second) + { + neighborvertex_1 = neighborotri.Org(); + neighborvertex_2 = neighborotri.Dest(); + neighborvertex_3 = neighborotri.Apex(); + // now calculate neighbor's circumcenter which is the voronoi site + neighborCircumcenter = predicates.FindCircumcenter(neighborvertex_1, neighborvertex_2, neighborvertex_3, + ref xi_tmp, ref eta_tmp); + + /// compute petal and Voronoi edge intersection /// + // in order to avoid degenerate cases, we need to do a vector based calculation for line + vector_x = (largestAngleCorner.y - smallestAngleCorner.y);//(-y, x) + vector_y = smallestAngleCorner.x - largestAngleCorner.x; + vector_x = myCircumcenter.x + vector_x; + vector_y = myCircumcenter.y + vector_y; + + + // by intersecting bisectors you will end up with the one you want to walk on + // then this line and circle should be intersected + CircleLineIntersection(myCircumcenter.x, myCircumcenter.y, vector_x, vector_y, + xPetalCtr, yPetalCtr, petalRadius, ref p); + + // we need to find correct intersection point, since line intersects circle twice + // this direction is always ACUTE + isCorrect = ChooseCorrectPoint(xMidOfMiddleEdge, yMidOfMiddleEdge, p[3], p[4], + myCircumcenter.x, myCircumcenter.y, false/*(isObtuse+1)%2*/); + // make sure which point is the correct one to be considered + if (isCorrect) + { + inter_x = p[3]; + inter_y = p[4]; + } + else + { + inter_x = p[1]; + inter_y = p[2]; + } + //----------------------hale new second direction:for slab calculation---------------// + // calculate the intersection of angle lines and Voronoi + linepnt1_x = largestAngleCorner.x; + linepnt1_y = largestAngleCorner.y; + // vector from largestAngleCorner to middleAngleCorner + line_vector_x = middleAngleCorner.x - largestAngleCorner.x; + line_vector_y = middleAngleCorner.y - largestAngleCorner.y; + // rotate the vector around largestAngleCorner in ccw by maxangle degrees + linepnt2_x = petal_slab_inter_x_second; + linepnt2_y = petal_slab_inter_y_second; + // now calculate the intersection of two lines + LineLineIntersection(myCircumcenter.x, myCircumcenter.y, vector_x, vector_y, linepnt1_x, linepnt1_y, linepnt2_x, linepnt2_y, ref line_p); + // check if there is a suitable intersection + if (line_p[0] > 0.0) + { + line_inter_x = line_p[1]; + line_inter_y = line_p[2]; + } + else + { + // for debugging (to make sure) + //printf("1) No intersection between two lines!!!\n"); + //printf("(%.14f,%.14f) (%.14f,%.14f) (%.14f,%.14f) (%.14f,%.14f)\n",myCircumcenter.x,myCircumcenter.y,vector_x,vector_y,linepnt1_x,linepnt1_y,linepnt2_x,linepnt2_y); + } + //---------------------------------------------------------------------// + /// check if there is a Voronoi vertex between before intersection /// + // check if the voronoi vertex is between the intersection and circumcenter + PointBetweenPoints(inter_x, inter_y, myCircumcenter.x, myCircumcenter.y, + neighborCircumcenter.x, neighborCircumcenter.y, ref voronoiOrInter); + /// determine the point to be suggested /// + if (p[0] > 0.0) + { // there is at least one intersection point + // if it is between circumcenter and intersection + // if it returns 1.0 this means we have a voronoi vertex within feasible region + if (Math.Abs(voronoiOrInter[0] - 1.0) <= EPS) + { + //-----------------hale new continues 1------------------// + // now check if the line intersection is between cc and voronoi + PointBetweenPoints(voronoiOrInter[2], voronoiOrInter[3], myCircumcenter.x, myCircumcenter.y, line_inter_x, line_inter_y, ref line_result); + if (Math.Abs(line_result[0] - 1.0) <= EPS && line_p[0] > 0.0) + { + // check if we can go further by picking the slab line and petal intersection + // calculate the distance to the smallest angle corner + // + if (((smallestAngleCorner.x - petal_slab_inter_x_second) * (smallestAngleCorner.x - petal_slab_inter_x_second) + + (smallestAngleCorner.y - petal_slab_inter_y_second) * (smallestAngleCorner.y - petal_slab_inter_y_second) > + lengthConst * ((smallestAngleCorner.x - line_inter_x) * + (smallestAngleCorner.x - line_inter_x) + + (smallestAngleCorner.y - line_inter_y) * + (smallestAngleCorner.y - line_inter_y))) + && (IsBadTriangleAngle(middleAngleCorner.x, middleAngleCorner.y, largestAngleCorner.x, largestAngleCorner.y, petal_slab_inter_x_second, petal_slab_inter_y_second)) + && MinDistanceToNeighbor(petal_slab_inter_x_second, petal_slab_inter_y_second, ref neighborotri) > MinDistanceToNeighbor(line_inter_x, line_inter_y, ref neighborotri)) + { + // slab and petal intersection is advised + dxSecondSuggestion = petal_slab_inter_x_second - torg.x; + dySecondSuggestion = petal_slab_inter_y_second - torg.y; + } + else + { // slab intersection point is further away + if (IsBadTriangleAngle(middleAngleCorner.x, middleAngleCorner.y, largestAngleCorner.x, largestAngleCorner.y, line_inter_x, line_inter_y)) + { + // apply perturbation + // find the distance between circumcenter and intersection point + d = Math.Sqrt((line_inter_x - myCircumcenter.x) * (line_inter_x - myCircumcenter.x) + + (line_inter_y - myCircumcenter.y) * (line_inter_y - myCircumcenter.y)); + // then find the vector going from intersection point to circumcenter + ax = myCircumcenter.x - line_inter_x; + ay = myCircumcenter.y - line_inter_y; + + ax = ax / d; + ay = ay / d; + // now calculate the new intersection point which is perturbated towards the circumcenter + line_inter_x = line_inter_x + ax * pertConst * Math.Sqrt(shortestEdgeDist); + line_inter_y = line_inter_y + ay * pertConst * Math.Sqrt(shortestEdgeDist); + if (IsBadTriangleAngle(middleAngleCorner.x, middleAngleCorner.y, largestAngleCorner.x, largestAngleCorner.y, line_inter_x, line_inter_y)) + { + // go back to circumcenter + dxSecondSuggestion = dx; + dySecondSuggestion = dy; + } + else + { + // intersection point is suggested + dxSecondSuggestion = line_inter_x - torg.x; + dySecondSuggestion = line_inter_y - torg.y; + + } + } + else + {// we are not creating a bad triangle + // slab intersection is advised + dxSecondSuggestion = line_result[2] - torg.x; + dySecondSuggestion = line_result[3] - torg.y; + } + } + //------------------------------------------------------// + } + else + { + if (IsBadTriangleAngle(middleAngleCorner.x, middleAngleCorner.y, largestAngleCorner.x, largestAngleCorner.y, neighborCircumcenter.x, neighborCircumcenter.y)) + { + // go back to circumcenter + dxSecondSuggestion = dx; + dySecondSuggestion = dy; + } + else + { // we are not creating a bad triangle + // neighbor's circumcenter is suggested + dxSecondSuggestion = voronoiOrInter[2] - torg.x; + dySecondSuggestion = voronoiOrInter[3] - torg.y; + } + } + } + else + { // there is no voronoi vertex between intersection point and circumcenter + //-----------------hale new continues 2-----------------// + // now check if the line intersection is between cc and intersection point + PointBetweenPoints(inter_x, inter_y, myCircumcenter.x, myCircumcenter.y, line_inter_x, line_inter_y, ref line_result); + if (Math.Abs(line_result[0] - 1.0) <= EPS && line_p[0] > 0.0) + { + // check if we can go further by picking the slab line and petal intersection + // calculate the distance to the smallest angle corner + if (((smallestAngleCorner.x - petal_slab_inter_x_second) * (smallestAngleCorner.x - petal_slab_inter_x_second) + + (smallestAngleCorner.y - petal_slab_inter_y_second) * (smallestAngleCorner.y - petal_slab_inter_y_second) > + lengthConst * ((smallestAngleCorner.x - line_inter_x) * + (smallestAngleCorner.x - line_inter_x) + + (smallestAngleCorner.y - line_inter_y) * + (smallestAngleCorner.y - line_inter_y))) + && (IsBadTriangleAngle(middleAngleCorner.x, middleAngleCorner.y, largestAngleCorner.x, largestAngleCorner.y, petal_slab_inter_x_second, petal_slab_inter_y_second)) + && MinDistanceToNeighbor(petal_slab_inter_x_second, petal_slab_inter_y_second, ref neighborotri) > MinDistanceToNeighbor(line_inter_x, line_inter_y, ref neighborotri)) + { + // slab and petal intersection is advised + dxSecondSuggestion = petal_slab_inter_x_second - torg.x; + dySecondSuggestion = petal_slab_inter_y_second - torg.y; + } + else + { // slab intersection point is further away ; + if (IsBadTriangleAngle(largestAngleCorner.x, largestAngleCorner.y, middleAngleCorner.x, middleAngleCorner.y, line_inter_x, line_inter_y)) + { + // apply perturbation + // find the distance between circumcenter and intersection point + d = Math.Sqrt((line_inter_x - myCircumcenter.x) * (line_inter_x - myCircumcenter.x) + + (line_inter_y - myCircumcenter.y) * (line_inter_y - myCircumcenter.y)); + // then find the vector going from intersection point to circumcenter + ax = myCircumcenter.x - line_inter_x; + ay = myCircumcenter.y - line_inter_y; + + ax = ax / d; + ay = ay / d; + // now calculate the new intersection point which is perturbated towards the circumcenter + line_inter_x = line_inter_x + ax * pertConst * Math.Sqrt(shortestEdgeDist); + line_inter_y = line_inter_y + ay * pertConst * Math.Sqrt(shortestEdgeDist); + if (IsBadTriangleAngle(middleAngleCorner.x, middleAngleCorner.y, largestAngleCorner.x, largestAngleCorner.y, line_inter_x, line_inter_y)) + { + // go back to circumcenter + dxSecondSuggestion = dx; + dySecondSuggestion = dy; + } + else + { + // intersection point is suggested + dxSecondSuggestion = line_inter_x - torg.x; + dySecondSuggestion = line_inter_y - torg.y; + } + } + else + { + // we are not creating a bad triangle + // slab intersection is advised + dxSecondSuggestion = line_result[2] - torg.x; + dySecondSuggestion = line_result[3] - torg.y; + } + } + //------------------------------------------------------// + } + else + { + if (IsBadTriangleAngle(middleAngleCorner.x, middleAngleCorner.y, largestAngleCorner.x, largestAngleCorner.y, inter_x, inter_y)) + { + // if it is inside feasible region, then insert v2 + // apply perturbation + // find the distance between circumcenter and intersection point + d = Math.Sqrt((inter_x - myCircumcenter.x) * (inter_x - myCircumcenter.x) + + (inter_y - myCircumcenter.y) * (inter_y - myCircumcenter.y)); + // then find the vector going from intersection point to circumcenter + ax = myCircumcenter.x - inter_x; + ay = myCircumcenter.y - inter_y; + + ax = ax / d; + ay = ay / d; + // now calculate the new intersection point which is perturbated towards the circumcenter + inter_x = inter_x + ax * pertConst * Math.Sqrt(shortestEdgeDist); + inter_y = inter_y + ay * pertConst * Math.Sqrt(shortestEdgeDist); + if (IsBadTriangleAngle(middleAngleCorner.x, middleAngleCorner.y, largestAngleCorner.x, largestAngleCorner.y, inter_x, inter_y)) + { + // go back to circumcenter + dxSecondSuggestion = dx; + dySecondSuggestion = dy; + } + else + { + // intersection point is suggested + dxSecondSuggestion = inter_x - torg.x; + dySecondSuggestion = inter_y - torg.y; + } + } + else + { + // intersection point is suggested + dxSecondSuggestion = inter_x - torg.x; + dySecondSuggestion = inter_y - torg.y; + } + } + } + + /// if it is an acute triangle, check if it is a good enough location /// + // for acute triangle case, we need to check if it is ok to use either of them + if ((smallestAngleCorner.x - myCircumcenter.x) * (smallestAngleCorner.x - myCircumcenter.x) + + (smallestAngleCorner.y - myCircumcenter.y) * (smallestAngleCorner.y - myCircumcenter.y) > + lengthConst * ((smallestAngleCorner.x - (dxSecondSuggestion + torg.x)) * + (smallestAngleCorner.x - (dxSecondSuggestion + torg.x)) + + (smallestAngleCorner.y - (dySecondSuggestion + torg.y)) * + (smallestAngleCorner.y - (dySecondSuggestion + torg.y)))) + { + // use circumcenter + dxSecondSuggestion = dx; + dySecondSuggestion = dy; + + }// else we stick on what we have found + } + }// if it is on the boundary, meaning no neighbor triangle in this direction, the other direction might be ok + if (isObtuse) + { + if (neighborNotFound_first && neighborNotFound_second) + { + //obtuse: check if the other direction works + if (justAcute * ((smallestAngleCorner.x - (xMidOfMiddleEdge)) * + (smallestAngleCorner.x - (xMidOfMiddleEdge)) + + (smallestAngleCorner.y - (yMidOfMiddleEdge)) * + (smallestAngleCorner.y - (yMidOfMiddleEdge))) > + (smallestAngleCorner.x - (xMidOfLongestEdge)) * + (smallestAngleCorner.x - (xMidOfLongestEdge)) + + (smallestAngleCorner.y - (yMidOfLongestEdge)) * + (smallestAngleCorner.y - (yMidOfLongestEdge))) + { + dx = dxSecondSuggestion; + dy = dySecondSuggestion; + } + else + { + dx = dxFirstSuggestion; + dy = dyFirstSuggestion; + } + } + else if (neighborNotFound_first) + { + //obtuse: check if the other direction works + if (justAcute * ((smallestAngleCorner.x - (dxSecondSuggestion + torg.x)) * + (smallestAngleCorner.x - (dxSecondSuggestion + torg.x)) + + (smallestAngleCorner.y - (dySecondSuggestion + torg.y)) * + (smallestAngleCorner.y - (dySecondSuggestion + torg.y))) > + (smallestAngleCorner.x - (xMidOfLongestEdge)) * + (smallestAngleCorner.x - (xMidOfLongestEdge)) + + (smallestAngleCorner.y - (yMidOfLongestEdge)) * + (smallestAngleCorner.y - (yMidOfLongestEdge))) + { + dx = dxSecondSuggestion; + dy = dySecondSuggestion; + } + else + { + dx = dxFirstSuggestion; + dy = dyFirstSuggestion; + } + } + else if (neighborNotFound_second) + { + //obtuse: check if the other direction works + if (justAcute * ((smallestAngleCorner.x - (xMidOfMiddleEdge)) * + (smallestAngleCorner.x - (xMidOfMiddleEdge)) + + (smallestAngleCorner.y - (yMidOfMiddleEdge)) * + (smallestAngleCorner.y - (yMidOfMiddleEdge))) > + (smallestAngleCorner.x - (dxFirstSuggestion + torg.x)) * + (smallestAngleCorner.x - (dxFirstSuggestion + torg.x)) + + (smallestAngleCorner.y - (dyFirstSuggestion + torg.y)) * + (smallestAngleCorner.y - (dyFirstSuggestion + torg.y))) + { + dx = dxSecondSuggestion; + dy = dySecondSuggestion; + } + else + { + dx = dxFirstSuggestion; + dy = dyFirstSuggestion; + } + } + else + { + //obtuse: check if the other direction works + if (justAcute * ((smallestAngleCorner.x - (dxSecondSuggestion + torg.x)) * + (smallestAngleCorner.x - (dxSecondSuggestion + torg.x)) + + (smallestAngleCorner.y - (dySecondSuggestion + torg.y)) * + (smallestAngleCorner.y - (dySecondSuggestion + torg.y))) > + (smallestAngleCorner.x - (dxFirstSuggestion + torg.x)) * + (smallestAngleCorner.x - (dxFirstSuggestion + torg.x)) + + (smallestAngleCorner.y - (dyFirstSuggestion + torg.y)) * + (smallestAngleCorner.y - (dyFirstSuggestion + torg.y))) + { + dx = dxSecondSuggestion; + dy = dySecondSuggestion; + } + else + { + dx = dxFirstSuggestion; + dy = dyFirstSuggestion; + } + } + } + else + { // acute : consider other direction + if (neighborNotFound_first && neighborNotFound_second) + { + //obtuse: check if the other direction works + if (justAcute * ((smallestAngleCorner.x - (xMidOfMiddleEdge)) * + (smallestAngleCorner.x - (xMidOfMiddleEdge)) + + (smallestAngleCorner.y - (yMidOfMiddleEdge)) * + (smallestAngleCorner.y - (yMidOfMiddleEdge))) > + (smallestAngleCorner.x - (xMidOfLongestEdge)) * + (smallestAngleCorner.x - (xMidOfLongestEdge)) + + (smallestAngleCorner.y - (yMidOfLongestEdge)) * + (smallestAngleCorner.y - (yMidOfLongestEdge))) + { + dx = dxSecondSuggestion; + dy = dySecondSuggestion; + } + else + { + dx = dxFirstSuggestion; + dy = dyFirstSuggestion; + } + } + else if (neighborNotFound_first) + { + //obtuse: check if the other direction works + if (justAcute * ((smallestAngleCorner.x - (dxSecondSuggestion + torg.x)) * + (smallestAngleCorner.x - (dxSecondSuggestion + torg.x)) + + (smallestAngleCorner.y - (dySecondSuggestion + torg.y)) * + (smallestAngleCorner.y - (dySecondSuggestion + torg.y))) > + (smallestAngleCorner.x - (xMidOfLongestEdge)) * + (smallestAngleCorner.x - (xMidOfLongestEdge)) + + (smallestAngleCorner.y - (yMidOfLongestEdge)) * + (smallestAngleCorner.y - (yMidOfLongestEdge))) + { + dx = dxSecondSuggestion; + dy = dySecondSuggestion; + } + else + { + dx = dxFirstSuggestion; + dy = dyFirstSuggestion; + } + } + else if (neighborNotFound_second) + { + //obtuse: check if the other direction works + if (justAcute * ((smallestAngleCorner.x - (xMidOfMiddleEdge)) * + (smallestAngleCorner.x - (xMidOfMiddleEdge)) + + (smallestAngleCorner.y - (yMidOfMiddleEdge)) * + (smallestAngleCorner.y - (yMidOfMiddleEdge))) > + (smallestAngleCorner.x - (dxFirstSuggestion + torg.x)) * + (smallestAngleCorner.x - (dxFirstSuggestion + torg.x)) + + (smallestAngleCorner.y - (dyFirstSuggestion + torg.y)) * + (smallestAngleCorner.y - (dyFirstSuggestion + torg.y))) + { + dx = dxSecondSuggestion; + dy = dySecondSuggestion; + } + else + { + dx = dxFirstSuggestion; + dy = dyFirstSuggestion; + } + } + else + { + //obtuse: check if the other direction works + if (justAcute * ((smallestAngleCorner.x - (dxSecondSuggestion + torg.x)) * + (smallestAngleCorner.x - (dxSecondSuggestion + torg.x)) + + (smallestAngleCorner.y - (dySecondSuggestion + torg.y)) * + (smallestAngleCorner.y - (dySecondSuggestion + torg.y))) > + (smallestAngleCorner.x - (dxFirstSuggestion + torg.x)) * + (smallestAngleCorner.x - (dxFirstSuggestion + torg.x)) + + (smallestAngleCorner.y - (dyFirstSuggestion + torg.y)) * + (smallestAngleCorner.y - (dyFirstSuggestion + torg.y))) + { + dx = dxSecondSuggestion; + dy = dySecondSuggestion; + } + else + { + dx = dxFirstSuggestion; + dy = dyFirstSuggestion; + } + } + + }// end if obtuse + }// end of relocation + }// end of almostGood + + Point circumcenter = new Point(); + + if (relocated <= 0) + { + circumcenter.x = torg.x + dx; + circumcenter.y = torg.y + dy; + } + else + { + circumcenter.x = origin_x + dx; + circumcenter.y = origin_y + dy; + } + xi = (yao * dx - xao * dy) * (2.0 * denominator); + eta = (xdo * dy - ydo * dx) * (2.0 * denominator); + + return circumcenter; + } + + /// + /// Given square of edge lengths of a triangle, + // determine its orientation + /// + /// + /// + /// + /// Returns a number indicating an orientation. + private int LongestShortestEdge(double aodist, double dadist, double dodist) + { + // 123: shortest: aodist // 213: shortest: dadist // 312: shortest: dodist + // middle: dadist // middle: aodist // middle: aodist + // longest: dodist // longest: dodist // longest: dadist + // 132: shortest: aodist // 231: shortest: dadist // 321: shortest: dodist + // middle: dodist // middle: dodist // middle: dadist + // longest: dadist // longest: aodist // longest: aodist + + int max = 0, min = 0, mid = 0, minMidMax; + if (dodist < aodist && dodist < dadist) + { + min = 3; // apex is the smallest angle, dodist is the longest edge + if (aodist < dadist) + { + max = 2; // dadist is the longest edge + mid = 1; // aodist is the middle longest edge + } + else + { + max = 1; // aodist is the longest edge + mid = 2; // dadist is the middle longest edge + } + } + else if (aodist < dadist) + { + min = 1; // dest is the smallest angle, aodist is the biggest edge + if (dodist < dadist) + { + max = 2; // dadist is the longest edge + mid = 3; // dodist is the middle longest edge + } + else + { + max = 3; // dodist is the longest edge + mid = 2; // dadist is the middle longest edge + } + } + else + { + min = 2; // origin is the smallest angle, dadist is the biggest edge + if (aodist < dodist) + { + max = 3; // dodist is the longest edge + mid = 1; // aodist is the middle longest edge + } + else + { + max = 1; // aodist is the longest edge + mid = 3; // dodist is the middle longest edge + } + } + minMidMax = min * 100 + mid * 10 + max; + // HANDLE ISOSCELES TRIANGLE CASE + return minMidMax; + } + + /// + /// Checks if smothing is possible for a given bad triangle. + /// + /// + /// + /// + /// + /// The new location for the point, if somothing is possible. + /// Returns 1, 2 or 3 if smoothing will work, 0 otherwise. + private int DoSmoothing(Otri badotri, Vertex torg, Vertex tdest, Vertex tapex, + ref double[] newloc) + { + + int numpoints_p = 0;// keeps the number of points in a star of point p, q, r + int numpoints_q = 0; + int numpoints_r = 0; + //int i; + double[] possibilities = new double[6];//there can be more than one possibilities + int num_pos = 0; // number of possibilities + int flag1 = 0, flag2 = 0, flag3 = 0; + bool newLocFound = false; + + //vertex v1, v2, v3; // for ccw test + //double p1[2], p2[2], p3[2]; + //double temp[2]; + + //********************* TRY TO RELOCATE POINT "p" *************** + + // get the surrounding points of p, so this gives us the triangles + numpoints_p = GetStarPoints(badotri, torg, tdest, tapex, 1, ref points_p); + // check if the points in counterclockwise order + // p1[0] = points_p[0]; p1[1] = points_p[1]; + // p2[0] = points_p[2]; p2[1] = points_p[3]; + // p3[0] = points_p[4]; p3[1] = points_p[5]; + // v1 = (vertex)p1; v2 = (vertex)p2; v3 = (vertex)p3; + // if(counterclockwise(m,b,v1,v2,v3) < 0){ + // // reverse the order to ccw + // for(i = 0; i < numpoints_p/2; i++){ + // temp[0] = points_p[2*i]; + // temp[1] = points_p[2*i+1]; + // points_p[2*i] = points_p[2*(numpoints_p-1)-2*i]; + // points_p[2*i+1] = points_p[2*(numpoints_p-1)+1-2*i]; + // points_p[2*(numpoints_p-1)-2*i] = temp[0]; + // points_p[2*(numpoints_p-1)+1-2*i] = temp[1]; + // } + // } + // m.counterclockcount--; + // INTERSECTION OF PETALS + // first check whether the star angles are appropriate for relocation + if (torg.type == VertexType.FreeVertex && numpoints_p != 0 && ValidPolygonAngles(numpoints_p, points_p)) + { + //newLocFound = getPetalIntersection(m, b, numpoints_p, points_p, newloc); + //newLocFound = getPetalIntersectionBruteForce(m, b,numpoints_p, points_p, newloc,torg[0],torg[1]); + if (behavior.MaxAngle == 0.0) + { + newLocFound = GetWedgeIntersectionWithoutMaxAngle(numpoints_p, points_p, ref newloc); + } + else + { + newLocFound = GetWedgeIntersection(numpoints_p, points_p, ref newloc); + } + //printf("call petal intersection for p\n"); + // make sure the relocated point is a free vertex + if (newLocFound) + { + possibilities[0] = newloc[0];// something found + possibilities[1] = newloc[1]; + num_pos++;// increase the number of possibilities + flag1 = 1; + } + } + + //********************* TRY TO RELOCATE POINT "q" *************** + + // get the surrounding points of q, so this gives us the triangles + numpoints_q = GetStarPoints(badotri, torg, tdest, tapex, 2, ref points_q); + // // check if the points in counterclockwise order + // v1[0] = points_q[0]; v1[1] = points_q[1]; + // v2[0] = points_q[2]; v2[1] = points_q[3]; + // v3[0] = points_q[4]; v3[1] = points_q[5]; + // if(counterclockwise(m,b,v1,v2,v3) < 0){ + // // reverse the order to ccw + // for(i = 0; i < numpoints_q/2; i++){ + // temp[0] = points_q[2*i]; + // temp[1] = points_q[2*i+1]; + // points_q[2*i] = points_q[2*(numpoints_q-1)-2*i]; + // points_q[2*i+1] = points_q[2*(numpoints_q-1)+1-2*i]; + // points_q[2*(numpoints_q-1)-2*i] = temp[0]; + // points_q[2*(numpoints_q-1)+1-2*i] = temp[1]; + // } + // } + // m.counterclockcount--; + // INTERSECTION OF PETALS + // first check whether the star angles are appropriate for relocation + if (tdest.type == VertexType.FreeVertex && numpoints_q != 0 && ValidPolygonAngles(numpoints_q, points_q)) + { + //newLocFound = getPetalIntersection(m, b,numpoints_q, points_q, newloc); + //newLocFound = getPetalIntersectionBruteForce(m, b,numpoints_q, points_q, newloc,tapex[0],tapex[1]); + if (behavior.MaxAngle == 0.0) + { + newLocFound = GetWedgeIntersectionWithoutMaxAngle(numpoints_q, points_q, ref newloc); + } + else + { + newLocFound = GetWedgeIntersection(numpoints_q, points_q, ref newloc); + } + //printf("call petal intersection for q\n"); + + // make sure the relocated point is a free vertex + if (newLocFound) + { + possibilities[2] = newloc[0];// something found + possibilities[3] = newloc[1]; + num_pos++;// increase the number of possibilities + flag2 = 2; + } + } + + + //********************* TRY TO RELOCATE POINT "q" *************** + // get the surrounding points of r, so this gives us the triangles + numpoints_r = GetStarPoints(badotri, torg, tdest, tapex, 3, ref points_r); + // check if the points in counterclockwise order + // v1[0] = points_r[0]; v1[1] = points_r[1]; + // v2[0] = points_r[2]; v2[1] = points_r[3]; + // v3[0] = points_r[4]; v3[1] = points_r[5]; + // if(counterclockwise(m,b,v1,v2,v3) < 0){ + // // reverse the order to ccw + // for(i = 0; i < numpoints_r/2; i++){ + // temp[0] = points_r[2*i]; + // temp[1] = points_r[2*i+1]; + // points_r[2*i] = points_r[2*(numpoints_r-1)-2*i]; + // points_r[2*i+1] = points_r[2*(numpoints_r-1)+1-2*i]; + // points_r[2*(numpoints_r-1)-2*i] = temp[0]; + // points_r[2*(numpoints_r-1)+1-2*i] = temp[1]; + // } + // } + // m.counterclockcount--; + // INTERSECTION OF PETALS + // first check whether the star angles are appropriate for relocation + if (tapex.type == VertexType.FreeVertex && numpoints_r != 0 && ValidPolygonAngles(numpoints_r, points_r)) + { + //newLocFound = getPetalIntersection(m, b,numpoints_r, points_r, newloc); + //newLocFound = getPetalIntersectionBruteForce(m, b,numpoints_r, points_r, newloc,tdest[0],tdest[1]); + if (behavior.MaxAngle == 0.0) + { + newLocFound = GetWedgeIntersectionWithoutMaxAngle(numpoints_r, points_r, ref newloc); + } + else + { + newLocFound = GetWedgeIntersection(numpoints_r, points_r, ref newloc); + } + + //printf("call petal intersection for r\n"); + + + // make sure the relocated point is a free vertex + if (newLocFound) + { + possibilities[4] = newloc[0];// something found + possibilities[5] = newloc[1]; + num_pos++;// increase the number of possibilities + flag3 = 3; + } + } + //printf("numpossibilities %d\n",num_pos); + //////////// AFTER FINISH CHECKING EVERY POSSIBILITY, CHOOSE ANY OF THE AVAILABLE ONE ////////////////////// + if (num_pos > 0) + { + if (flag1 > 0) + { // suggest to relocate origin + newloc[0] = possibilities[0]; + newloc[1] = possibilities[1]; + return flag1; + + } + else + { + if (flag2 > 0) + { // suggest to relocate apex + newloc[0] = possibilities[2]; + newloc[1] = possibilities[3]; + return flag2; + + } + else + {// suggest to relocate destination + if (flag3 > 0) + { + newloc[0] = possibilities[4]; + newloc[1] = possibilities[5]; + return flag3; + + } + } + } + } + + return 0;// could not find any good relocation + } + + /// + /// Finds the star of a given point. + /// + /// + /// + /// + /// + /// + /// List of points on the star of the given point. + /// Number of points on the star of the given point. + private int GetStarPoints(Otri badotri, Vertex p, Vertex q, Vertex r, + int whichPoint, ref double[] points) + { + + Otri neighotri = default(Otri); // for return value of the function + Otri tempotri; // for temporary usage + double first_x = 0, first_y = 0; // keeps the first point to be considered + double second_x = 0, second_y = 0; // for determining the edge we will begin + double third_x = 0, third_y = 0; // termination + double[] returnPoint = new double[2]; // for keeping the returned point + int numvertices = 0; // for keeping number of surrounding vertices + + // first determine which point to be used to find its neighbor triangles + switch (whichPoint) + { + case 1: + first_x = p.x; // point at the center + first_y = p.y; + second_x = r.x; // second vertex of first edge to consider + second_y = r.y; + third_x = q.x; // for terminating the search + third_y = q.y; + break; + case 2: + first_x = q.x; // point at the center + first_y = q.y; + second_x = p.x; // second vertex of first edge to consider + second_y = p.y; + third_x = r.x; // for terminating the search + third_y = r.y; + break; + case 3: + first_x = r.x; // point at the center + first_y = r.y; + second_x = q.x; // second vertex of first edge to consider + second_y = q.y; + third_x = p.x; // for terminating the search + third_y = p.y; + break; + } + tempotri = badotri; + // add first point as the end of first edge + points[numvertices] = second_x; + numvertices++; + points[numvertices] = second_y; + numvertices++; + // assign as dummy value + returnPoint[0] = second_x; returnPoint[1] = second_y; + // until we reach the third point of the beginning triangle + do + { + // find the neighbor's third point where it is incident to given edge + if (!GetNeighborsVertex(tempotri, first_x, first_y, second_x, second_y, ref returnPoint, ref neighotri)) + { + // go to next triangle + tempotri = neighotri; + // now the second point is the neighbor's third vertex + second_x = returnPoint[0]; + second_y = returnPoint[1]; + // add a new point to the list of surrounding points + points[numvertices] = returnPoint[0]; + numvertices++; + points[numvertices] = returnPoint[1]; + numvertices++; + } + else + { + numvertices = 0; + break; + } + + } while (!((Math.Abs(returnPoint[0] - third_x) <= EPS) && + (Math.Abs(returnPoint[1] - third_y) <= EPS))); + return numvertices / 2; + + } + + /// + /// Gets a neighbours vertex. + /// + /// + /// + /// + /// + /// + /// Neighbor's third vertex incident to given edge. + /// Pointer for the neighbor triangle. + /// Returns true if vertex was found. + private bool GetNeighborsVertex(Otri badotri, + double first_x, double first_y, + double second_x, double second_y, + ref double[] thirdpoint, ref Otri neighotri) + { + + Otri neighbor = default(Otri); // keeps the neighbor triangles + bool notFound = false; // boolean variable if we can find that neighbor or not + + // for keeping the vertices of the neighbor triangle + Vertex neighborvertex_1 = null; + Vertex neighborvertex_2 = null; + Vertex neighborvertex_3 = null; + + // used for finding neighbor triangle + int firstVertexMatched = 0, secondVertexMatched = 0; // to find the correct neighbor + //triangle ptr; // Temporary variable used by sym() + //int i; // index variable + // find neighbors + // Check each of the triangle's three neighbors to find the correct one + for (badotri.orient = 0; badotri.orient < 3; badotri.orient++) + { + // Find the neighbor. + badotri.Sym(ref neighbor); + // check if it is the one we are looking for by checking the corners + // first check if the neighbor is nonexistent, since it can be on the border + if (neighbor.tri.id != Mesh.DUMMY) + { + // then check if two wanted corners are also in this triangle + // take the vertices of the candidate neighbor + neighborvertex_1 = neighbor.Org(); + neighborvertex_2 = neighbor.Dest(); + neighborvertex_3 = neighbor.Apex(); + + // check if it is really a triangle + if ((neighborvertex_1.x == neighborvertex_2.x && neighborvertex_1.y == neighborvertex_2.y) + || (neighborvertex_2.x == neighborvertex_3.x && neighborvertex_2.y == neighborvertex_3.y) + || (neighborvertex_1.x == neighborvertex_3.x && neighborvertex_1.y == neighborvertex_3.y)) + { + //printf("Two vertices are the same!!!!!!!\n"); + } + else + { + // begin searching for the correct neighbor triangle + firstVertexMatched = 0; + if ((Math.Abs(first_x - neighborvertex_1.x) < EPS) && + (Math.Abs(first_y - neighborvertex_1.y) < EPS)) + { + firstVertexMatched = 11; // neighbor's 1st vertex is matched to first vertex + + } + else if ((Math.Abs(first_x - neighborvertex_2.x) < EPS) && + (Math.Abs(first_y - neighborvertex_2.y) < EPS)) + { + firstVertexMatched = 12; // neighbor's 2nd vertex is matched to first vertex + + } + else if ((Math.Abs(first_x - neighborvertex_3.x) < EPS) && + (Math.Abs(first_y - neighborvertex_3.y) < EPS)) + { + firstVertexMatched = 13; // neighbor's 3rd vertex is matched to first vertex + + }/*else{ + // none of them matched + } // end of first vertex matching */ + + secondVertexMatched = 0; + if ((Math.Abs(second_x - neighborvertex_1.x) < EPS) && + (Math.Abs(second_y - neighborvertex_1.y) < EPS)) + { + secondVertexMatched = 21; // neighbor's 1st vertex is matched to second vertex + } + else if ((Math.Abs(second_x - neighborvertex_2.x) < EPS) && + (Math.Abs(second_y - neighborvertex_2.y) < EPS)) + { + secondVertexMatched = 22; // neighbor's 2nd vertex is matched to second vertex + } + else if ((Math.Abs(second_x - neighborvertex_3.x) < EPS) && + (Math.Abs(second_y - neighborvertex_3.y) < EPS)) + { + secondVertexMatched = 23; // neighbor's 3rd vertex is matched to second vertex + }/*else{ + // none of them matched + } // end of second vertex matching*/ + + } + + }// if neighbor exists or not + + if (((firstVertexMatched == 11) && (secondVertexMatched == 22 || secondVertexMatched == 23)) + || ((firstVertexMatched == 12) && (secondVertexMatched == 21 || secondVertexMatched == 23)) + || ((firstVertexMatched == 13) && (secondVertexMatched == 21 || secondVertexMatched == 22))) + break; + }// end of for loop over all orientations + + switch (firstVertexMatched) + { + case 0: + notFound = true; + break; + case 11: + if (secondVertexMatched == 22) + { + thirdpoint[0] = neighborvertex_3.x; + thirdpoint[1] = neighborvertex_3.y; + } + else if (secondVertexMatched == 23) + { + thirdpoint[0] = neighborvertex_2.x; + thirdpoint[1] = neighborvertex_2.y; + } + else { notFound = true; } + break; + case 12: + if (secondVertexMatched == 21) + { + thirdpoint[0] = neighborvertex_3.x; + thirdpoint[1] = neighborvertex_3.y; + } + else if (secondVertexMatched == 23) + { + thirdpoint[0] = neighborvertex_1.x; + thirdpoint[1] = neighborvertex_1.y; + } + else { notFound = true; } + break; + case 13: + if (secondVertexMatched == 21) + { + thirdpoint[0] = neighborvertex_2.x; + thirdpoint[1] = neighborvertex_2.y; + } + else if (secondVertexMatched == 22) + { + thirdpoint[0] = neighborvertex_1.x; + thirdpoint[1] = neighborvertex_1.y; + } + else { notFound = true; } + break; + default: + if (secondVertexMatched == 0) { notFound = true; } + break; + } + // pointer of the neighbor triangle + neighotri = neighbor; + return notFound; + + } + + /// + /// Find a new point location by wedge intersection. + /// + /// + /// + /// A new location for the point according to surrounding points. + /// Returns true if new location found + private bool GetWedgeIntersectionWithoutMaxAngle(int numpoints, + double[] points, ref double[] newloc) + { + //double total_x = 0; + //double total_y = 0; + double x0, y0, x1, y1, x2, y2; + //double compConst = 0.01; // for comparing real numbers + + double x01, y01; + //double x12, y12; + + //double ax, ay, bx, by; //two intersections of two petals disks + + double d01;//, d12 + + //double petalx0, petaly0, petalr0, petalx1, petaly1, petalr1; + + //double p[5]; + + // Resize work arrays + if (2 * numpoints > petalx.Length) + { + petalx = new double[2 * numpoints]; + petaly = new double[2 * numpoints]; + petalr = new double[2 * numpoints]; + wedges = new double[2 * numpoints * 16 + 36]; + } + + double xmid, ymid, dist, x3, y3; + double x_1, y_1, x_2, y_2, x_3, y_3, x_4, y_4, tempx, tempy; + double ux, uy; + double alpha; + double[] p1 = new double[3]; + + //double poly_points; + int numpolypoints = 0; + + //int numBadTriangle; + + int i, j; + + int s, flag, count, num; + + double petalcenterconstant, petalradiusconstant; + + x0 = points[2 * numpoints - 4]; + y0 = points[2 * numpoints - 3]; + x1 = points[2 * numpoints - 2]; + y1 = points[2 * numpoints - 1]; + + // minimum angle + alpha = behavior.MinAngle * Math.PI / 180.0; + // initialize the constants + if (behavior.goodAngle == 1.0) + { + petalcenterconstant = 0; + petalradiusconstant = 0; + } + else + { + petalcenterconstant = 0.5 / Math.Tan(alpha); + petalradiusconstant = 0.5 / Math.Sin(alpha); + } + + for (i = 0; i < numpoints * 2; i = i + 2) + { + x2 = points[i]; + y2 = points[i + 1]; + + //printf("POLYGON POINTS (p,q) #%d (%.12f, %.12f) (%.12f, %.12f)\n", i/2, x0, y0,x1, y1); + + x01 = x1 - x0; + y01 = y1 - y0; + d01 = Math.Sqrt(x01 * x01 + y01 * y01); + // find the petal of each edge 01; + + // printf("PETAL CONSTANT (%.12f, %.12f)\n", + // b.petalcenterconstant, b.petalradiusconstant ); + // printf("PETAL DIFFS (%.6f, %.6f, %.4f)\n", x01, y01, d01); + + petalx[i / 2] = x0 + 0.5 * x01 - petalcenterconstant * y01; + petaly[i / 2] = y0 + 0.5 * y01 + petalcenterconstant * x01; + petalr[i / 2] = petalradiusconstant * d01; + petalx[numpoints + i / 2] = petalx[i / 2]; + petaly[numpoints + i / 2] = petaly[i / 2]; + petalr[numpoints + i / 2] = petalr[i / 2]; + //printf("PETAL POINTS #%d (%.12f, %.12f) R= %.12f\n", i/2, petalx[i/2],petaly[i/2], petalr[i/2]); + + /// FIRST FIND THE HALF-PLANE POINTS FOR EACH PETAL + xmid = (x0 + x1) / 2.0; // mid point of pq + ymid = (y0 + y1) / 2.0; + + // distance between xmid and petal center + dist = Math.Sqrt((petalx[i / 2] - xmid) * (petalx[i / 2] - xmid) + (petaly[i / 2] - ymid) * (petaly[i / 2] - ymid)); + // find the unit vector goes from mid point to petal center + ux = (petalx[i / 2] - xmid) / dist; + uy = (petaly[i / 2] - ymid) / dist; + // find the third point other than p and q + x3 = petalx[i / 2] + ux * petalr[i / 2]; + y3 = petaly[i / 2] + uy * petalr[i / 2]; + /// FIND THE LINE POINTS BY THE ROTATION MATRIX + // cw rotation matrix [cosX sinX; -sinX cosX] + // cw rotation about (x,y) [ux*cosX + uy*sinX + x - x*cosX - y*sinX; -ux*sinX + uy*cosX + y + x*sinX - y*cosX] + // ccw rotation matrix [cosX -sinX; sinX cosX] + // ccw rotation about (x,y) [ux*cosX - uy*sinX + x - x*cosX + y*sinX; ux*sinX + uy*cosX + y - x*sinX - y*cosX] + /// LINE #1: (x1,y1) & (x_1,y_1) + // vector from p to q + ux = x1 - x0; + uy = y1 - y0; + // rotate the vector around p = (x0,y0) in ccw by alpha degrees + x_1 = x1 * Math.Cos(alpha) - y1 * Math.Sin(alpha) + x0 - x0 * Math.Cos(alpha) + y0 * Math.Sin(alpha); + y_1 = x1 * Math.Sin(alpha) + y1 * Math.Cos(alpha) + y0 - x0 * Math.Sin(alpha) - y0 * Math.Cos(alpha); + // add these to wedges list as lines in order + wedges[i * 16] = x0; wedges[i * 16 + 1] = y0; + wedges[i * 16 + 2] = x_1; wedges[i * 16 + 3] = y_1; + //printf("LINE #1 (%.12f, %.12f) (%.12f, %.12f)\n", x0,y0,x_1,y_1); + /// LINE #2: (x2,y2) & (x_2,y_2) + // vector from p to q + ux = x0 - x1; + uy = y0 - y1; + // rotate the vector around q = (x1,y1) in cw by alpha degrees + x_2 = x0 * Math.Cos(alpha) + y0 * Math.Sin(alpha) + x1 - x1 * Math.Cos(alpha) - y1 * Math.Sin(alpha); + y_2 = -x0 * Math.Sin(alpha) + y0 * Math.Cos(alpha) + y1 + x1 * Math.Sin(alpha) - y1 * Math.Cos(alpha); + // add these to wedges list as lines in order + wedges[i * 16 + 4] = x_2; wedges[i * 16 + 5] = y_2; + wedges[i * 16 + 6] = x1; wedges[i * 16 + 7] = y1; + //printf("LINE #2 (%.12f, %.12f) (%.12f, %.12f)\n", x_2,y_2,x1,y1); + // vector from (petalx, petaly) to (x3,y3) + ux = x3 - petalx[i / 2]; + uy = y3 - petaly[i / 2]; + tempx = x3; tempy = y3; + /// LINE #3, #4, #5: (x3,y3) & (x_3,y_3) + for (j = 1; j < 4; j++) + { + // rotate the vector around (petalx,petaly) in cw by (60 - alpha)*j degrees + x_3 = x3 * Math.Cos((Math.PI / 3.0 - alpha) * j) + y3 * Math.Sin((Math.PI / 3.0 - alpha) * j) + petalx[i / 2] - petalx[i / 2] * Math.Cos((Math.PI / 3.0 - alpha) * j) - petaly[i / 2] * Math.Sin((Math.PI / 3.0 - alpha) * j); + y_3 = -x3 * Math.Sin((Math.PI / 3.0 - alpha) * j) + y3 * Math.Cos((Math.PI / 3.0 - alpha) * j) + petaly[i / 2] + petalx[i / 2] * Math.Sin((Math.PI / 3.0 - alpha) * j) - petaly[i / 2] * Math.Cos((Math.PI / 3.0 - alpha) * j); + // add these to wedges list as lines in order + wedges[i * 16 + 8 + 4 * (j - 1)] = x_3; wedges[i * 16 + 9 + 4 * (j - 1)] = y_3; + wedges[i * 16 + 10 + 4 * (j - 1)] = tempx; wedges[i * 16 + 11 + 4 * (j - 1)] = tempy; + tempx = x_3; tempy = y_3; + } + tempx = x3; tempy = y3; + /// LINE #6, #7, #8: (x3,y3) & (x_4,y_4) + for (j = 1; j < 4; j++) + { + // rotate the vector around (petalx,petaly) in ccw by (60 - alpha)*j degrees + x_4 = x3 * Math.Cos((Math.PI / 3.0 - alpha) * j) - y3 * Math.Sin((Math.PI / 3.0 - alpha) * j) + petalx[i / 2] - petalx[i / 2] * Math.Cos((Math.PI / 3.0 - alpha) * j) + petaly[i / 2] * Math.Sin((Math.PI / 3.0 - alpha) * j); + y_4 = x3 * Math.Sin((Math.PI / 3.0 - alpha) * j) + y3 * Math.Cos((Math.PI / 3.0 - alpha) * j) + petaly[i / 2] - petalx[i / 2] * Math.Sin((Math.PI / 3.0 - alpha) * j) - petaly[i / 2] * Math.Cos((Math.PI / 3.0 - alpha) * j); + + // add these to wedges list as lines in order + wedges[i * 16 + 20 + 4 * (j - 1)] = tempx; wedges[i * 16 + 21 + 4 * (j - 1)] = tempy; + wedges[i * 16 + 22 + 4 * (j - 1)] = x_4; wedges[i * 16 + 23 + 4 * (j - 1)] = y_4; + tempx = x_4; tempy = y_4; + } + //printf("LINE #3 (%.12f, %.12f) (%.12f, %.12f)\n", x_3,y_3,x3,y3); + //printf("LINE #4 (%.12f, %.12f) (%.12f, %.12f)\n", x3,y3,x_4,y_4); + + /// IF IT IS THE FIRST ONE, FIND THE CONVEX POLYGON + if (i == 0) + { + // line1 & line2: p1 + LineLineIntersection(x0, y0, x_1, y_1, x1, y1, x_2, y_2, ref p1); + if ((p1[0] == 1.0)) + { + // #0 + initialConvexPoly[0] = p1[1]; initialConvexPoly[1] = p1[2]; + // #1 + initialConvexPoly[2] = wedges[i * 16 + 16]; initialConvexPoly[3] = wedges[i * 16 + 17]; + // #2 + initialConvexPoly[4] = wedges[i * 16 + 12]; initialConvexPoly[5] = wedges[i * 16 + 13]; + // #3 + initialConvexPoly[6] = wedges[i * 16 + 8]; initialConvexPoly[7] = wedges[i * 16 + 9]; + // #4 + initialConvexPoly[8] = x3; initialConvexPoly[9] = y3; + // #5 + initialConvexPoly[10] = wedges[i * 16 + 22]; initialConvexPoly[11] = wedges[i * 16 + 23]; + // #6 + initialConvexPoly[12] = wedges[i * 16 + 26]; initialConvexPoly[13] = wedges[i * 16 + 27]; + // #7 + initialConvexPoly[14] = wedges[i * 16 + 30]; initialConvexPoly[15] = wedges[i * 16 + 31]; + //printf("INITIAL POLY [%.12f, %.12f;%.12f, %.12f;%.12f, %.12f;%.12f, %.12f;%.12f, %.12f;%.12f, %.12f;%.12f, %.12f;%.12f, %.12f]\n", initialConvexPoly[0],initialConvexPoly[1],initialConvexPoly[2],initialConvexPoly[3],initialConvexPoly[4],initialConvexPoly[5],initialConvexPoly[6],initialConvexPoly[7],initialConvexPoly[8],initialConvexPoly[9],initialConvexPoly[10],initialConvexPoly[11],initialConvexPoly[12],initialConvexPoly[13],initialConvexPoly[14],initialConvexPoly[15]); + } + } + + x0 = x1; y0 = y1; + x1 = x2; y1 = y2; + } + + /// HALF PLANE INTERSECTION: START SPLITTING THE INITIAL POLYGON TO FIND FEASIBLE REGION + if (numpoints != 0) + { + // first intersect the opposite located ones + s = (numpoints - 1) / 2 + 1; + flag = 0; + count = 0; + i = 1; + num = 8; + for (j = 0; j < 32; j = j + 4) + { + numpolypoints = HalfPlaneIntersection(num, ref initialConvexPoly, wedges[32 * s + j], wedges[32 * s + 1 + j], wedges[32 * s + 2 + j], wedges[32 * s + 3 + j]); + if (numpolypoints == 0) + return false; + else + num = numpolypoints; + } + count++; + while (count < numpoints - 1) + { + for (j = 0; j < 32; j = j + 4) + { + numpolypoints = HalfPlaneIntersection(num, ref initialConvexPoly, wedges[32 * (i + s * flag) + j], wedges[32 * (i + s * flag) + 1 + j], wedges[32 * (i + s * flag) + 2 + j], wedges[32 * (i + s * flag) + 3 + j]); + if (numpolypoints == 0) + return false; + else + num = numpolypoints; + } + i = i + flag; + flag = (flag + 1) % 2; + count++; + } + /// IF THERE IS A FEASIBLE INTERSECTION POLYGON, FIND ITS CENTROID AS THE NEW LOCATION + FindPolyCentroid(numpolypoints, initialConvexPoly, ref newloc); + + if (behavior.fixedArea) + { + // numBadTriangle = 0; + // for(j= 0; j < numpoints *2-2; j = j+2){ + // if(testTriangleAngleArea(m,b,&newloc[0],&newloc[1], &points[j], &points[j+1], &points[j+2], &points[j+3] )){ + // numBadTriangle++; + // } + // } + // if(testTriangleAngleArea(m,b, &newloc[0],&newloc[1], &points[0], &points[1], &points[numpoints*2-2], &points[numpoints*2-1] )){ + // numBadTriangle++; + // } + // + // if (numBadTriangle == 0) { + // + // return 1; + // } + } + else + { + //printf("yes, we found a feasible region num: %d newloc (%.12f,%.12f)\n", numpolypoints, newloc[0], newloc[1]); + // for(i = 0; i < 2*numpolypoints; i = i+2){ + // printf("point %d) (%.12f,%.12f)\n", i/2, initialConvexPoly[i], initialConvexPoly[i+1]); + // } + // printf("numpoints %d\n",numpoints); + return true; + } + } + + + return false; + } + + /// + /// Find a new point location by wedge intersection. + /// + /// + /// + /// A new location for the point according to surrounding points. + /// Returns true if new location found + private bool GetWedgeIntersection(int numpoints, double[] points, ref double[] newloc) + { + //double total_x = 0; + //double total_y = 0; + double x0, y0, x1, y1, x2, y2; + //double compConst = 0.01; // for comparing real numbers + + double x01, y01; + //double x12, y12; + + //double ax, ay, bx, by; //two intersections of two petals disks + + double d01;//, d12 + + //double petalx0, petaly1, petaly0, petalr0, petalx1, petalr1; + + //double p[5]; + + // Resize work arrays + if (2 * numpoints > petalx.Length) + { + petalx = new double[2 * numpoints]; + petaly = new double[2 * numpoints]; + petalr = new double[2 * numpoints]; + wedges = new double[2 * numpoints * 20 + 40]; + } + + double xmid, ymid, dist, x3, y3; + double x_1, y_1, x_2, y_2, x_3, y_3, x_4, y_4, tempx, tempy, x_5, y_5, x_6, y_6; + double ux, uy; + + double[] p1 = new double[3]; + double[] p2 = new double[3]; + double[] p3 = new double[3]; + double[] p4 = new double[3]; + + //double poly_points; + int numpolypoints = 0; + int howManyPoints = 0; // keeps the number of points used for representing the wedge + double line345 = 4.0, line789 = 4.0; // flag keeping which line to skip or construct + + int numBadTriangle; + + int i, j, k; + + int s, flag, count, num; + + int n, e; + + double weight; + + double petalcenterconstant, petalradiusconstant; + + x0 = points[2 * numpoints - 4]; + y0 = points[2 * numpoints - 3]; + x1 = points[2 * numpoints - 2]; + y1 = points[2 * numpoints - 1]; + + // minimum / maximum angle + double alpha, sinAlpha, cosAlpha, beta, sinBeta, cosBeta; + alpha = behavior.MinAngle * Math.PI / 180.0; + sinAlpha = Math.Sin(alpha); + cosAlpha = Math.Cos(alpha); + beta = behavior.MaxAngle * Math.PI / 180.0; + sinBeta = Math.Sin(beta); + cosBeta = Math.Cos(beta); + + // initialize the constants + if (behavior.goodAngle == 1.0) + { + petalcenterconstant = 0; + petalradiusconstant = 0; + } + else + { + petalcenterconstant = 0.5 / Math.Tan(alpha); + petalradiusconstant = 0.5 / Math.Sin(alpha); + } + + for (i = 0; i < numpoints * 2; i = i + 2) + { + // go to the next point + x2 = points[i]; + y2 = points[i + 1]; + + // printf("POLYGON POINTS (p,q) #%d (%.12f, %.12f) (%.12f, %.12f)\n", i/2, x0, y0,x1, y1); + + x01 = x1 - x0; + y01 = y1 - y0; + d01 = Math.Sqrt(x01 * x01 + y01 * y01); + // find the petal of each edge 01; + + // printf("PETAL CONSTANT (%.12f, %.12f)\n", + // b.petalcenterconstant, b.petalradiusconstant ); + // printf("PETAL DIFFS (%.6f, %.6f, %.4f)\n", x01, y01, d01); + //printf("i:%d numpoints:%d\n", i, numpoints); + petalx[i / 2] = x0 + 0.5 * x01 - petalcenterconstant * y01; + petaly[i / 2] = y0 + 0.5 * y01 + petalcenterconstant * x01; + petalr[i / 2] = petalradiusconstant * d01; + petalx[numpoints + i / 2] = petalx[i / 2]; + petaly[numpoints + i / 2] = petaly[i / 2]; + petalr[numpoints + i / 2] = petalr[i / 2]; + //printf("PETAL POINTS #%d (%.12f, %.12f) R= %.12f\n", i/2, petalx[i/2],petaly[i/2], petalr[i/2]); + + /// FIRST FIND THE HALF-PLANE POINTS FOR EACH PETAL + xmid = (x0 + x1) / 2.0; // mid point of pq + ymid = (y0 + y1) / 2.0; + + // distance between xmid and petal center + dist = Math.Sqrt((petalx[i / 2] - xmid) * (petalx[i / 2] - xmid) + (petaly[i / 2] - ymid) * (petaly[i / 2] - ymid)); + // find the unit vector goes from mid point to petal center + ux = (petalx[i / 2] - xmid) / dist; + uy = (petaly[i / 2] - ymid) / dist; + // find the third point other than p and q + x3 = petalx[i / 2] + ux * petalr[i / 2]; + y3 = petaly[i / 2] + uy * petalr[i / 2]; + /// FIND THE LINE POINTS BY THE ROTATION MATRIX + // cw rotation matrix [cosX sinX; -sinX cosX] + // cw rotation about (x,y) [ux*cosX + uy*sinX + x - x*cosX - y*sinX; -ux*sinX + uy*cosX + y + x*sinX - y*cosX] + // ccw rotation matrix [cosX -sinX; sinX cosX] + // ccw rotation about (x,y) [ux*cosX - uy*sinX + x - x*cosX + y*sinX; ux*sinX + uy*cosX + y - x*sinX - y*cosX] + /// LINE #1: (x1,y1) & (x_1,y_1) + // vector from p to q + ux = x1 - x0; + uy = y1 - y0; + // rotate the vector around p = (x0,y0) in ccw by alpha degrees + x_1 = x1 * cosAlpha - y1 * sinAlpha + x0 - x0 * cosAlpha + y0 * sinAlpha; + y_1 = x1 * sinAlpha + y1 * cosAlpha + y0 - x0 * sinAlpha - y0 * cosAlpha; + // add these to wedges list as lines in order + wedges[i * 20] = x0; wedges[i * 20 + 1] = y0; + wedges[i * 20 + 2] = x_1; wedges[i * 20 + 3] = y_1; + //printf("LINE #1 (%.12f, %.12f) (%.12f, %.12f)\n", x0,y0,x_1,y_1); + /// LINE #2: (x2,y2) & (x_2,y_2) + // vector from q to p + ux = x0 - x1; + uy = y0 - y1; + // rotate the vector around q = (x1,y1) in cw by alpha degrees + x_2 = x0 * cosAlpha + y0 * sinAlpha + x1 - x1 * cosAlpha - y1 * sinAlpha; + y_2 = -x0 * sinAlpha + y0 * cosAlpha + y1 + x1 * sinAlpha - y1 * cosAlpha; + // add these to wedges list as lines in order + wedges[i * 20 + 4] = x_2; wedges[i * 20 + 5] = y_2; + wedges[i * 20 + 6] = x1; wedges[i * 20 + 7] = y1; + //printf("LINE #2 (%.12f, %.12f) (%.12f, %.12f)\n", x_2,y_2,x1,y1); + // vector from (petalx, petaly) to (x3,y3) + ux = x3 - petalx[i / 2]; + uy = y3 - petaly[i / 2]; + tempx = x3; tempy = y3; + + /// DETERMINE HOW MANY POINTS TO USE ACCORDING TO THE MINANGLE-MAXANGLE COMBINATION + // petal center angle + alpha = (2.0 * behavior.MaxAngle + behavior.MinAngle - 180.0); + if (alpha <= 0.0) + {// when only angle lines needed + // 4 point case + howManyPoints = 4; + //printf("4 point case\n"); + line345 = 1.0; + line789 = 1.0; + } + else if (alpha <= 5.0) + {// when only angle lines plus two other lines are needed + // 6 point case + howManyPoints = 6; + //printf("6 point case\n"); + line345 = 2.0; + line789 = 2.0; + } + else if (alpha <= 10.0) + {// when we need more lines + // 8 point case + howManyPoints = 8; + line345 = 3.0; + line789 = 3.0; + //printf("8 point case\n"); + } + else + {// when we have a big wedge + // 10 point case + howManyPoints = 10; + //printf("10 point case\n"); + line345 = 4.0; + line789 = 4.0; + } + alpha = alpha * Math.PI / 180.0; + /// LINE #3, #4, #5: (x3,y3) & (x_3,y_3) + for (j = 1; j < line345; j++) + { + if (line345 == 1) + continue; + // rotate the vector around (petalx,petaly) in cw by (alpha/3.0)*j degrees + x_3 = x3 * Math.Cos((alpha / (line345 - 1.0)) * j) + y3 * Math.Sin(((alpha / (line345 - 1.0)) * j)) + petalx[i / 2] - petalx[i / 2] * Math.Cos(((alpha / (line345 - 1.0)) * j)) - petaly[i / 2] * Math.Sin(((alpha / (line345 - 1.0)) * j)); + y_3 = -x3 * Math.Sin(((alpha / (line345 - 1.0)) * j)) + y3 * Math.Cos(((alpha / (line345 - 1.0)) * j)) + petaly[i / 2] + petalx[i / 2] * Math.Sin(((alpha / (line345 - 1.0)) * j)) - petaly[i / 2] * Math.Cos(((alpha / (line345 - 1.0)) * j)); + // add these to wedges list as lines in order + wedges[i * 20 + 8 + 4 * (j - 1)] = x_3; wedges[i * 20 + 9 + 4 * (j - 1)] = y_3; + wedges[i * 20 + 10 + 4 * (j - 1)] = tempx; wedges[i * 20 + 11 + 4 * (j - 1)] = tempy; + tempx = x_3; tempy = y_3; + } + /// LINE #6: (x2,y2) & (x_3,y_3) + // vector from q to p + ux = x0 - x1; + uy = y0 - y1; + // rotate the vector around q = (x1,y1) in cw by alpha degrees + x_5 = x0 * cosBeta + y0 * sinBeta + x1 - x1 * cosBeta - y1 * sinBeta; + y_5 = -x0 * sinBeta + y0 * cosBeta + y1 + x1 * sinBeta - y1 * cosBeta; + wedges[i * 20 + 20] = x1; wedges[i * 20 + 21] = y1; + wedges[i * 20 + 22] = x_5; wedges[i * 20 + 23] = y_5; + + tempx = x3; tempy = y3; + /// LINE #7, #8, #9: (x3,y3) & (x_4,y_4) + for (j = 1; j < line789; j++) + { + if (line789 == 1) + continue; + // rotate the vector around (petalx,petaly) in ccw by (alpha/3.0)*j degrees + x_4 = x3 * Math.Cos((alpha / (line789 - 1.0)) * j) - y3 * Math.Sin((alpha / (line789 - 1.0)) * j) + petalx[i / 2] - petalx[i / 2] * Math.Cos((alpha / (line789 - 1.0)) * j) + petaly[i / 2] * Math.Sin((alpha / (line789 - 1.0)) * j); + y_4 = x3 * Math.Sin((alpha / (line789 - 1.0)) * j) + y3 * Math.Cos((alpha / (line789 - 1.0)) * j) + petaly[i / 2] - petalx[i / 2] * Math.Sin((alpha / (line789 - 1.0)) * j) - petaly[i / 2] * Math.Cos((alpha / (line789 - 1.0)) * j); + + // add these to wedges list as lines in order + wedges[i * 20 + 24 + 4 * (j - 1)] = tempx; wedges[i * 20 + 25 + 4 * (j - 1)] = tempy; + wedges[i * 20 + 26 + 4 * (j - 1)] = x_4; wedges[i * 20 + 27 + 4 * (j - 1)] = y_4; + tempx = x_4; tempy = y_4; + } + /// LINE #10: (x1,y1) & (x_3,y_3) + // vector from p to q + ux = x1 - x0; + uy = y1 - y0; + // rotate the vector around p = (x0,y0) in ccw by alpha degrees + x_6 = x1 * cosBeta - y1 * sinBeta + x0 - x0 * cosBeta + y0 * sinBeta; + y_6 = x1 * sinBeta + y1 * cosBeta + y0 - x0 * sinBeta - y0 * cosBeta; + wedges[i * 20 + 36] = x_6; wedges[i * 20 + 37] = y_6; + wedges[i * 20 + 38] = x0; wedges[i * 20 + 39] = y0; + + //printf("LINE #1 (%.12f, %.12f) (%.12f, %.12f)\n", x0,y0,x_1,y_1); + /// IF IT IS THE FIRST ONE, FIND THE CONVEX POLYGON + if (i == 0) + { + switch (howManyPoints) + { + case 4: + // line1 & line2 & line3 & line4 + LineLineIntersection(x0, y0, x_1, y_1, x1, y1, x_2, y_2, ref p1); + LineLineIntersection(x0, y0, x_1, y_1, x1, y1, x_5, y_5, ref p2); + LineLineIntersection(x0, y0, x_6, y_6, x1, y1, x_5, y_5, ref p3); + LineLineIntersection(x0, y0, x_6, y_6, x1, y1, x_2, y_2, ref p4); + if ((p1[0] == 1.0) && (p2[0] == 1.0) && (p3[0] == 1.0) && (p4[0] == 1.0)) + { + // #0 + initialConvexPoly[0] = p1[1]; initialConvexPoly[1] = p1[2]; + // #1 + initialConvexPoly[2] = p2[1]; initialConvexPoly[3] = p2[2]; + // #2 + initialConvexPoly[4] = p3[1]; initialConvexPoly[5] = p3[2]; + // #3 + initialConvexPoly[6] = p4[1]; initialConvexPoly[7] = p4[2]; + } + break; + case 6: + // line1 & line2 & line3 + LineLineIntersection(x0, y0, x_1, y_1, x1, y1, x_2, y_2, ref p1); + LineLineIntersection(x0, y0, x_1, y_1, x1, y1, x_5, y_5, ref p2); + LineLineIntersection(x0, y0, x_6, y_6, x1, y1, x_2, y_2, ref p3); + if ((p1[0] == 1.0) && (p2[0] == 1.0) && (p3[0] == 1.0)) + { + // #0 + initialConvexPoly[0] = p1[1]; initialConvexPoly[1] = p1[2]; + // #1 + initialConvexPoly[2] = p2[1]; initialConvexPoly[3] = p2[2]; + // #2 + initialConvexPoly[4] = wedges[i * 20 + 8]; initialConvexPoly[5] = wedges[i * 20 + 9]; + // #3 + initialConvexPoly[6] = x3; initialConvexPoly[7] = y3; + // #4 + initialConvexPoly[8] = wedges[i * 20 + 26]; initialConvexPoly[9] = wedges[i * 20 + 27]; + // #5 + initialConvexPoly[10] = p3[1]; initialConvexPoly[11] = p3[2]; + } + break; + case 8: + // line1 & line2: p1 + LineLineIntersection(x0, y0, x_1, y_1, x1, y1, x_2, y_2, ref p1); + LineLineIntersection(x0, y0, x_1, y_1, x1, y1, x_5, y_5, ref p2); + LineLineIntersection(x0, y0, x_6, y_6, x1, y1, x_2, y_2, ref p3); + if ((p1[0] == 1.0) && (p2[0] == 1.0) && (p3[0] == 1.0)) + { + // #0 + initialConvexPoly[0] = p1[1]; initialConvexPoly[1] = p1[2]; + // #1 + initialConvexPoly[2] = p2[1]; initialConvexPoly[3] = p2[2]; + // #2 + initialConvexPoly[4] = wedges[i * 20 + 12]; initialConvexPoly[5] = wedges[i * 20 + 13]; + // #3 + initialConvexPoly[6] = wedges[i * 20 + 8]; initialConvexPoly[7] = wedges[i * 20 + 9]; + // #4 + initialConvexPoly[8] = x3; initialConvexPoly[9] = y3; + // #5 + initialConvexPoly[10] = wedges[i * 20 + 26]; initialConvexPoly[11] = wedges[i * 20 + 27]; + // #6 + initialConvexPoly[12] = wedges[i * 20 + 30]; initialConvexPoly[13] = wedges[i * 20 + 31]; + // #7 + initialConvexPoly[14] = p3[1]; initialConvexPoly[15] = p3[2]; + } + break; + case 10: + // line1 & line2: p1 + LineLineIntersection(x0, y0, x_1, y_1, x1, y1, x_2, y_2, ref p1); + LineLineIntersection(x0, y0, x_1, y_1, x1, y1, x_5, y_5, ref p2); + LineLineIntersection(x0, y0, x_6, y_6, x1, y1, x_2, y_2, ref p3); + //printf("p3 %f %f %f (%f %f) (%f %f) (%f %f) (%f %f)\n",p3[0],p3[1],p3[2], x0, y0, x_6, x_6, x1, y1, x_2, y_2); + if ((p1[0] == 1.0) && (p2[0] == 1.0) && (p3[0] == 1.0)) + { + // #0 + initialConvexPoly[0] = p1[1]; initialConvexPoly[1] = p1[2]; + // #1 + initialConvexPoly[2] = p2[1]; initialConvexPoly[3] = p2[2]; + // #2 + initialConvexPoly[4] = wedges[i * 20 + 16]; initialConvexPoly[5] = wedges[i * 20 + 17]; + // #3 + initialConvexPoly[6] = wedges[i * 20 + 12]; initialConvexPoly[7] = wedges[i * 20 + 13]; + // #4 + initialConvexPoly[8] = wedges[i * 20 + 8]; initialConvexPoly[9] = wedges[i * 20 + 9]; + // #5 + initialConvexPoly[10] = x3; initialConvexPoly[11] = y3; + // #6 + initialConvexPoly[12] = wedges[i * 20 + 28]; initialConvexPoly[13] = wedges[i * 20 + 29]; + // #7 + initialConvexPoly[14] = wedges[i * 20 + 32]; initialConvexPoly[15] = wedges[i * 20 + 33]; + // #8 + initialConvexPoly[16] = wedges[i * 20 + 34]; initialConvexPoly[17] = wedges[i * 20 + 35]; + // #9 + initialConvexPoly[18] = p3[1]; initialConvexPoly[19] = p3[2]; + } + break; + } + // printf("smallest edge (%f,%f) (%f,%f)\n", x0,y0, x1,y1); + // printf("real INITIAL POLY [%.12f, %.12f;%.12f, %.12f;%.12f, %.12f;%.12f, %.12f;%.12f, %.12f;%.12f, %.12f;%.12f, %.12f;%.12f, %.12f;%.12f, %.12f;%.12f, %.12f;]\n", initialConvexPoly[0],initialConvexPoly[1],initialConvexPoly[2],initialConvexPoly[3],initialConvexPoly[4],initialConvexPoly[5],initialConvexPoly[6],initialConvexPoly[7],initialConvexPoly[8],initialConvexPoly[9],initialConvexPoly[10],initialConvexPoly[11],initialConvexPoly[12],initialConvexPoly[13],initialConvexPoly[14],initialConvexPoly[15],initialConvexPoly[16],initialConvexPoly[17],initialConvexPoly[18],initialConvexPoly[19]); + } + + x0 = x1; y0 = y1; + x1 = x2; y1 = y2; + } + /// HALF PLANE INTERSECTION: START SPLITTING THE INITIAL POLYGON TO FIND FEASIBLE REGION + if (numpoints != 0) + { + // first intersect the opposite located ones + s = (numpoints - 1) / 2 + 1; + flag = 0; + count = 0; + i = 1; + num = howManyPoints; + for (j = 0; j < 40; j = j + 4) + { + // in order to skip non-existent lines + if (howManyPoints == 4 && (j == 8 || j == 12 || j == 16 || j == 24 || j == 28 || j == 32)) + { + continue; + } + else if (howManyPoints == 6 && (j == 12 || j == 16 || j == 28 || j == 32)) + { + continue; + } + else if (howManyPoints == 8 && (j == 16 || j == 32)) + { + continue; + } + // printf("%d 1 INITIAL POLY [%.12f, %.12f;%.12f, %.12f;%.12f, %.12f;%.12f, %.12f;%.12f, %.12f;%.12f, %.12f;%.12f, %.12f;%.12f, %.12f;%.12f, %.12f;%.12f, %.12f;]\n",num, initialConvexPoly[0],initialConvexPoly[1],initialConvexPoly[2],initialConvexPoly[3],initialConvexPoly[4],initialConvexPoly[5],initialConvexPoly[6],initialConvexPoly[7],initialConvexPoly[8],initialConvexPoly[9],initialConvexPoly[10],initialConvexPoly[11],initialConvexPoly[12],initialConvexPoly[13],initialConvexPoly[14],initialConvexPoly[15],initialConvexPoly[16],initialConvexPoly[17],initialConvexPoly[18],initialConvexPoly[19]); + // printf("line (%f, %f) (%f, %f)\n",wedges[40*s+j],wedges[40*s+1+j], wedges[40*s+2+j], wedges[40*s+3+j]); + numpolypoints = HalfPlaneIntersection(num, ref initialConvexPoly, wedges[40 * s + j], wedges[40 * s + 1 + j], wedges[40 * s + 2 + j], wedges[40 * s + 3 + j]); + + if (numpolypoints == 0) + return false; + else + num = numpolypoints; + } + count++; + //printf("yes here\n"); + while (count < numpoints - 1) + { + for (j = 0; j < 40; j = j + 4) + { + // in order to skip non-existent lines + if (howManyPoints == 4 && (j == 8 || j == 12 || j == 16 || j == 24 || j == 28 || j == 32)) + { + continue; + } + else if (howManyPoints == 6 && (j == 12 || j == 16 || j == 28 || j == 32)) + { + continue; + } + else if (howManyPoints == 8 && (j == 16 || j == 32)) + { + continue; + } + ////printf("%d 2 INITIAL POLY [%.12f, %.12f;%.12f, %.12f;%.12f, %.12f;%.12f, %.12f;%.12f, %.12f;%.12f, %.12f;%.12f, %.12f;%.12f, %.12f;%.12f, %.12f;%.12f, %.12f;]\n",numpolypoints, initialConvexPoly[0],initialConvexPoly[1],initialConvexPoly[2],initialConvexPoly[3],initialConvexPoly[4],initialConvexPoly[5],initialConvexPoly[6],initialConvexPoly[7],initialConvexPoly[8],initialConvexPoly[9],initialConvexPoly[10],initialConvexPoly[11],initialConvexPoly[12],initialConvexPoly[13],initialConvexPoly[14],initialConvexPoly[15],initialConvexPoly[16],initialConvexPoly[17],initialConvexPoly[18],initialConvexPoly[19]); + //printf("line (%.20f, %.20f) (%.20f, %.20f)\n", wedges[40 * (i + s * flag) + j], wedges[40 * (i + s * flag) + 1 + j], wedges[40 * (i + s * flag) + 2 + j], wedges[40 * (i + s * flag) + 3 + j]); + numpolypoints = HalfPlaneIntersection(num, ref initialConvexPoly, wedges[40 * (i + s * flag) + j], wedges[40 * (i + s * flag) + 1 + j], wedges[40 * (i + s * flag) + 2 + j], wedges[40 * (i + s * flag) + 3 + j]); + + if (numpolypoints == 0) + return false; + else + num = numpolypoints; + } + i = i + flag; + flag = (flag + 1) % 2; + count++; + } + /// IF THERE IS A FEASIBLE INTERSECTION POLYGON, FIND ITS CENTROID AS THE NEW LOCATION + FindPolyCentroid(numpolypoints, initialConvexPoly, ref newloc); + + if (behavior.MaxAngle != 0.0) + { + numBadTriangle = 0; + for (j = 0; j < numpoints * 2 - 2; j = j + 2) + { + if (IsBadTriangleAngle(newloc[0], newloc[1], points[j], points[j + 1], points[j + 2], points[j + 3])) + { + numBadTriangle++; + } + } + if (IsBadTriangleAngle(newloc[0], newloc[1], points[0], points[1], points[numpoints * 2 - 2], points[numpoints * 2 - 1])) + { + numBadTriangle++; + } + + if (numBadTriangle == 0) + { + + return true; + } + n = (numpoints <= 2) ? 20 : 30; + // try points other than centroid + for (k = 0; k < 2 * numpoints; k = k + 2) + { + for (e = 1; e < n; e = e + 1) + { + newloc[0] = 0.0; newloc[1] = 0.0; + for (i = 0; i < 2 * numpoints; i = i + 2) + { + weight = 1.0 / numpoints; + if (i == k) + { + newloc[0] = newloc[0] + 0.1 * e * weight * points[i]; + newloc[1] = newloc[1] + 0.1 * e * weight * points[i + 1]; + } + else + { + weight = (1.0 - 0.1 * e * weight) / (double)(numpoints - 1.0); + newloc[0] = newloc[0] + weight * points[i]; + newloc[1] = newloc[1] + weight * points[i + 1]; + } + + } + numBadTriangle = 0; + for (j = 0; j < numpoints * 2 - 2; j = j + 2) + { + if (IsBadTriangleAngle(newloc[0], newloc[1], points[j], points[j + 1], points[j + 2], points[j + 3])) + { + numBadTriangle++; + } + } + if (IsBadTriangleAngle(newloc[0], newloc[1], points[0], points[1], points[numpoints * 2 - 2], points[numpoints * 2 - 1])) + { + numBadTriangle++; + } + + if (numBadTriangle == 0) + { + + return true; + } + } + } + } + else + { + //printf("yes, we found a feasible region num: %d newloc (%.12f,%.12f)\n", numpolypoints, newloc[0], newloc[1]); + // for(i = 0; i < 2*numpolypoints; i = i+2){ + // printf("point %d) (%.12f,%.12f)\n", i/2, initialConvexPoly[i], initialConvexPoly[i+1]); + // } + // printf("numpoints %d\n",numpoints); + return true; + } + } + + + return false; + } + + /// + /// Check polygon for min angle. + /// + /// + /// + /// Returns true if the polygon has angles greater than 2*minangle. + private bool ValidPolygonAngles(int numpoints, double[] points) + { + int i;//,j + for (i = 0; i < numpoints; i++) + { + if (i == numpoints - 1) + { + if (IsBadPolygonAngle(points[i * 2], points[i * 2 + 1], points[0], points[1], points[2], points[3])) + { + return false; // one of the inner angles is less than required + } + } + else if (i == numpoints - 2) + { + if (IsBadPolygonAngle(points[i * 2], points[i * 2 + 1], points[(i + 1) * 2], points[(i + 1) * 2 + 1], points[0], points[1])) + { + return false; // one of the inner angles is less than required + } + } + else + { + if (IsBadPolygonAngle(points[i * 2], points[i * 2 + 1], points[(i + 1) * 2], points[(i + 1) * 2 + 1], points[(i + 2) * 2], points[(i + 2) * 2 + 1])) + { + return false; // one of the inner angles is less than required + } + } + } + return true; // all angles are valid + } + + /// + /// Given three coordinates of a polygon, tests to see if it satisfies the minimum + /// angle condition for relocation. + /// + /// + /// + /// + /// + /// + /// + /// Returns true, if it is a BAD polygon corner, returns false if it is a GOOD + /// polygon corner + private bool IsBadPolygonAngle(double x1, double y1, + double x2, double y2, double x3, double y3) + { + // variables keeping the distance values for the edges + double dx12, dy12, dx23, dy23, dx31, dy31; + double dist12, dist23, dist31; + + double cosAngle; // in order to check minimum angle condition + + // calculate the side lengths + + dx12 = x1 - x2; + dy12 = y1 - y2; + dx23 = x2 - x3; + dy23 = y2 - y3; + dx31 = x3 - x1; + dy31 = y3 - y1; + // calculate the squares of the side lentghs + dist12 = dx12 * dx12 + dy12 * dy12; + dist23 = dx23 * dx23 + dy23 * dy23; + dist31 = dx31 * dx31 + dy31 * dy31; + + /// calculate cosine of largest angle /// + cosAngle = (dist12 + dist23 - dist31) / (2 * Math.Sqrt(dist12) * Math.Sqrt(dist23)); + // Check whether the angle is smaller than permitted which is 2*minangle!!! + //printf("angle: %f 2*minangle = %f\n",acos(cosAngle)*180/PI, 2*acos(Math.Sqrt(b.goodangle))*180/PI); + if (Math.Acos(cosAngle) < 2 * Math.Acos(Math.Sqrt(behavior.goodAngle))) + { + return true;// it is a BAD triangle + } + return false;// it is a GOOD triangle + + } + + /// + /// Given four points representing two lines, returns the intersection point. + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// The intersection point. + /// + // referenced to: http://local.wasp.uwa.edu.au/~pbourke/geometry/ + /// + private void LineLineIntersection( + double x1, double y1, + double x2, double y2, + double x3, double y3, + double x4, double y4, ref double[] p) + { + // x1,y1 P1 coordinates (point of line 1) + // x2,y2 P2 coordinates (point of line 1) + // x3,y3 P3 coordinates (point of line 2) + // x4,y4 P4 coordinates (point of line 2) + // p[1],p[2] intersection coordinates + // + // This function returns a pointer array which first index indicates + // weather they intersect on one point or not, followed by coordinate pairs. + + double u_a, u_b, denom; + + // calculate denominator first + denom = (y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1); + u_a = (x4 - x3) * (y1 - y3) - (y4 - y3) * (x1 - x3); + u_b = (x2 - x1) * (y1 - y3) - (y2 - y1) * (x1 - x3); + // if denominator and numerator equal to zero, lines are coincident + if (Math.Abs(denom - 0.0) < EPS && (Math.Abs(u_b - 0.0) < EPS && Math.Abs(u_a - 0.0) < EPS)) + { + p[0] = 0.0; + } + // if denominator equals to zero, lines are parallel + else if (Math.Abs(denom - 0.0) < EPS) + { + p[0] = 0.0; + } + else + { + p[0] = 1.0; + u_a = u_a / denom; + u_b = u_b / denom; + p[1] = x1 + u_a * (x2 - x1); // not the intersection point + p[2] = y1 + u_a * (y2 - y1); + } + } + + /// + /// Returns the convex polygon which is the intersection of the given convex + /// polygon with the halfplane on the left side (regarding the directional vector) + /// of the given line. + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// http://www.mathematik.uni-ulm.de/stochastik/lehre/ws03_04/rt/Geometry2D.ps + /// + private int HalfPlaneIntersection(int numvertices, ref double[] convexPoly, double x1, double y1, double x2, double y2) + { + double dx, dy; // direction of the line + double z, min, max; + int i, j; + + int numpolys; + double[] res = null; + int count = 0; + int intFound = 0; + dx = x2 - x1; + dy = y2 - y1; + numpolys = SplitConvexPolygon(numvertices, convexPoly, x1, y1, x2, y2, polys); + + if (numpolys == 3) + { + count = numvertices; + } + else + { + for (i = 0; i < numpolys; i++) + { + min = double.MaxValue; + max = double.MinValue; + // compute the minimum and maximum of the + // third coordinate of the cross product + for (j = 1; j <= 2 * polys[i][0] - 1; j = j + 2) + { + z = dx * (polys[i][j + 1] - y1) - dy * (polys[i][j] - x1); + min = (z < min ? z : min); + max = (z > max ? z : max); + } + // ... and choose the (absolute) greater of both + z = (Math.Abs(min) > Math.Abs(max) ? min : max); + // and if it is positive, the polygon polys[i] + // is on the left side of line + if (z > 0.0) + { + res = polys[i]; + intFound = 1; + break; + } + } + if (intFound == 1) + { + while (count < res[0]) + { + convexPoly[2 * count] = res[2 * count + 1]; + convexPoly[2 * count + 1] = res[2 * count + 2]; + count++; + + } + } + } + // update convexPoly + return count; + } + + /// + /// Splits a convex polygons into one or two polygons through the intersection + /// with the given line (regarding the directional vector of the given line). + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// http://www.mathematik.uni-ulm.de/stochastik/lehre/ws03_04/rt/Geometry2D.ps + /// + private int SplitConvexPolygon(int numvertices, double[] convexPoly, double x1, double y1, double x2, double y2, double[][] polys) + { + // state = 0: before the first intersection (with the line) + // state = 1: after the first intersection (with the line) + // state = 2: after the second intersection (with the line) + + int state = 0; + double[] p = new double[3]; + int poly1counter = 0; + int poly2counter = 0; + int numpolys; + int i; + double compConst = 0.000000000001; + // for debugging + int case1 = 0, case2 = 0, case3 = 0, case31 = 0, case32 = 0, case33 = 0, case311 = 0, case3111 = 0; + // intersect all edges of poly with line + for (i = 0; i < 2 * numvertices; i = i + 2) + { + int j = (i + 2 >= 2 * numvertices) ? 0 : i + 2; + LineLineSegmentIntersection(x1, y1, x2, y2, convexPoly[i], convexPoly[i + 1], convexPoly[j], convexPoly[j + 1], ref p); + // if this edge does not intersect with line + if (Math.Abs(p[0] - 0.0) <= compConst) + { + //System.out.println("null"); + // add p[j] to the proper polygon + if (state == 1) + { + poly2counter++; + poly2[2 * poly2counter - 1] = convexPoly[j]; + poly2[2 * poly2counter] = convexPoly[j + 1]; + } + else + { + poly1counter++; + poly1[2 * poly1counter - 1] = convexPoly[j]; + poly1[2 * poly1counter] = convexPoly[j + 1]; + } + // debug + case1++; + } + // ... or if the intersection is the whole edge + else if (Math.Abs(p[0] - 2.0) <= compConst) + { + //System.out.println(o); + // then we can not reach state 1 and 2 + poly1counter++; + poly1[2 * poly1counter - 1] = convexPoly[j]; + poly1[2 * poly1counter] = convexPoly[j + 1]; + // debug + case2++; + } + // ... or if the intersection is a point + else + { + // debug + case3++; + // if the point is the second vertex of the edge + if (Math.Abs(p[1] - convexPoly[j]) <= compConst && Math.Abs(p[2] - convexPoly[j + 1]) <= compConst) + { + // debug + case31++; + if (state == 1) + { + poly2counter++; + poly2[2 * poly2counter - 1] = convexPoly[j]; + poly2[2 * poly2counter] = convexPoly[j + 1]; + poly1counter++; + poly1[2 * poly1counter - 1] = convexPoly[j]; + poly1[2 * poly1counter] = convexPoly[j + 1]; + state++; + } + else if (state == 0) + { + // debug + case311++; + poly1counter++; + poly1[2 * poly1counter - 1] = convexPoly[j]; + poly1[2 * poly1counter] = convexPoly[j + 1]; + // test whether the polygon is splitted + // or the line only touches the polygon + if (i + 4 < 2 * numvertices) + { + int s1 = LinePointLocation(x1, y1, x2, y2, convexPoly[i], convexPoly[i + 1]); + int s2 = LinePointLocation(x1, y1, x2, y2, convexPoly[i + 4], convexPoly[i + 5]); + // the line only splits the polygon + // when the previous and next vertex lie + // on different sides of the line + if (s1 != s2 && s1 != 0 && s2 != 0) + { + // debug + case3111++; + poly2counter++; + poly2[2 * poly2counter - 1] = convexPoly[j]; + poly2[2 * poly2counter] = convexPoly[j + 1]; + state++; + } + } + } + } + // ... if the point is not the other vertex of the edge + else if (!(Math.Abs(p[1] - convexPoly[i]) <= compConst && Math.Abs(p[2] - convexPoly[i + 1]) <= compConst)) + { + // debug + case32++; + poly1counter++; + poly1[2 * poly1counter - 1] = p[1]; + poly1[2 * poly1counter] = p[2]; + poly2counter++; + poly2[2 * poly2counter - 1] = p[1]; + poly2[2 * poly2counter] = p[2]; + if (state == 1) + { + poly1counter++; + poly1[2 * poly1counter - 1] = convexPoly[j]; + poly1[2 * poly1counter] = convexPoly[j + 1]; + } + else if (state == 0) + { + poly2counter++; + poly2[2 * poly2counter - 1] = convexPoly[j]; + poly2[2 * poly2counter] = convexPoly[j + 1]; + } + state++; + } + // ... else if the point is the second vertex of the edge + else + { + // debug + case33++; + if (state == 1) + { + poly2counter++; + poly2[2 * poly2counter - 1] = convexPoly[j]; + poly2[2 * poly2counter] = convexPoly[j + 1]; + } + else + { + poly1counter++; + poly1[2 * poly1counter - 1] = convexPoly[j]; + poly1[2 * poly1counter] = convexPoly[j + 1]; + } + } + } + } + // after splitting the state must be 0 or 2 + // (depending whether the polygon was splitted or not) + if (state != 0 && state != 2) + { + // printf("there is something wrong state: %d\n", state); + // printf("polygon might not be convex!!\n"); + // printf("case1: %d\ncase2: %d\ncase3: %d\ncase31: %d case311: %d case3111: %d\ncase32: %d\ncase33: %d\n", case1, case2, case3, case31, case311, case3111, case32, case33); + // printf("numvertices %d\n=============\n", numvertices); + + // if there is something wrong with the intersection, just ignore this one + numpolys = 3; + } + else + { + // finally convert the vertex lists into convex polygons + numpolys = (state == 0) ? 1 : 2; + poly1[0] = poly1counter; + poly2[0] = poly2counter; + // convert the first convex polygon + polys[0] = poly1; + // convert the second convex polygon + if (state == 2) + { + polys[1] = poly2; + } + } + return numpolys; + } + + /// + /// Determines on which side (relative to the direction) of the given line and the + /// point lies (regarding the directional vector) of the given line. + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// http://www.mathematik.uni-ulm.de/stochastik/lehre/ws03_04/rt/Geometry2D.ps + /// + private int LinePointLocation(double x1, double y1, double x2, double y2, double x, double y) + { + double z; + if (Math.Atan((y2 - y1) / (x2 - x1)) * 180.0 / Math.PI == 90.0) + { + if (Math.Abs(x1 - x) <= 0.00000000001) + return 0; + } + else + { + if (Math.Abs(y1 + (((y2 - y1) * (x - x1)) / (x2 - x1)) - y) <= EPS) + return 0; + } + // third component of the 3 dimensional product + z = (x2 - x1) * (y - y1) - (y2 - y1) * (x - x1); + if (Math.Abs(z - 0.0) <= 0.00000000001) + { + return 0; + } + else if (z > 0) + { + return 1; + } + else + { + return 2; + } + } + + /// + /// Given four points representing one line and a line segment, returns the intersection point + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// referenced to: http://local.wasp.uwa.edu.au/~pbourke/geometry/ + /// + private void LineLineSegmentIntersection( + double x1, double y1, + double x2, double y2, + double x3, double y3, + double x4, double y4, ref double[] p) + { + // x1,y1 P1 coordinates (point of line) + // x2,y2 P2 coordinates (point of line) + // x3,y3 P3 coordinates (point of line segment) + // x4,y4 P4 coordinates (point of line segment) + // p[1],p[2] intersection coordinates + // + // This function returns a pointer array which first index indicates + // weather they intersect on one point or not, followed by coordinate pairs. + + double u_a, u_b, denom; + double compConst = 0.0000000000001; + // calculate denominator first + denom = (y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1); + u_a = (x4 - x3) * (y1 - y3) - (y4 - y3) * (x1 - x3); + u_b = (x2 - x1) * (y1 - y3) - (y2 - y1) * (x1 - x3); + + + //if(fabs(denom-0.0) < compConst && (fabs(u_b-0.0) < compConst && fabs(u_a-0.0) < compConst)){ + //printf("denom %.20f u_b %.20f u_a %.20f\n",denom, u_b, u_a); + if (Math.Abs(denom - 0.0) < compConst) + { + if (Math.Abs(u_b - 0.0) < compConst && Math.Abs(u_a - 0.0) < compConst) + { + p[0] = 2.0; // if denominator and numerator equal to zero, lines are coincident + } + else + { + p[0] = 0.0;// if denominator equals to zero, lines are parallel + } + + } + else + { + u_b = u_b / denom; + u_a = u_a / denom; + // printf("u_b %.20f\n", u_b); + if (u_b < -compConst || u_b > 1.0 + compConst) + { // check if it is on the line segment + // printf("line (%.20f, %.20f) (%.20f, %.20f) line seg (%.20f, %.20f) (%.20f, %.20f) \n",x1, y1 ,x2, y2 ,x3, y3 , x4, y4); + p[0] = 0.0; + } + else + { + p[0] = 1.0; + p[1] = x1 + u_a * (x2 - x1); // intersection point + p[2] = y1 + u_a * (y2 - y1); + } + } + + } + + /// + /// Returns the centroid of a given polygon + /// + /// + /// + /// Centroid of a given polygon + private void FindPolyCentroid(int numpoints, double[] points, ref double[] centroid) + { + int i; + //double area = 0.0;//, temp + centroid[0] = 0.0; centroid[1] = 0.0; + + for (i = 0; i < 2 * numpoints; i = i + 2) + { + + centroid[0] = centroid[0] + points[i]; + centroid[1] = centroid[1] + points[i + 1]; + + } + centroid[0] = centroid[0] / numpoints; + centroid[1] = centroid[1] / numpoints; + } + + /// + /// Given two points representing a line and a radius together with a center point + /// representing a circle, returns the intersection points. + /// + /// + /// + /// + /// + /// + /// + /// + /// Pointer to list of intersection points + /// + /// referenced to: http://local.wasp.uwa.edu.au/~pbourke/geometry/sphereline/ + /// + private void CircleLineIntersection( + double x1, double y1, + double x2, double y2, + double x3, double y3, double r, ref double[] p) + { + // x1,y1 P1 coordinates [point of line] + // x2,y2 P2 coordinates [point of line] + // x3,y3, r P3 coordinates(circle center) and radius [circle] + // p[1],p[2]; p[3],p[4] intersection coordinates + // + // This function returns a pointer array which first index indicates + // the number of intersection points, followed by coordinate pairs. + + //double x , y ; + double a, b, c, mu, i; + + a = (x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1); + b = 2 * ((x2 - x1) * (x1 - x3) + (y2 - y1) * (y1 - y3)); + c = x3 * x3 + y3 * y3 + x1 * x1 + y1 * y1 - 2 * (x3 * x1 + y3 * y1) - r * r; + i = b * b - 4 * a * c; + + if (i < 0.0) + { + // no intersection + p[0] = 0.0; + } + else if (Math.Abs(i - 0.0) < EPS) + { + // one intersection + p[0] = 1.0; + + mu = -b / (2 * a); + p[1] = x1 + mu * (x2 - x1); + p[2] = y1 + mu * (y2 - y1); + + } + else if (i > 0.0 && !(Math.Abs(a - 0.0) < EPS)) + { + // two intersections + p[0] = 2.0; + // first intersection + mu = (-b + Math.Sqrt(i)) / (2 * a); + p[1] = x1 + mu * (x2 - x1); + p[2] = y1 + mu * (y2 - y1); + // second intersection + mu = (-b - Math.Sqrt(i)) / (2 * a); + p[3] = x1 + mu * (x2 - x1); + p[4] = y1 + mu * (y2 - y1); + + + } + else + { + p[0] = 0.0; + } + } + + /// + /// Given three points, check if the point is the correct point that we are looking for. + /// + /// P1 coordinates (bisector point of dual edge on triangle) + /// P1 coordinates (bisector point of dual edge on triangle) + /// P2 coordinates (intersection point) + /// P2 coordinates (intersection point) + /// P3 coordinates (circumcenter point) + /// P3 coordinates (circumcenter point) + /// + /// Returns true, if given point is the correct one otherwise return false. + private bool ChooseCorrectPoint( + double x1, double y1, + double x2, double y2, + double x3, double y3, bool isObtuse) + { + double d1, d2; + bool p; + + // squared distance between circumcenter and intersection point + d1 = (x2 - x3) * (x2 - x3) + (y2 - y3) * (y2 - y3); + // squared distance between bisector point and intersection point + d2 = (x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1); + + if (isObtuse) + { + // obtuse case + if (d2 >= d1) + { + p = true; // means we have found the right point + } + else + { + p = false; // means take the other point + } + } + else + { + // non-obtuse case + if (d2 < d1) + { + p = true; // means we have found the right point + } + else + { + p = false; // means take the other point + } + } + /// HANDLE RIGHT TRIANGLE CASE!!!!!!!!!!!!!!!!!!!!!!!!!!!! + return p; + + } + + /// + /// This function returns a pointer array which first index indicates the whether + /// the point is in between the other points, followed by coordinate pairs. + /// + /// P1 coordinates [point of line] (point on Voronoi edge - intersection) + /// P1 coordinates [point of line] (point on Voronoi edge - intersection) + /// P2 coordinates [point of line] (circumcenter) + /// P2 coordinates [point of line] (circumcenter) + /// P3 coordinates [point to be compared] (neighbor's circumcenter) + /// P3 coordinates [point to be compared] (neighbor's circumcenter) + /// + private void PointBetweenPoints(double x1, double y1, double x2, double y2, double x, double y, ref double[] p) + { + // now check whether the point is close to circumcenter than intersection point + // BETWEEN THE POINTS + if ((x2 - x) * (x2 - x) + (y2 - y) * (y2 - y) < (x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1)) + { + p[0] = 1.0; + // calculate the squared distance to circumcenter + p[1] = (x - x2) * (x - x2) + (y - y2) * (y - y2); + p[2] = x; + p[3] = y; + }// *NOT* BETWEEN THE POINTS + else + { + p[0] = 0.0; + p[1] = 0.0; + p[2] = 0.0; + p[3] = 0.0; + } + } + + /// + /// Given three coordinates of a triangle, tests a triangle to see if it satisfies + /// the minimum and/or maximum angle condition. + /// + /// + /// + /// + /// + /// + /// + /// Returns true, if it is a BAD triangle, returns false if it is a GOOD triangle. + private bool IsBadTriangleAngle(double x1, double y1, double x2, double y2, double x3, double y3) + { + // variables keeping the distance values for the edges + double dxod, dyod, dxda, dyda, dxao, dyao; + double dxod2, dyod2, dxda2, dyda2, dxao2, dyao2; + + double apexlen, orglen, destlen; + double angle; // in order to check minimum angle condition + + double maxangle; // in order to check minimum angle condition + // calculate the side lengths + + dxod = x1 - x2; + dyod = y1 - y2; + dxda = x2 - x3; + dyda = y2 - y3; + dxao = x3 - x1; + dyao = y3 - y1; + // calculate the squares of the side lentghs + 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; + + // try to find the minimum edge and accordingly the pqr orientation + if ((apexlen < orglen) && (apexlen < destlen)) + { + // Find the square of the cosine of the angle at the apex. + angle = dxda * dxao + dyda * dyao; + angle = angle * angle / (orglen * destlen); + } + else if (orglen < destlen) + { + // Find the square of the cosine of the angle at the origin. + angle = dxod * dxao + dyod * dyao; + angle = angle * angle / (apexlen * destlen); + } + else + { + // Find the square of the cosine of the angle at the destination. + angle = dxod * dxda + dyod * dyda; + angle = angle * angle / (apexlen * orglen); + + } + + // try to find the maximum edge and accordingly the pqr orientation + if ((apexlen > orglen) && (apexlen > destlen)) + { + // Find the cosine of the angle at the apex. + maxangle = (orglen + destlen - apexlen) / (2 * Math.Sqrt(orglen * destlen)); + } + else if (orglen > destlen) + { + // Find the cosine of the angle at the origin. + maxangle = (apexlen + destlen - orglen) / (2 * Math.Sqrt(apexlen * destlen)); + } + else + { + // 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) || (behavior.MaxAngle != 0.00 && maxangle < behavior.maxGoodAngle)) + { + return true;// it is a bad triangle + } + + return false;// it is a good triangle + } + + /// + /// Given the triangulation, and a vertex returns the minimum distance to the + /// vertices of the triangle where the given vertex located. + /// + /// + /// + /// + /// + private double MinDistanceToNeighbor(double newlocX, double newlocY, ref Otri searchtri) + { + Otri horiz = default(Otri); // for search operation + LocateResult intersect = LocateResult.Outside; + Vertex v1, v2, v3, torg, tdest; + double d1, d2, d3, ahead; + //triangle ptr; // Temporary variable used by sym(). + + Point newvertex = new Point(newlocX, newlocY); + + // printf("newvertex %f,%f\n", newvertex[0], newvertex[1]); + // Find the location of the vertex to be inserted. Check if a good + // starting triangle has already been provided by the caller. + // Find a boundary triangle. + //horiz.tri = m.dummytri; + //horiz.orient = 0; + //horiz.symself(); + // Search for a triangle containing 'newvertex'. + // Start searching from the triangle provided by the caller. + // Where are we? + torg = searchtri.Org(); + tdest = searchtri.Dest(); + // Check the starting triangle's vertices. + if ((torg.x == newvertex.x) && (torg.y == newvertex.y)) + { + intersect = LocateResult.OnVertex; + searchtri.Copy(ref horiz); + + } + else if ((tdest.x == newvertex.x) && (tdest.y == newvertex.y)) + { + searchtri.Lnext(); + intersect = LocateResult.OnVertex; + searchtri.Copy(ref horiz); + } + else + { + // Orient 'searchtri' to fit the preconditions of calling preciselocate(). + ahead = predicates.CounterClockwise(torg, tdest, newvertex); + if (ahead < 0.0) + { + // Turn around so that 'searchpoint' is to the left of the + // edge specified by 'searchtri'. + searchtri.Sym(); + searchtri.Copy(ref horiz); + intersect = mesh.locator.PreciseLocate(newvertex, ref horiz, false); + } + else if (ahead == 0.0) + { + // Check if 'searchpoint' is between 'torg' and 'tdest'. + if (((torg.x < newvertex.x) == (newvertex.x < tdest.x)) && + ((torg.y < newvertex.y) == (newvertex.y < tdest.y))) + { + intersect = LocateResult.OnEdge; + searchtri.Copy(ref horiz); + + } + } + else + { + searchtri.Copy(ref horiz); + intersect = mesh.locator.PreciseLocate(newvertex, ref horiz, false); + } + } + if (intersect == LocateResult.OnVertex || intersect == LocateResult.Outside) + { + // set distance to 0 + //m.VertexDealloc(newvertex); + return 0.0; + } + else + { // intersect == ONEDGE || intersect == INTRIANGLE + // find the triangle vertices + v1 = horiz.Org(); + v2 = horiz.Dest(); + v3 = horiz.Apex(); + d1 = (v1.x - newvertex.x) * (v1.x - newvertex.x) + (v1.y - newvertex.y) * (v1.y - newvertex.y); + d2 = (v2.x - newvertex.x) * (v2.x - newvertex.x) + (v2.y - newvertex.y) * (v2.y - newvertex.y); + d3 = (v3.x - newvertex.x) * (v3.x - newvertex.x) + (v3.y - newvertex.y) * (v3.y - newvertex.y); + //m.VertexDealloc(newvertex); + // find minimum of the distance + if (d1 <= d2 && d1 <= d3) + { + return d1; + } + else if (d2 <= d3) + { + return d2; + } + else + { + return d3; + } + } + } + } +} \ No newline at end of file diff --git a/External/Triangle.NET/Triangle/RobustPredicates.cs b/External/Triangle.NET/Triangle/RobustPredicates.cs new file mode 100644 index 0000000..fd47d07 --- /dev/null +++ b/External/Triangle.NET/Triangle/RobustPredicates.cs @@ -0,0 +1,1346 @@ +// ----------------------------------------------------------------------- +// +// 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/ +// +// ----------------------------------------------------------------------- + +namespace TriangleNet +{ + using System; + using TriangleNet.Geometry; + using TriangleNet.Tools; + + /// + /// Adaptive exact arithmetic geometric predicates. + /// + /// + /// The adaptive exact arithmetic geometric predicates implemented herein are described in + /// detail in the paper "Adaptive Precision Floating-Point Arithmetic and Fast Robust + /// Geometric Predicates." by Jonathan Richard Shewchuk, see + /// http://www.cs.cmu.edu/~quake/robust.html + /// + /// The macros of the original C code were automatically expanded using the Visual Studio + /// command prompt with the command "CL /P /C EXACT.C", see + /// http://msdn.microsoft.com/en-us/library/8z9z0bx6.aspx + /// + public class RobustPredicates : IPredicates + { + #region Default predicates instance (Singleton) + + private static readonly object creationLock = new object(); + private static RobustPredicates _default; + + /// + /// Gets the default configuration instance. + /// + public static RobustPredicates Default + { + get + { + if (_default == null) + { + lock (creationLock) + { + if (_default == null) + { + _default = new RobustPredicates(); + } + } + } + + return _default; + } + } + + #endregion + + #region Static initialization + + private static double epsilon, splitter, resulterrbound; + private static double ccwerrboundA, ccwerrboundB, ccwerrboundC; + private static double iccerrboundA, iccerrboundB, iccerrboundC; + //private static double o3derrboundA, o3derrboundB, o3derrboundC; + + /// + /// Initialize the variables used for exact arithmetic. + /// + /// + /// 'epsilon' is the largest power of two such that 1.0 + epsilon = 1.0 in + /// floating-point arithmetic. 'epsilon' bounds the relative roundoff + /// error. It is used for floating-point error analysis. + /// + /// 'splitter' is used to split floating-point numbers into two half- + /// length significands for exact multiplication. + /// + /// I imagine that a highly optimizing compiler might be too smart for its + /// own good, and somehow cause this routine to fail, if it pretends that + /// floating-point arithmetic is too much like double arithmetic. + /// + /// Don't change this routine unless you fully understand it. + /// + static RobustPredicates() + { + double half; + double check, lastcheck; + bool every_other; + + every_other = true; + half = 0.5; + epsilon = 1.0; + splitter = 1.0; + check = 1.0; + // Repeatedly divide 'epsilon' by two until it is too small to add to + // one without causing roundoff. (Also check if the sum is equal to + // the previous sum, for machines that round up instead of using exact + // rounding. Not that these routines will work on such machines.) + do + { + lastcheck = check; + epsilon *= half; + if (every_other) + { + splitter *= 2.0; + } + every_other = !every_other; + check = 1.0 + epsilon; + } while ((check != 1.0) && (check != lastcheck)); + splitter += 1.0; + // Error bounds for orientation and incircle tests. + resulterrbound = (3.0 + 8.0 * epsilon) * epsilon; + ccwerrboundA = (3.0 + 16.0 * epsilon) * epsilon; + ccwerrboundB = (2.0 + 12.0 * epsilon) * epsilon; + ccwerrboundC = (9.0 + 64.0 * epsilon) * epsilon * epsilon; + iccerrboundA = (10.0 + 96.0 * epsilon) * epsilon; + iccerrboundB = (4.0 + 48.0 * epsilon) * epsilon; + iccerrboundC = (44.0 + 576.0 * epsilon) * epsilon * epsilon; + //o3derrboundA = (7.0 + 56.0 * epsilon) * epsilon; + //o3derrboundB = (3.0 + 28.0 * epsilon) * epsilon; + //o3derrboundC = (26.0 + 288.0 * epsilon) * epsilon * epsilon; + } + + #endregion + + public RobustPredicates() + { + AllocateWorkspace(); + } + + /// + /// Check, if the three points appear in counterclockwise order. The result is + /// also a rough approximation of twice the signed area of the triangle defined + /// by the three points. + /// + /// Point a. + /// Point b. + /// Point c. + /// Return a positive value if the points pa, pb, and pc occur in + /// counterclockwise order; a negative value if they occur in clockwise order; + /// and zero if they are collinear. + public double CounterClockwise(Point pa, Point pb, Point pc) + { + double detleft, detright, det; + double detsum, errbound; + + Statistic.CounterClockwiseCount++; + + detleft = (pa.x - pc.x) * (pb.y - pc.y); + detright = (pa.y - pc.y) * (pb.x - pc.x); + det = detleft - detright; + + if (Behavior.NoExact) + { + return det; + } + + if (detleft > 0.0) + { + if (detright <= 0.0) + { + return det; + } + else + { + detsum = detleft + detright; + } + } + else if (detleft < 0.0) + { + if (detright >= 0.0) + { + return det; + } + else + { + detsum = -detleft - detright; + } + } + else + { + return det; + } + + errbound = ccwerrboundA * detsum; + if ((det >= errbound) || (-det >= errbound)) + { + return det; + } + + Statistic.CounterClockwiseAdaptCount++; + return CounterClockwiseAdapt(pa, pb, pc, detsum); + } + + /// + /// Check if the point pd lies inside the circle passing through pa, pb, and pc. The + /// points pa, pb, and pc must be in counterclockwise order, or the sign of the result + /// will be reversed. + /// + /// Point a. + /// Point b. + /// Point c. + /// Point d. + /// Return a positive value if the point pd lies inside the circle passing through + /// pa, pb, and pc; a negative value if it lies outside; and zero if the four points + /// are cocircular. + public double InCircle(Point pa, Point pb, Point pc, Point pd) + { + double adx, bdx, cdx, ady, bdy, cdy; + double bdxcdy, cdxbdy, cdxady, adxcdy, adxbdy, bdxady; + double alift, blift, clift; + double det; + double permanent, errbound; + + Statistic.InCircleCount++; + + adx = pa.x - pd.x; + bdx = pb.x - pd.x; + cdx = pc.x - pd.x; + ady = pa.y - pd.y; + bdy = pb.y - pd.y; + cdy = pc.y - pd.y; + + bdxcdy = bdx * cdy; + cdxbdy = cdx * bdy; + alift = adx * adx + ady * ady; + + cdxady = cdx * ady; + adxcdy = adx * cdy; + blift = bdx * bdx + bdy * bdy; + + adxbdy = adx * bdy; + bdxady = bdx * ady; + clift = cdx * cdx + cdy * cdy; + + det = alift * (bdxcdy - cdxbdy) + + blift * (cdxady - adxcdy) + + clift * (adxbdy - bdxady); + + if (Behavior.NoExact) + { + return det; + } + + permanent = (Math.Abs(bdxcdy) + Math.Abs(cdxbdy)) * alift + + (Math.Abs(cdxady) + Math.Abs(adxcdy)) * blift + + (Math.Abs(adxbdy) + Math.Abs(bdxady)) * clift; + errbound = iccerrboundA * permanent; + if ((det > errbound) || (-det > errbound)) + { + return det; + } + + Statistic.InCircleAdaptCount++; + return InCircleAdapt(pa, pb, pc, pd, permanent); + } + + /// + /// Return a positive value if the point pd is incompatible with the circle + /// or plane passing through pa, pb, and pc (meaning that pd is inside the + /// circle or below the plane); a negative value if it is compatible; and + /// zero if the four points are cocircular/coplanar. The points pa, pb, and + /// pc must be in counterclockwise order, or the sign of the result will be + /// reversed. + /// + /// Point a. + /// Point b. + /// Point c. + /// Point d. + /// Return a positive value if the point pd lies inside the circle passing through + /// pa, pb, and pc; a negative value if it lies outside; and zero if the four points + /// are cocircular. + public double NonRegular(Point pa, Point pb, Point pc, Point pd) + { + return InCircle(pa, pb, pc, pd); + } + + /// + /// Find the circumcenter of a triangle. + /// + /// Triangle point. + /// Triangle point. + /// Triangle point. + /// Relative coordinate of new location. + /// Relative coordinate of new location. + /// Off-center constant. + /// Coordinates of the circumcenter (or off-center) + public Point FindCircumcenter(Point org, Point dest, Point apex, + ref double xi, ref double eta, double offconstant) + { + double xdo, ydo, xao, yao; + double dodist, aodist, dadist; + double denominator; + double dx, dy, dxoff, dyoff; + + Statistic.CircumcenterCount++; + + // 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; + dodist = xdo * xdo + ydo * ydo; + aodist = xao * xao + yao * yao; + dadist = (dest.x - apex.x) * (dest.x - apex.x) + + (dest.y - apex.y) * (dest.y - apex.y); + + if (Behavior.NoExact) + { + denominator = 0.5 / (xdo * yao - xao * ydo); + } + else + { + // Use the counterclockwise() routine to ensure a positive (and + // reasonably accurate) result, avoiding any possibility of + // division by zero. + denominator = 0.5 / CounterClockwise(dest, apex, org); + // Don't count the above as an orientation test. + Statistic.CounterClockwiseCount--; + } + + dx = (yao * dodist - ydo * aodist) * denominator; + dy = (xdo * aodist - xao * dodist) * denominator; + + // Find the (squared) length of the triangle's shortest edge. This + // serves as a conservative estimate of the insertion radius of the + // circumcenter's parent. The estimate is used to ensure that + // the algorithm terminates even if very small angles appear in + // the input PSLG. + if ((dodist < aodist) && (dodist < dadist)) + { + if (offconstant > 0.0) + { + // Find the position of the off-center, as described by Alper Ungor. + dxoff = 0.5 * xdo - offconstant * ydo; + dyoff = 0.5 * ydo + offconstant * xdo; + // If the off-center is closer to the origin than the + // circumcenter, use the off-center instead. + if (dxoff * dxoff + dyoff * dyoff < dx * dx + dy * dy) + { + dx = dxoff; + dy = dyoff; + } + } + } + else if (aodist < dadist) + { + if (offconstant > 0.0) + { + dxoff = 0.5 * xao + offconstant * yao; + dyoff = 0.5 * yao - offconstant * xao; + // If the off-center is closer to the origin than the + // circumcenter, use the off-center instead. + if (dxoff * dxoff + dyoff * dyoff < dx * dx + dy * dy) + { + dx = dxoff; + dy = dyoff; + } + } + } + else + { + if (offconstant > 0.0) + { + dxoff = 0.5 * (apex.x - dest.x) - offconstant * (apex.y - dest.y); + dyoff = 0.5 * (apex.y - dest.y) + offconstant * (apex.x - dest.x); + // If the off-center is closer to the destination than the + // circumcenter, use the off-center instead. + if (dxoff * dxoff + dyoff * dyoff < + (dx - xdo) * (dx - xdo) + (dy - ydo) * (dy - ydo)) + { + dx = xdo + dxoff; + dy = ydo + dyoff; + } + } + } + + // To interpolate vertex attributes for the new vertex inserted at + // the circumcenter, 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. + // Calculate the xi and eta coordinates of the circumcenter. + xi = (yao * dx - xao * dy) * (2.0 * denominator); + eta = (xdo * dy - ydo * dx) * (2.0 * denominator); + + return new Point(org.x + dx, org.y + dy); + } + + /// + /// Find the circumcenter of a triangle. + /// + /// Triangle point. + /// Triangle point. + /// Triangle point. + /// Relative coordinate of new location. + /// Relative coordinate of new location. + /// Coordinates of the circumcenter + /// + /// The result is returned both in terms of x-y coordinates and xi-eta + /// (barycentric) coordinates. The xi-eta coordinate system is defined in + /// terms of the triangle: the origin of the triangle is the origin of the + /// coordinate system; the destination of the triangle is one unit along the + /// xi axis; and the apex of the triangle is one unit along the eta axis. + /// This procedure also returns the square of the length of the triangle's + /// shortest edge. + /// + public Point FindCircumcenter(Point org, Point dest, Point apex, + ref double xi, ref double eta) + { + double xdo, ydo, xao, yao; + double dodist, aodist; + double denominator; + double dx, dy; + + Statistic.CircumcenterCount++; + + // 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; + dodist = xdo * xdo + ydo * ydo; + aodist = xao * xao + yao * yao; + + if (Behavior.NoExact) + { + denominator = 0.5 / (xdo * yao - xao * ydo); + } + else + { + // Use the counterclockwise() routine to ensure a positive (and + // reasonably accurate) result, avoiding any possibility of + // division by zero. + denominator = 0.5 / CounterClockwise(dest, apex, org); + // Don't count the above as an orientation test. + Statistic.CounterClockwiseCount--; + } + + dx = (yao * dodist - ydo * aodist) * denominator; + dy = (xdo * aodist - xao * dodist) * denominator; + + // To interpolate vertex attributes for the new vertex inserted at + // the circumcenter, 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. + // Calculate the xi and eta coordinates of the circumcenter. + xi = (yao * dx - xao * dy) * (2.0 * denominator); + eta = (xdo * dy - ydo * dx) * (2.0 * denominator); + + return new Point(org.x + dx, org.y + dy); + } + + #region Exact arithmetics + + /// + /// Sum two expansions, eliminating zero components from the output expansion. + /// + /// + /// + /// + /// + /// + /// + /// + /// Sets h = e + f. See the Robust Predicates paper for details. + /// + /// If round-to-even is used (as with IEEE 754), maintains the strongly nonoverlapping + /// property. (That is, if e is strongly nonoverlapping, h will be also.) Does NOT + /// maintain the nonoverlapping or nonadjacent properties. + /// + private int FastExpansionSumZeroElim(int elen, double[] e, int flen, double[] f, double[] h) + { + double Q; + double Qnew; + double hh; + double bvirt; + double avirt, bround, around; + int eindex, findex, hindex; + double enow, fnow; + + enow = e[0]; + fnow = f[0]; + eindex = findex = 0; + if ((fnow > enow) == (fnow > -enow)) + { + Q = enow; + enow = e[++eindex]; + } + else + { + Q = fnow; + fnow = f[++findex]; + } + hindex = 0; + if ((eindex < elen) && (findex < flen)) + { + if ((fnow > enow) == (fnow > -enow)) + { + Qnew = (double)(enow + Q); bvirt = Qnew - enow; hh = Q - bvirt; + enow = e[++eindex]; + } + else + { + Qnew = (double)(fnow + Q); bvirt = Qnew - fnow; hh = Q - bvirt; + fnow = f[++findex]; + } + Q = Qnew; + if (hh != 0.0) + { + h[hindex++] = hh; + } + while ((eindex < elen) && (findex < flen)) + { + if ((fnow > enow) == (fnow > -enow)) + { + Qnew = (double)(Q + enow); + bvirt = (double)(Qnew - Q); + avirt = Qnew - bvirt; + bround = enow - bvirt; + around = Q - avirt; + hh = around + bround; + + enow = e[++eindex]; + } + else + { + Qnew = (double)(Q + fnow); + bvirt = (double)(Qnew - Q); + avirt = Qnew - bvirt; + bround = fnow - bvirt; + around = Q - avirt; + hh = around + bround; + + fnow = f[++findex]; + } + Q = Qnew; + if (hh != 0.0) + { + h[hindex++] = hh; + } + } + } + while (eindex < elen) + { + Qnew = (double)(Q + enow); + bvirt = (double)(Qnew - Q); + avirt = Qnew - bvirt; + bround = enow - bvirt; + around = Q - avirt; + hh = around + bround; + + enow = e[++eindex]; + Q = Qnew; + if (hh != 0.0) + { + h[hindex++] = hh; + } + } + while (findex < flen) + { + Qnew = (double)(Q + fnow); + bvirt = (double)(Qnew - Q); + avirt = Qnew - bvirt; + bround = fnow - bvirt; + around = Q - avirt; + hh = around + bround; + + fnow = f[++findex]; + Q = Qnew; + if (hh != 0.0) + { + h[hindex++] = hh; + } + } + if ((Q != 0.0) || (hindex == 0)) + { + h[hindex++] = Q; + } + return hindex; + } + + /// + /// Multiply an expansion by a scalar, eliminating zero components from the output expansion. + /// + /// + /// + /// + /// + /// + /// + /// Sets h = be. See my Robust Predicates paper for details. + /// + /// Maintains the nonoverlapping property. If round-to-even is used (as with IEEE 754), + /// maintains the strongly nonoverlapping and nonadjacent properties as well. (That is, + /// if e has one of these properties, so will h.) + /// + private int ScaleExpansionZeroElim(int elen, double[] e, double b, double[] h) + { + double Q, sum; + double hh; + double product1; + double product0; + int eindex, hindex; + double enow; + double bvirt; + double avirt, bround, around; + double c; + double abig; + double ahi, alo, bhi, blo; + double err1, err2, err3; + + c = (double)(splitter * b); abig = (double)(c - b); bhi = c - abig; blo = b - bhi; + Q = (double)(e[0] * b); c = (double)(splitter * e[0]); abig = (double)(c - e[0]); ahi = c - abig; alo = e[0] - ahi; err1 = Q - (ahi * bhi); err2 = err1 - (alo * bhi); err3 = err2 - (ahi * blo); hh = (alo * blo) - err3; + hindex = 0; + if (hh != 0) + { + h[hindex++] = hh; + } + for (eindex = 1; eindex < elen; eindex++) + { + enow = e[eindex]; + product1 = (double)(enow * b); c = (double)(splitter * enow); abig = (double)(c - enow); ahi = c - abig; alo = enow - ahi; err1 = product1 - (ahi * bhi); err2 = err1 - (alo * bhi); err3 = err2 - (ahi * blo); product0 = (alo * blo) - err3; + sum = (double)(Q + product0); bvirt = (double)(sum - Q); avirt = sum - bvirt; bround = product0 - bvirt; around = Q - avirt; hh = around + bround; + if (hh != 0) + { + h[hindex++] = hh; + } + Q = (double)(product1 + sum); bvirt = Q - product1; hh = sum - bvirt; + if (hh != 0) + { + h[hindex++] = hh; + } + } + if ((Q != 0.0) || (hindex == 0)) + { + h[hindex++] = Q; + } + return hindex; + } + + /// + /// Produce a one-word estimate of an expansion's value. + /// + /// + /// + /// + private double Estimate(int elen, double[] e) + { + double Q; + int eindex; + + Q = e[0]; + for (eindex = 1; eindex < elen; eindex++) + { + Q += e[eindex]; + } + return Q; + } + + /// + /// Return a positive value if the points pa, pb, and pc occur in counterclockwise + /// order; a negative value if they occur in clockwise order; and zero if they are + /// collinear. The result is also a rough approximation of twice the signed area of + /// the triangle defined by the three points. + /// + /// + /// + /// + /// + /// + /// + /// Uses exact arithmetic if necessary to ensure a correct answer. The result returned + /// is the determinant of a matrix. This determinant is computed adaptively, in the + /// sense that exact arithmetic is used only to the degree it is needed to ensure that + /// the returned value has the correct sign. Hence, this function is usually quite fast, + /// but will run more slowly when the input points are collinear or nearly so. + /// + private double CounterClockwiseAdapt(Point pa, Point pb, Point pc, double detsum) + { + double acx, acy, bcx, bcy; + double acxtail, acytail, bcxtail, bcytail; + double detleft, detright; + double detlefttail, detrighttail; + double det, errbound; + // Edited to work around index out of range exceptions (changed array length from 4 to 5). + // See unsafe indexing in FastExpansionSumZeroElim. + double[] B = new double[5], u = new double[5]; + double[] C1 = new double[8], C2 = new double[12], D = new double[16]; + double B3; + int C1length, C2length, Dlength; + + double u3; + double s1, t1; + double s0, t0; + + double bvirt; + double avirt, bround, around; + double c; + double abig; + double ahi, alo, bhi, blo; + double err1, err2, err3; + double _i, _j; + double _0; + + acx = (double)(pa.x - pc.x); + bcx = (double)(pb.x - pc.x); + acy = (double)(pa.y - pc.y); + bcy = (double)(pb.y - pc.y); + + detleft = (double)(acx * bcy); c = (double)(splitter * acx); abig = (double)(c - acx); ahi = c - abig; alo = acx - ahi; c = (double)(splitter * bcy); abig = (double)(c - bcy); bhi = c - abig; blo = bcy - bhi; err1 = detleft - (ahi * bhi); err2 = err1 - (alo * bhi); err3 = err2 - (ahi * blo); detlefttail = (alo * blo) - err3; + detright = (double)(acy * bcx); c = (double)(splitter * acy); abig = (double)(c - acy); ahi = c - abig; alo = acy - ahi; c = (double)(splitter * bcx); abig = (double)(c - bcx); bhi = c - abig; blo = bcx - bhi; err1 = detright - (ahi * bhi); err2 = err1 - (alo * bhi); err3 = err2 - (ahi * blo); detrighttail = (alo * blo) - err3; + + _i = (double)(detlefttail - detrighttail); bvirt = (double)(detlefttail - _i); avirt = _i + bvirt; bround = bvirt - detrighttail; around = detlefttail - avirt; B[0] = around + bround; _j = (double)(detleft + _i); bvirt = (double)(_j - detleft); avirt = _j - bvirt; bround = _i - bvirt; around = detleft - avirt; _0 = around + bround; _i = (double)(_0 - detright); bvirt = (double)(_0 - _i); avirt = _i + bvirt; bround = bvirt - detright; around = _0 - avirt; B[1] = around + bround; B3 = (double)(_j + _i); bvirt = (double)(B3 - _j); avirt = B3 - bvirt; bround = _i - bvirt; around = _j - avirt; B[2] = around + bround; + + B[3] = B3; + + det = Estimate(4, B); + errbound = ccwerrboundB * detsum; + if ((det >= errbound) || (-det >= errbound)) + { + return det; + } + + bvirt = (double)(pa.x - acx); avirt = acx + bvirt; bround = bvirt - pc.x; around = pa.x - avirt; acxtail = around + bround; + bvirt = (double)(pb.x - bcx); avirt = bcx + bvirt; bround = bvirt - pc.x; around = pb.x - avirt; bcxtail = around + bround; + bvirt = (double)(pa.y - acy); avirt = acy + bvirt; bround = bvirt - pc.y; around = pa.y - avirt; acytail = around + bround; + bvirt = (double)(pb.y - bcy); avirt = bcy + bvirt; bround = bvirt - pc.y; around = pb.y - avirt; bcytail = around + bround; + + if ((acxtail == 0.0) && (acytail == 0.0) + && (bcxtail == 0.0) && (bcytail == 0.0)) + { + return det; + } + + errbound = ccwerrboundC * detsum + resulterrbound * ((det) >= 0.0 ? (det) : -(det)); + det += (acx * bcytail + bcy * acxtail) + - (acy * bcxtail + bcx * acytail); + if ((det >= errbound) || (-det >= errbound)) + { + return det; + } + + s1 = (double)(acxtail * bcy); c = (double)(splitter * acxtail); abig = (double)(c - acxtail); ahi = c - abig; alo = acxtail - ahi; c = (double)(splitter * bcy); abig = (double)(c - bcy); bhi = c - abig; blo = bcy - bhi; err1 = s1 - (ahi * bhi); err2 = err1 - (alo * bhi); err3 = err2 - (ahi * blo); s0 = (alo * blo) - err3; + t1 = (double)(acytail * bcx); c = (double)(splitter * acytail); abig = (double)(c - acytail); ahi = c - abig; alo = acytail - ahi; c = (double)(splitter * bcx); abig = (double)(c - bcx); bhi = c - abig; blo = bcx - bhi; err1 = t1 - (ahi * bhi); err2 = err1 - (alo * bhi); err3 = err2 - (ahi * blo); t0 = (alo * blo) - err3; + _i = (double)(s0 - t0); bvirt = (double)(s0 - _i); avirt = _i + bvirt; bround = bvirt - t0; around = s0 - avirt; u[0] = around + bround; _j = (double)(s1 + _i); bvirt = (double)(_j - s1); avirt = _j - bvirt; bround = _i - bvirt; around = s1 - avirt; _0 = around + bround; _i = (double)(_0 - t1); bvirt = (double)(_0 - _i); avirt = _i + bvirt; bround = bvirt - t1; around = _0 - avirt; u[1] = around + bround; u3 = (double)(_j + _i); bvirt = (double)(u3 - _j); avirt = u3 - bvirt; bround = _i - bvirt; around = _j - avirt; u[2] = around + bround; + u[3] = u3; + C1length = FastExpansionSumZeroElim(4, B, 4, u, C1); + + s1 = (double)(acx * bcytail); c = (double)(splitter * acx); abig = (double)(c - acx); ahi = c - abig; alo = acx - ahi; c = (double)(splitter * bcytail); abig = (double)(c - bcytail); bhi = c - abig; blo = bcytail - bhi; err1 = s1 - (ahi * bhi); err2 = err1 - (alo * bhi); err3 = err2 - (ahi * blo); s0 = (alo * blo) - err3; + t1 = (double)(acy * bcxtail); c = (double)(splitter * acy); abig = (double)(c - acy); ahi = c - abig; alo = acy - ahi; c = (double)(splitter * bcxtail); abig = (double)(c - bcxtail); bhi = c - abig; blo = bcxtail - bhi; err1 = t1 - (ahi * bhi); err2 = err1 - (alo * bhi); err3 = err2 - (ahi * blo); t0 = (alo * blo) - err3; + _i = (double)(s0 - t0); bvirt = (double)(s0 - _i); avirt = _i + bvirt; bround = bvirt - t0; around = s0 - avirt; u[0] = around + bround; _j = (double)(s1 + _i); bvirt = (double)(_j - s1); avirt = _j - bvirt; bround = _i - bvirt; around = s1 - avirt; _0 = around + bround; _i = (double)(_0 - t1); bvirt = (double)(_0 - _i); avirt = _i + bvirt; bround = bvirt - t1; around = _0 - avirt; u[1] = around + bround; u3 = (double)(_j + _i); bvirt = (double)(u3 - _j); avirt = u3 - bvirt; bround = _i - bvirt; around = _j - avirt; u[2] = around + bround; + u[3] = u3; + C2length = FastExpansionSumZeroElim(C1length, C1, 4, u, C2); + + s1 = (double)(acxtail * bcytail); c = (double)(splitter * acxtail); abig = (double)(c - acxtail); ahi = c - abig; alo = acxtail - ahi; c = (double)(splitter * bcytail); abig = (double)(c - bcytail); bhi = c - abig; blo = bcytail - bhi; err1 = s1 - (ahi * bhi); err2 = err1 - (alo * bhi); err3 = err2 - (ahi * blo); s0 = (alo * blo) - err3; + t1 = (double)(acytail * bcxtail); c = (double)(splitter * acytail); abig = (double)(c - acytail); ahi = c - abig; alo = acytail - ahi; c = (double)(splitter * bcxtail); abig = (double)(c - bcxtail); bhi = c - abig; blo = bcxtail - bhi; err1 = t1 - (ahi * bhi); err2 = err1 - (alo * bhi); err3 = err2 - (ahi * blo); t0 = (alo * blo) - err3; + _i = (double)(s0 - t0); bvirt = (double)(s0 - _i); avirt = _i + bvirt; bround = bvirt - t0; around = s0 - avirt; u[0] = around + bround; _j = (double)(s1 + _i); bvirt = (double)(_j - s1); avirt = _j - bvirt; bround = _i - bvirt; around = s1 - avirt; _0 = around + bround; _i = (double)(_0 - t1); bvirt = (double)(_0 - _i); avirt = _i + bvirt; bround = bvirt - t1; around = _0 - avirt; u[1] = around + bround; u3 = (double)(_j + _i); bvirt = (double)(u3 - _j); avirt = u3 - bvirt; bround = _i - bvirt; around = _j - avirt; u[2] = around + bround; + u[3] = u3; + Dlength = FastExpansionSumZeroElim(C2length, C2, 4, u, D); + + return (D[Dlength - 1]); + } + + /// + /// Return a positive value if the point pd lies inside the circle passing through + /// pa, pb, and pc; a negative value if it lies outside; and zero if the four points + /// are cocircular. The points pa, pb, and pc must be in counterclockwise order, or + /// the sign of the result will be reversed. + /// + /// + /// + /// + /// + /// + /// + /// + /// Uses exact arithmetic if necessary to ensure a correct answer. The result returned + /// is the determinant of a matrix. This determinant is computed adaptively, in the + /// sense that exact arithmetic is used only to the degree it is needed to ensure that + /// the returned value has the correct sign. Hence, this function is usually quite fast, + /// but will run more slowly when the input points are cocircular or nearly so. + /// + private double InCircleAdapt(Point pa, Point pb, Point pc, Point pd, double permanent) + { + double adx, bdx, cdx, ady, bdy, cdy; + double det, errbound; + + double bdxcdy1, cdxbdy1, cdxady1, adxcdy1, adxbdy1, bdxady1; + double bdxcdy0, cdxbdy0, cdxady0, adxcdy0, adxbdy0, bdxady0; + double[] bc = new double[4], ca = new double[4], ab = new double[4]; + double bc3, ca3, ab3; + int axbclen, axxbclen, aybclen, ayybclen, alen; + int bxcalen, bxxcalen, bycalen, byycalen, blen; + int cxablen, cxxablen, cyablen, cyyablen, clen; + int ablen; + double[] finnow, finother, finswap; + int finlength; + + double adxtail, bdxtail, cdxtail, adytail, bdytail, cdytail; + double adxadx1, adyady1, bdxbdx1, bdybdy1, cdxcdx1, cdycdy1; + double adxadx0, adyady0, bdxbdx0, bdybdy0, cdxcdx0, cdycdy0; + double[] aa = new double[4], bb = new double[4], cc = new double[4]; + double aa3, bb3, cc3; + double ti1, tj1; + double ti0, tj0; + // Edited to work around index out of range exceptions (changed array length from 4 to 5). + // See unsafe indexing in FastExpansionSumZeroElim. + double[] u = new double[5], v = new double[5]; + double u3, v3; + int temp8len, temp16alen, temp16blen, temp16clen; + int temp32alen, temp32blen, temp48len, temp64len; + double[] axtbb = new double[8], axtcc = new double[8], aytbb = new double[8], aytcc = new double[8]; + int axtbblen, axtcclen, aytbblen, aytcclen; + double[] bxtaa = new double[8], bxtcc = new double[8], bytaa = new double[8], bytcc = new double[8]; + int bxtaalen, bxtcclen, bytaalen, bytcclen; + double[] cxtaa = new double[8], cxtbb = new double[8], cytaa = new double[8], cytbb = new double[8]; + int cxtaalen, cxtbblen, cytaalen, cytbblen; + double[] axtbc = new double[8], aytbc = new double[8], bxtca = new double[8], bytca = new double[8], cxtab = new double[8], cytab = new double[8]; + int axtbclen = 0, aytbclen = 0, bxtcalen = 0, bytcalen = 0, cxtablen = 0, cytablen = 0; + double[] axtbct = new double[16], aytbct = new double[16], bxtcat = new double[16], bytcat = new double[16], cxtabt = new double[16], cytabt = new double[16]; + int axtbctlen, aytbctlen, bxtcatlen, bytcatlen, cxtabtlen, cytabtlen; + double[] axtbctt = new double[8], aytbctt = new double[8], bxtcatt = new double[8]; + double[] bytcatt = new double[8], cxtabtt = new double[8], cytabtt = new double[8]; + int axtbcttlen, aytbcttlen, bxtcattlen, bytcattlen, cxtabttlen, cytabttlen; + double[] abt = new double[8], bct = new double[8], cat = new double[8]; + int abtlen, bctlen, catlen; + double[] abtt = new double[4], bctt = new double[4], catt = new double[4]; + int abttlen, bcttlen, cattlen; + double abtt3, bctt3, catt3; + double negate; + + double bvirt; + double avirt, bround, around; + double c; + double abig; + double ahi, alo, bhi, blo; + double err1, err2, err3; + double _i, _j; + double _0; + + adx = (double)(pa.x - pd.x); + bdx = (double)(pb.x - pd.x); + cdx = (double)(pc.x - pd.x); + ady = (double)(pa.y - pd.y); + bdy = (double)(pb.y - pd.y); + cdy = (double)(pc.y - pd.y); + + adx = (double)(pa.x - pd.x); + bdx = (double)(pb.x - pd.x); + cdx = (double)(pc.x - pd.x); + ady = (double)(pa.y - pd.y); + bdy = (double)(pb.y - pd.y); + cdy = (double)(pc.y - pd.y); + + bdxcdy1 = (double)(bdx * cdy); c = (double)(splitter * bdx); abig = (double)(c - bdx); ahi = c - abig; alo = bdx - ahi; c = (double)(splitter * cdy); abig = (double)(c - cdy); bhi = c - abig; blo = cdy - bhi; err1 = bdxcdy1 - (ahi * bhi); err2 = err1 - (alo * bhi); err3 = err2 - (ahi * blo); bdxcdy0 = (alo * blo) - err3; + cdxbdy1 = (double)(cdx * bdy); c = (double)(splitter * cdx); abig = (double)(c - cdx); ahi = c - abig; alo = cdx - ahi; c = (double)(splitter * bdy); abig = (double)(c - bdy); bhi = c - abig; blo = bdy - bhi; err1 = cdxbdy1 - (ahi * bhi); err2 = err1 - (alo * bhi); err3 = err2 - (ahi * blo); cdxbdy0 = (alo * blo) - err3; + _i = (double)(bdxcdy0 - cdxbdy0); bvirt = (double)(bdxcdy0 - _i); avirt = _i + bvirt; bround = bvirt - cdxbdy0; around = bdxcdy0 - avirt; bc[0] = around + bround; _j = (double)(bdxcdy1 + _i); bvirt = (double)(_j - bdxcdy1); avirt = _j - bvirt; bround = _i - bvirt; around = bdxcdy1 - avirt; _0 = around + bround; _i = (double)(_0 - cdxbdy1); bvirt = (double)(_0 - _i); avirt = _i + bvirt; bround = bvirt - cdxbdy1; around = _0 - avirt; bc[1] = around + bround; bc3 = (double)(_j + _i); bvirt = (double)(bc3 - _j); avirt = bc3 - bvirt; bround = _i - bvirt; around = _j - avirt; bc[2] = around + bround; + bc[3] = bc3; + axbclen = ScaleExpansionZeroElim(4, bc, adx, axbc); + axxbclen = ScaleExpansionZeroElim(axbclen, axbc, adx, axxbc); + aybclen = ScaleExpansionZeroElim(4, bc, ady, aybc); + ayybclen = ScaleExpansionZeroElim(aybclen, aybc, ady, ayybc); + alen = FastExpansionSumZeroElim(axxbclen, axxbc, ayybclen, ayybc, adet); + + cdxady1 = (double)(cdx * ady); c = (double)(splitter * cdx); abig = (double)(c - cdx); ahi = c - abig; alo = cdx - ahi; c = (double)(splitter * ady); abig = (double)(c - ady); bhi = c - abig; blo = ady - bhi; err1 = cdxady1 - (ahi * bhi); err2 = err1 - (alo * bhi); err3 = err2 - (ahi * blo); cdxady0 = (alo * blo) - err3; + adxcdy1 = (double)(adx * cdy); c = (double)(splitter * adx); abig = (double)(c - adx); ahi = c - abig; alo = adx - ahi; c = (double)(splitter * cdy); abig = (double)(c - cdy); bhi = c - abig; blo = cdy - bhi; err1 = adxcdy1 - (ahi * bhi); err2 = err1 - (alo * bhi); err3 = err2 - (ahi * blo); adxcdy0 = (alo * blo) - err3; + _i = (double)(cdxady0 - adxcdy0); bvirt = (double)(cdxady0 - _i); avirt = _i + bvirt; bround = bvirt - adxcdy0; around = cdxady0 - avirt; ca[0] = around + bround; _j = (double)(cdxady1 + _i); bvirt = (double)(_j - cdxady1); avirt = _j - bvirt; bround = _i - bvirt; around = cdxady1 - avirt; _0 = around + bround; _i = (double)(_0 - adxcdy1); bvirt = (double)(_0 - _i); avirt = _i + bvirt; bround = bvirt - adxcdy1; around = _0 - avirt; ca[1] = around + bround; ca3 = (double)(_j + _i); bvirt = (double)(ca3 - _j); avirt = ca3 - bvirt; bround = _i - bvirt; around = _j - avirt; ca[2] = around + bround; + ca[3] = ca3; + bxcalen = ScaleExpansionZeroElim(4, ca, bdx, bxca); + bxxcalen = ScaleExpansionZeroElim(bxcalen, bxca, bdx, bxxca); + bycalen = ScaleExpansionZeroElim(4, ca, bdy, byca); + byycalen = ScaleExpansionZeroElim(bycalen, byca, bdy, byyca); + blen = FastExpansionSumZeroElim(bxxcalen, bxxca, byycalen, byyca, bdet); + + adxbdy1 = (double)(adx * bdy); c = (double)(splitter * adx); abig = (double)(c - adx); ahi = c - abig; alo = adx - ahi; c = (double)(splitter * bdy); abig = (double)(c - bdy); bhi = c - abig; blo = bdy - bhi; err1 = adxbdy1 - (ahi * bhi); err2 = err1 - (alo * bhi); err3 = err2 - (ahi * blo); adxbdy0 = (alo * blo) - err3; + bdxady1 = (double)(bdx * ady); c = (double)(splitter * bdx); abig = (double)(c - bdx); ahi = c - abig; alo = bdx - ahi; c = (double)(splitter * ady); abig = (double)(c - ady); bhi = c - abig; blo = ady - bhi; err1 = bdxady1 - (ahi * bhi); err2 = err1 - (alo * bhi); err3 = err2 - (ahi * blo); bdxady0 = (alo * blo) - err3; + _i = (double)(adxbdy0 - bdxady0); bvirt = (double)(adxbdy0 - _i); avirt = _i + bvirt; bround = bvirt - bdxady0; around = adxbdy0 - avirt; ab[0] = around + bround; _j = (double)(adxbdy1 + _i); bvirt = (double)(_j - adxbdy1); avirt = _j - bvirt; bround = _i - bvirt; around = adxbdy1 - avirt; _0 = around + bround; _i = (double)(_0 - bdxady1); bvirt = (double)(_0 - _i); avirt = _i + bvirt; bround = bvirt - bdxady1; around = _0 - avirt; ab[1] = around + bround; ab3 = (double)(_j + _i); bvirt = (double)(ab3 - _j); avirt = ab3 - bvirt; bround = _i - bvirt; around = _j - avirt; ab[2] = around + bround; + ab[3] = ab3; + cxablen = ScaleExpansionZeroElim(4, ab, cdx, cxab); + cxxablen = ScaleExpansionZeroElim(cxablen, cxab, cdx, cxxab); + cyablen = ScaleExpansionZeroElim(4, ab, cdy, cyab); + cyyablen = ScaleExpansionZeroElim(cyablen, cyab, cdy, cyyab); + clen = FastExpansionSumZeroElim(cxxablen, cxxab, cyyablen, cyyab, cdet); + + ablen = FastExpansionSumZeroElim(alen, adet, blen, bdet, abdet); + finlength = FastExpansionSumZeroElim(ablen, abdet, clen, cdet, fin1); + + det = Estimate(finlength, fin1); + errbound = iccerrboundB * permanent; + if ((det >= errbound) || (-det >= errbound)) + { + return det; + } + + bvirt = (double)(pa.x - adx); avirt = adx + bvirt; bround = bvirt - pd.x; around = pa.x - avirt; adxtail = around + bround; + bvirt = (double)(pa.y - ady); avirt = ady + bvirt; bround = bvirt - pd.y; around = pa.y - avirt; adytail = around + bround; + bvirt = (double)(pb.x - bdx); avirt = bdx + bvirt; bround = bvirt - pd.x; around = pb.x - avirt; bdxtail = around + bround; + bvirt = (double)(pb.y - bdy); avirt = bdy + bvirt; bround = bvirt - pd.y; around = pb.y - avirt; bdytail = around + bround; + bvirt = (double)(pc.x - cdx); avirt = cdx + bvirt; bround = bvirt - pd.x; around = pc.x - avirt; cdxtail = around + bround; + bvirt = (double)(pc.y - cdy); avirt = cdy + bvirt; bround = bvirt - pd.y; around = pc.y - avirt; cdytail = around + bround; + if ((adxtail == 0.0) && (bdxtail == 0.0) && (cdxtail == 0.0) + && (adytail == 0.0) && (bdytail == 0.0) && (cdytail == 0.0)) + { + return det; + } + + errbound = iccerrboundC * permanent + resulterrbound * ((det) >= 0.0 ? (det) : -(det)); + det += ((adx * adx + ady * ady) * ((bdx * cdytail + cdy * bdxtail) - (bdy * cdxtail + cdx * bdytail)) + + 2.0 * (adx * adxtail + ady * adytail) * (bdx * cdy - bdy * cdx)) + + ((bdx * bdx + bdy * bdy) * ((cdx * adytail + ady * cdxtail) - (cdy * adxtail + adx * cdytail)) + + 2.0 * (bdx * bdxtail + bdy * bdytail) * (cdx * ady - cdy * adx)) + + ((cdx * cdx + cdy * cdy) * ((adx * bdytail + bdy * adxtail) - (ady * bdxtail + bdx * adytail)) + + 2.0 * (cdx * cdxtail + cdy * cdytail) * (adx * bdy - ady * bdx)); + if ((det >= errbound) || (-det >= errbound)) + { + return det; + } + + finnow = fin1; + finother = fin2; + + if ((bdxtail != 0.0) || (bdytail != 0.0) || (cdxtail != 0.0) || (cdytail != 0.0)) + { + adxadx1 = (double)(adx * adx); c = (double)(splitter * adx); abig = (double)(c - adx); ahi = c - abig; alo = adx - ahi; err1 = adxadx1 - (ahi * ahi); err3 = err1 - ((ahi + ahi) * alo); adxadx0 = (alo * alo) - err3; + adyady1 = (double)(ady * ady); c = (double)(splitter * ady); abig = (double)(c - ady); ahi = c - abig; alo = ady - ahi; err1 = adyady1 - (ahi * ahi); err3 = err1 - ((ahi + ahi) * alo); adyady0 = (alo * alo) - err3; + _i = (double)(adxadx0 + adyady0); bvirt = (double)(_i - adxadx0); avirt = _i - bvirt; bround = adyady0 - bvirt; around = adxadx0 - avirt; aa[0] = around + bround; _j = (double)(adxadx1 + _i); bvirt = (double)(_j - adxadx1); avirt = _j - bvirt; bround = _i - bvirt; around = adxadx1 - avirt; _0 = around + bround; _i = (double)(_0 + adyady1); bvirt = (double)(_i - _0); avirt = _i - bvirt; bround = adyady1 - bvirt; around = _0 - avirt; aa[1] = around + bround; aa3 = (double)(_j + _i); bvirt = (double)(aa3 - _j); avirt = aa3 - bvirt; bround = _i - bvirt; around = _j - avirt; aa[2] = around + bround; + aa[3] = aa3; + } + if ((cdxtail != 0.0) || (cdytail != 0.0) || (adxtail != 0.0) || (adytail != 0.0)) + { + bdxbdx1 = (double)(bdx * bdx); c = (double)(splitter * bdx); abig = (double)(c - bdx); ahi = c - abig; alo = bdx - ahi; err1 = bdxbdx1 - (ahi * ahi); err3 = err1 - ((ahi + ahi) * alo); bdxbdx0 = (alo * alo) - err3; + bdybdy1 = (double)(bdy * bdy); c = (double)(splitter * bdy); abig = (double)(c - bdy); ahi = c - abig; alo = bdy - ahi; err1 = bdybdy1 - (ahi * ahi); err3 = err1 - ((ahi + ahi) * alo); bdybdy0 = (alo * alo) - err3; + _i = (double)(bdxbdx0 + bdybdy0); bvirt = (double)(_i - bdxbdx0); avirt = _i - bvirt; bround = bdybdy0 - bvirt; around = bdxbdx0 - avirt; bb[0] = around + bround; _j = (double)(bdxbdx1 + _i); bvirt = (double)(_j - bdxbdx1); avirt = _j - bvirt; bround = _i - bvirt; around = bdxbdx1 - avirt; _0 = around + bround; _i = (double)(_0 + bdybdy1); bvirt = (double)(_i - _0); avirt = _i - bvirt; bround = bdybdy1 - bvirt; around = _0 - avirt; bb[1] = around + bround; bb3 = (double)(_j + _i); bvirt = (double)(bb3 - _j); avirt = bb3 - bvirt; bround = _i - bvirt; around = _j - avirt; bb[2] = around + bround; + bb[3] = bb3; + } + if ((adxtail != 0.0) || (adytail != 0.0) || (bdxtail != 0.0) || (bdytail != 0.0)) + { + cdxcdx1 = (double)(cdx * cdx); c = (double)(splitter * cdx); abig = (double)(c - cdx); ahi = c - abig; alo = cdx - ahi; err1 = cdxcdx1 - (ahi * ahi); err3 = err1 - ((ahi + ahi) * alo); cdxcdx0 = (alo * alo) - err3; + cdycdy1 = (double)(cdy * cdy); c = (double)(splitter * cdy); abig = (double)(c - cdy); ahi = c - abig; alo = cdy - ahi; err1 = cdycdy1 - (ahi * ahi); err3 = err1 - ((ahi + ahi) * alo); cdycdy0 = (alo * alo) - err3; + _i = (double)(cdxcdx0 + cdycdy0); bvirt = (double)(_i - cdxcdx0); avirt = _i - bvirt; bround = cdycdy0 - bvirt; around = cdxcdx0 - avirt; cc[0] = around + bround; _j = (double)(cdxcdx1 + _i); bvirt = (double)(_j - cdxcdx1); avirt = _j - bvirt; bround = _i - bvirt; around = cdxcdx1 - avirt; _0 = around + bround; _i = (double)(_0 + cdycdy1); bvirt = (double)(_i - _0); avirt = _i - bvirt; bround = cdycdy1 - bvirt; around = _0 - avirt; cc[1] = around + bround; cc3 = (double)(_j + _i); bvirt = (double)(cc3 - _j); avirt = cc3 - bvirt; bround = _i - bvirt; around = _j - avirt; cc[2] = around + bround; + cc[3] = cc3; + } + + if (adxtail != 0.0) + { + axtbclen = ScaleExpansionZeroElim(4, bc, adxtail, axtbc); + temp16alen = ScaleExpansionZeroElim(axtbclen, axtbc, 2.0 * adx, temp16a); + + axtcclen = ScaleExpansionZeroElim(4, cc, adxtail, axtcc); + temp16blen = ScaleExpansionZeroElim(axtcclen, axtcc, bdy, temp16b); + + axtbblen = ScaleExpansionZeroElim(4, bb, adxtail, axtbb); + temp16clen = ScaleExpansionZeroElim(axtbblen, axtbb, -cdy, temp16c); + + temp32alen = FastExpansionSumZeroElim(temp16alen, temp16a, temp16blen, temp16b, temp32a); + temp48len = FastExpansionSumZeroElim(temp16clen, temp16c, temp32alen, temp32a, temp48); + finlength = FastExpansionSumZeroElim(finlength, finnow, temp48len, temp48, finother); + finswap = finnow; finnow = finother; finother = finswap; + } + if (adytail != 0.0) + { + aytbclen = ScaleExpansionZeroElim(4, bc, adytail, aytbc); + temp16alen = ScaleExpansionZeroElim(aytbclen, aytbc, 2.0 * ady, temp16a); + + aytbblen = ScaleExpansionZeroElim(4, bb, adytail, aytbb); + temp16blen = ScaleExpansionZeroElim(aytbblen, aytbb, cdx, temp16b); + + aytcclen = ScaleExpansionZeroElim(4, cc, adytail, aytcc); + temp16clen = ScaleExpansionZeroElim(aytcclen, aytcc, -bdx, temp16c); + + temp32alen = FastExpansionSumZeroElim(temp16alen, temp16a, temp16blen, temp16b, temp32a); + temp48len = FastExpansionSumZeroElim(temp16clen, temp16c, temp32alen, temp32a, temp48); + finlength = FastExpansionSumZeroElim(finlength, finnow, temp48len, temp48, finother); + finswap = finnow; finnow = finother; finother = finswap; + } + if (bdxtail != 0.0) + { + bxtcalen = ScaleExpansionZeroElim(4, ca, bdxtail, bxtca); + temp16alen = ScaleExpansionZeroElim(bxtcalen, bxtca, 2.0 * bdx, temp16a); + + bxtaalen = ScaleExpansionZeroElim(4, aa, bdxtail, bxtaa); + temp16blen = ScaleExpansionZeroElim(bxtaalen, bxtaa, cdy, temp16b); + + bxtcclen = ScaleExpansionZeroElim(4, cc, bdxtail, bxtcc); + temp16clen = ScaleExpansionZeroElim(bxtcclen, bxtcc, -ady, temp16c); + + temp32alen = FastExpansionSumZeroElim(temp16alen, temp16a, temp16blen, temp16b, temp32a); + temp48len = FastExpansionSumZeroElim(temp16clen, temp16c, temp32alen, temp32a, temp48); + finlength = FastExpansionSumZeroElim(finlength, finnow, temp48len, temp48, finother); + finswap = finnow; finnow = finother; finother = finswap; + } + if (bdytail != 0.0) + { + bytcalen = ScaleExpansionZeroElim(4, ca, bdytail, bytca); + temp16alen = ScaleExpansionZeroElim(bytcalen, bytca, 2.0 * bdy, temp16a); + + bytcclen = ScaleExpansionZeroElim(4, cc, bdytail, bytcc); + temp16blen = ScaleExpansionZeroElim(bytcclen, bytcc, adx, temp16b); + + bytaalen = ScaleExpansionZeroElim(4, aa, bdytail, bytaa); + temp16clen = ScaleExpansionZeroElim(bytaalen, bytaa, -cdx, temp16c); + + temp32alen = FastExpansionSumZeroElim(temp16alen, temp16a, temp16blen, temp16b, temp32a); + temp48len = FastExpansionSumZeroElim(temp16clen, temp16c, temp32alen, temp32a, temp48); + finlength = FastExpansionSumZeroElim(finlength, finnow, temp48len, temp48, finother); + finswap = finnow; finnow = finother; finother = finswap; + } + if (cdxtail != 0.0) + { + cxtablen = ScaleExpansionZeroElim(4, ab, cdxtail, cxtab); + temp16alen = ScaleExpansionZeroElim(cxtablen, cxtab, 2.0 * cdx, temp16a); + + cxtbblen = ScaleExpansionZeroElim(4, bb, cdxtail, cxtbb); + temp16blen = ScaleExpansionZeroElim(cxtbblen, cxtbb, ady, temp16b); + + cxtaalen = ScaleExpansionZeroElim(4, aa, cdxtail, cxtaa); + temp16clen = ScaleExpansionZeroElim(cxtaalen, cxtaa, -bdy, temp16c); + + temp32alen = FastExpansionSumZeroElim(temp16alen, temp16a, temp16blen, temp16b, temp32a); + temp48len = FastExpansionSumZeroElim(temp16clen, temp16c, temp32alen, temp32a, temp48); + finlength = FastExpansionSumZeroElim(finlength, finnow, temp48len, temp48, finother); + finswap = finnow; finnow = finother; finother = finswap; + } + if (cdytail != 0.0) + { + cytablen = ScaleExpansionZeroElim(4, ab, cdytail, cytab); + temp16alen = ScaleExpansionZeroElim(cytablen, cytab, 2.0 * cdy, temp16a); + + cytaalen = ScaleExpansionZeroElim(4, aa, cdytail, cytaa); + temp16blen = ScaleExpansionZeroElim(cytaalen, cytaa, bdx, temp16b); + + cytbblen = ScaleExpansionZeroElim(4, bb, cdytail, cytbb); + temp16clen = ScaleExpansionZeroElim(cytbblen, cytbb, -adx, temp16c); + + temp32alen = FastExpansionSumZeroElim(temp16alen, temp16a, temp16blen, temp16b, temp32a); + temp48len = FastExpansionSumZeroElim(temp16clen, temp16c, temp32alen, temp32a, temp48); + finlength = FastExpansionSumZeroElim(finlength, finnow, temp48len, temp48, finother); + finswap = finnow; finnow = finother; finother = finswap; + } + + if ((adxtail != 0.0) || (adytail != 0.0)) + { + if ((bdxtail != 0.0) || (bdytail != 0.0) + || (cdxtail != 0.0) || (cdytail != 0.0)) + { + ti1 = (double)(bdxtail * cdy); c = (double)(splitter * bdxtail); abig = (double)(c - bdxtail); ahi = c - abig; alo = bdxtail - ahi; c = (double)(splitter * cdy); abig = (double)(c - cdy); bhi = c - abig; blo = cdy - bhi; err1 = ti1 - (ahi * bhi); err2 = err1 - (alo * bhi); err3 = err2 - (ahi * blo); ti0 = (alo * blo) - err3; + tj1 = (double)(bdx * cdytail); c = (double)(splitter * bdx); abig = (double)(c - bdx); ahi = c - abig; alo = bdx - ahi; c = (double)(splitter * cdytail); abig = (double)(c - cdytail); bhi = c - abig; blo = cdytail - bhi; err1 = tj1 - (ahi * bhi); err2 = err1 - (alo * bhi); err3 = err2 - (ahi * blo); tj0 = (alo * blo) - err3; + _i = (double)(ti0 + tj0); bvirt = (double)(_i - ti0); avirt = _i - bvirt; bround = tj0 - bvirt; around = ti0 - avirt; u[0] = around + bround; _j = (double)(ti1 + _i); bvirt = (double)(_j - ti1); avirt = _j - bvirt; bround = _i - bvirt; around = ti1 - avirt; _0 = around + bround; _i = (double)(_0 + tj1); bvirt = (double)(_i - _0); avirt = _i - bvirt; bround = tj1 - bvirt; around = _0 - avirt; u[1] = around + bround; u3 = (double)(_j + _i); bvirt = (double)(u3 - _j); avirt = u3 - bvirt; bround = _i - bvirt; around = _j - avirt; u[2] = around + bround; + u[3] = u3; + negate = -bdy; + ti1 = (double)(cdxtail * negate); c = (double)(splitter * cdxtail); abig = (double)(c - cdxtail); ahi = c - abig; alo = cdxtail - ahi; c = (double)(splitter * negate); abig = (double)(c - negate); bhi = c - abig; blo = negate - bhi; err1 = ti1 - (ahi * bhi); err2 = err1 - (alo * bhi); err3 = err2 - (ahi * blo); ti0 = (alo * blo) - err3; + negate = -bdytail; + tj1 = (double)(cdx * negate); c = (double)(splitter * cdx); abig = (double)(c - cdx); ahi = c - abig; alo = cdx - ahi; c = (double)(splitter * negate); abig = (double)(c - negate); bhi = c - abig; blo = negate - bhi; err1 = tj1 - (ahi * bhi); err2 = err1 - (alo * bhi); err3 = err2 - (ahi * blo); tj0 = (alo * blo) - err3; + _i = (double)(ti0 + tj0); bvirt = (double)(_i - ti0); avirt = _i - bvirt; bround = tj0 - bvirt; around = ti0 - avirt; v[0] = around + bround; _j = (double)(ti1 + _i); bvirt = (double)(_j - ti1); avirt = _j - bvirt; bround = _i - bvirt; around = ti1 - avirt; _0 = around + bround; _i = (double)(_0 + tj1); bvirt = (double)(_i - _0); avirt = _i - bvirt; bround = tj1 - bvirt; around = _0 - avirt; v[1] = around + bround; v3 = (double)(_j + _i); bvirt = (double)(v3 - _j); avirt = v3 - bvirt; bround = _i - bvirt; around = _j - avirt; v[2] = around + bround; + v[3] = v3; + bctlen = FastExpansionSumZeroElim(4, u, 4, v, bct); + + ti1 = (double)(bdxtail * cdytail); c = (double)(splitter * bdxtail); abig = (double)(c - bdxtail); ahi = c - abig; alo = bdxtail - ahi; c = (double)(splitter * cdytail); abig = (double)(c - cdytail); bhi = c - abig; blo = cdytail - bhi; err1 = ti1 - (ahi * bhi); err2 = err1 - (alo * bhi); err3 = err2 - (ahi * blo); ti0 = (alo * blo) - err3; + tj1 = (double)(cdxtail * bdytail); c = (double)(splitter * cdxtail); abig = (double)(c - cdxtail); ahi = c - abig; alo = cdxtail - ahi; c = (double)(splitter * bdytail); abig = (double)(c - bdytail); bhi = c - abig; blo = bdytail - bhi; err1 = tj1 - (ahi * bhi); err2 = err1 - (alo * bhi); err3 = err2 - (ahi * blo); tj0 = (alo * blo) - err3; + _i = (double)(ti0 - tj0); bvirt = (double)(ti0 - _i); avirt = _i + bvirt; bround = bvirt - tj0; around = ti0 - avirt; bctt[0] = around + bround; _j = (double)(ti1 + _i); bvirt = (double)(_j - ti1); avirt = _j - bvirt; bround = _i - bvirt; around = ti1 - avirt; _0 = around + bround; _i = (double)(_0 - tj1); bvirt = (double)(_0 - _i); avirt = _i + bvirt; bround = bvirt - tj1; around = _0 - avirt; bctt[1] = around + bround; bctt3 = (double)(_j + _i); bvirt = (double)(bctt3 - _j); avirt = bctt3 - bvirt; bround = _i - bvirt; around = _j - avirt; bctt[2] = around + bround; + bctt[3] = bctt3; + bcttlen = 4; + } + else + { + bct[0] = 0.0; + bctlen = 1; + bctt[0] = 0.0; + bcttlen = 1; + } + + if (adxtail != 0.0) + { + temp16alen = ScaleExpansionZeroElim(axtbclen, axtbc, adxtail, temp16a); + axtbctlen = ScaleExpansionZeroElim(bctlen, bct, adxtail, axtbct); + temp32alen = ScaleExpansionZeroElim(axtbctlen, axtbct, 2.0 * adx, temp32a); + temp48len = FastExpansionSumZeroElim(temp16alen, temp16a, temp32alen, temp32a, temp48); + finlength = FastExpansionSumZeroElim(finlength, finnow, temp48len, temp48, finother); + finswap = finnow; finnow = finother; finother = finswap; + if (bdytail != 0.0) + { + temp8len = ScaleExpansionZeroElim(4, cc, adxtail, temp8); + temp16alen = ScaleExpansionZeroElim(temp8len, temp8, bdytail, temp16a); + finlength = FastExpansionSumZeroElim(finlength, finnow, temp16alen, temp16a, finother); + finswap = finnow; finnow = finother; finother = finswap; + } + if (cdytail != 0.0) + { + temp8len = ScaleExpansionZeroElim(4, bb, -adxtail, temp8); + temp16alen = ScaleExpansionZeroElim(temp8len, temp8, cdytail, temp16a); + finlength = FastExpansionSumZeroElim(finlength, finnow, temp16alen, temp16a, finother); + finswap = finnow; finnow = finother; finother = finswap; + } + + temp32alen = ScaleExpansionZeroElim(axtbctlen, axtbct, adxtail, temp32a); + axtbcttlen = ScaleExpansionZeroElim(bcttlen, bctt, adxtail, axtbctt); + temp16alen = ScaleExpansionZeroElim(axtbcttlen, axtbctt, 2.0 * adx, temp16a); + temp16blen = ScaleExpansionZeroElim(axtbcttlen, axtbctt, adxtail, temp16b); + temp32blen = FastExpansionSumZeroElim(temp16alen, temp16a, temp16blen, temp16b, temp32b); + temp64len = FastExpansionSumZeroElim(temp32alen, temp32a, temp32blen, temp32b, temp64); + finlength = FastExpansionSumZeroElim(finlength, finnow, temp64len, temp64, finother); + finswap = finnow; finnow = finother; finother = finswap; + } + if (adytail != 0.0) + { + temp16alen = ScaleExpansionZeroElim(aytbclen, aytbc, adytail, temp16a); + aytbctlen = ScaleExpansionZeroElim(bctlen, bct, adytail, aytbct); + temp32alen = ScaleExpansionZeroElim(aytbctlen, aytbct, 2.0 * ady, temp32a); + temp48len = FastExpansionSumZeroElim(temp16alen, temp16a, temp32alen, temp32a, temp48); + finlength = FastExpansionSumZeroElim(finlength, finnow, temp48len, temp48, finother); + finswap = finnow; finnow = finother; finother = finswap; + + + temp32alen = ScaleExpansionZeroElim(aytbctlen, aytbct, adytail, temp32a); + aytbcttlen = ScaleExpansionZeroElim(bcttlen, bctt, adytail, aytbctt); + temp16alen = ScaleExpansionZeroElim(aytbcttlen, aytbctt, 2.0 * ady, temp16a); + temp16blen = ScaleExpansionZeroElim(aytbcttlen, aytbctt, adytail, temp16b); + temp32blen = FastExpansionSumZeroElim(temp16alen, temp16a, temp16blen, temp16b, temp32b); + temp64len = FastExpansionSumZeroElim(temp32alen, temp32a, temp32blen, temp32b, temp64); + finlength = FastExpansionSumZeroElim(finlength, finnow, temp64len, temp64, finother); + finswap = finnow; finnow = finother; finother = finswap; + } + } + if ((bdxtail != 0.0) || (bdytail != 0.0)) + { + if ((cdxtail != 0.0) || (cdytail != 0.0) + || (adxtail != 0.0) || (adytail != 0.0)) + { + ti1 = (double)(cdxtail * ady); c = (double)(splitter * cdxtail); abig = (double)(c - cdxtail); ahi = c - abig; alo = cdxtail - ahi; c = (double)(splitter * ady); abig = (double)(c - ady); bhi = c - abig; blo = ady - bhi; err1 = ti1 - (ahi * bhi); err2 = err1 - (alo * bhi); err3 = err2 - (ahi * blo); ti0 = (alo * blo) - err3; + tj1 = (double)(cdx * adytail); c = (double)(splitter * cdx); abig = (double)(c - cdx); ahi = c - abig; alo = cdx - ahi; c = (double)(splitter * adytail); abig = (double)(c - adytail); bhi = c - abig; blo = adytail - bhi; err1 = tj1 - (ahi * bhi); err2 = err1 - (alo * bhi); err3 = err2 - (ahi * blo); tj0 = (alo * blo) - err3; + _i = (double)(ti0 + tj0); bvirt = (double)(_i - ti0); avirt = _i - bvirt; bround = tj0 - bvirt; around = ti0 - avirt; u[0] = around + bround; _j = (double)(ti1 + _i); bvirt = (double)(_j - ti1); avirt = _j - bvirt; bround = _i - bvirt; around = ti1 - avirt; _0 = around + bround; _i = (double)(_0 + tj1); bvirt = (double)(_i - _0); avirt = _i - bvirt; bround = tj1 - bvirt; around = _0 - avirt; u[1] = around + bround; u3 = (double)(_j + _i); bvirt = (double)(u3 - _j); avirt = u3 - bvirt; bround = _i - bvirt; around = _j - avirt; u[2] = around + bround; + u[3] = u3; + negate = -cdy; + ti1 = (double)(adxtail * negate); c = (double)(splitter * adxtail); abig = (double)(c - adxtail); ahi = c - abig; alo = adxtail - ahi; c = (double)(splitter * negate); abig = (double)(c - negate); bhi = c - abig; blo = negate - bhi; err1 = ti1 - (ahi * bhi); err2 = err1 - (alo * bhi); err3 = err2 - (ahi * blo); ti0 = (alo * blo) - err3; + negate = -cdytail; + tj1 = (double)(adx * negate); c = (double)(splitter * adx); abig = (double)(c - adx); ahi = c - abig; alo = adx - ahi; c = (double)(splitter * negate); abig = (double)(c - negate); bhi = c - abig; blo = negate - bhi; err1 = tj1 - (ahi * bhi); err2 = err1 - (alo * bhi); err3 = err2 - (ahi * blo); tj0 = (alo * blo) - err3; + _i = (double)(ti0 + tj0); bvirt = (double)(_i - ti0); avirt = _i - bvirt; bround = tj0 - bvirt; around = ti0 - avirt; v[0] = around + bround; _j = (double)(ti1 + _i); bvirt = (double)(_j - ti1); avirt = _j - bvirt; bround = _i - bvirt; around = ti1 - avirt; _0 = around + bround; _i = (double)(_0 + tj1); bvirt = (double)(_i - _0); avirt = _i - bvirt; bround = tj1 - bvirt; around = _0 - avirt; v[1] = around + bround; v3 = (double)(_j + _i); bvirt = (double)(v3 - _j); avirt = v3 - bvirt; bround = _i - bvirt; around = _j - avirt; v[2] = around + bround; + v[3] = v3; + catlen = FastExpansionSumZeroElim(4, u, 4, v, cat); + + ti1 = (double)(cdxtail * adytail); c = (double)(splitter * cdxtail); abig = (double)(c - cdxtail); ahi = c - abig; alo = cdxtail - ahi; c = (double)(splitter * adytail); abig = (double)(c - adytail); bhi = c - abig; blo = adytail - bhi; err1 = ti1 - (ahi * bhi); err2 = err1 - (alo * bhi); err3 = err2 - (ahi * blo); ti0 = (alo * blo) - err3; + tj1 = (double)(adxtail * cdytail); c = (double)(splitter * adxtail); abig = (double)(c - adxtail); ahi = c - abig; alo = adxtail - ahi; c = (double)(splitter * cdytail); abig = (double)(c - cdytail); bhi = c - abig; blo = cdytail - bhi; err1 = tj1 - (ahi * bhi); err2 = err1 - (alo * bhi); err3 = err2 - (ahi * blo); tj0 = (alo * blo) - err3; + _i = (double)(ti0 - tj0); bvirt = (double)(ti0 - _i); avirt = _i + bvirt; bround = bvirt - tj0; around = ti0 - avirt; catt[0] = around + bround; _j = (double)(ti1 + _i); bvirt = (double)(_j - ti1); avirt = _j - bvirt; bround = _i - bvirt; around = ti1 - avirt; _0 = around + bround; _i = (double)(_0 - tj1); bvirt = (double)(_0 - _i); avirt = _i + bvirt; bround = bvirt - tj1; around = _0 - avirt; catt[1] = around + bround; catt3 = (double)(_j + _i); bvirt = (double)(catt3 - _j); avirt = catt3 - bvirt; bround = _i - bvirt; around = _j - avirt; catt[2] = around + bround; + catt[3] = catt3; + cattlen = 4; + } + else + { + cat[0] = 0.0; + catlen = 1; + catt[0] = 0.0; + cattlen = 1; + } + + if (bdxtail != 0.0) + { + temp16alen = ScaleExpansionZeroElim(bxtcalen, bxtca, bdxtail, temp16a); + bxtcatlen = ScaleExpansionZeroElim(catlen, cat, bdxtail, bxtcat); + temp32alen = ScaleExpansionZeroElim(bxtcatlen, bxtcat, 2.0 * bdx, temp32a); + temp48len = FastExpansionSumZeroElim(temp16alen, temp16a, temp32alen, temp32a, temp48); + finlength = FastExpansionSumZeroElim(finlength, finnow, temp48len, temp48, finother); + finswap = finnow; finnow = finother; finother = finswap; + if (cdytail != 0.0) + { + temp8len = ScaleExpansionZeroElim(4, aa, bdxtail, temp8); + temp16alen = ScaleExpansionZeroElim(temp8len, temp8, cdytail, temp16a); + finlength = FastExpansionSumZeroElim(finlength, finnow, temp16alen, temp16a, finother); + finswap = finnow; finnow = finother; finother = finswap; + } + if (adytail != 0.0) + { + temp8len = ScaleExpansionZeroElim(4, cc, -bdxtail, temp8); + temp16alen = ScaleExpansionZeroElim(temp8len, temp8, adytail, temp16a); + finlength = FastExpansionSumZeroElim(finlength, finnow, temp16alen, temp16a, finother); + finswap = finnow; finnow = finother; finother = finswap; + } + + temp32alen = ScaleExpansionZeroElim(bxtcatlen, bxtcat, bdxtail, temp32a); + bxtcattlen = ScaleExpansionZeroElim(cattlen, catt, bdxtail, bxtcatt); + temp16alen = ScaleExpansionZeroElim(bxtcattlen, bxtcatt, 2.0 * bdx, temp16a); + temp16blen = ScaleExpansionZeroElim(bxtcattlen, bxtcatt, bdxtail, temp16b); + temp32blen = FastExpansionSumZeroElim(temp16alen, temp16a, temp16blen, temp16b, temp32b); + temp64len = FastExpansionSumZeroElim(temp32alen, temp32a, temp32blen, temp32b, temp64); + finlength = FastExpansionSumZeroElim(finlength, finnow, temp64len, temp64, finother); + finswap = finnow; finnow = finother; finother = finswap; + } + if (bdytail != 0.0) + { + temp16alen = ScaleExpansionZeroElim(bytcalen, bytca, bdytail, temp16a); + bytcatlen = ScaleExpansionZeroElim(catlen, cat, bdytail, bytcat); + temp32alen = ScaleExpansionZeroElim(bytcatlen, bytcat, 2.0 * bdy, temp32a); + temp48len = FastExpansionSumZeroElim(temp16alen, temp16a, temp32alen, temp32a, temp48); + finlength = FastExpansionSumZeroElim(finlength, finnow, temp48len, temp48, finother); + finswap = finnow; finnow = finother; finother = finswap; + + temp32alen = ScaleExpansionZeroElim(bytcatlen, bytcat, bdytail, temp32a); + bytcattlen = ScaleExpansionZeroElim(cattlen, catt, bdytail, bytcatt); + temp16alen = ScaleExpansionZeroElim(bytcattlen, bytcatt, 2.0 * bdy, temp16a); + temp16blen = ScaleExpansionZeroElim(bytcattlen, bytcatt, bdytail, temp16b); + temp32blen = FastExpansionSumZeroElim(temp16alen, temp16a, temp16blen, temp16b, temp32b); + temp64len = FastExpansionSumZeroElim(temp32alen, temp32a, temp32blen, temp32b, temp64); + finlength = FastExpansionSumZeroElim(finlength, finnow, temp64len, temp64, finother); + finswap = finnow; finnow = finother; finother = finswap; + } + } + if ((cdxtail != 0.0) || (cdytail != 0.0)) + { + if ((adxtail != 0.0) || (adytail != 0.0) + || (bdxtail != 0.0) || (bdytail != 0.0)) + { + ti1 = (double)(adxtail * bdy); c = (double)(splitter * adxtail); abig = (double)(c - adxtail); ahi = c - abig; alo = adxtail - ahi; c = (double)(splitter * bdy); abig = (double)(c - bdy); bhi = c - abig; blo = bdy - bhi; err1 = ti1 - (ahi * bhi); err2 = err1 - (alo * bhi); err3 = err2 - (ahi * blo); ti0 = (alo * blo) - err3; + tj1 = (double)(adx * bdytail); c = (double)(splitter * adx); abig = (double)(c - adx); ahi = c - abig; alo = adx - ahi; c = (double)(splitter * bdytail); abig = (double)(c - bdytail); bhi = c - abig; blo = bdytail - bhi; err1 = tj1 - (ahi * bhi); err2 = err1 - (alo * bhi); err3 = err2 - (ahi * blo); tj0 = (alo * blo) - err3; + _i = (double)(ti0 + tj0); bvirt = (double)(_i - ti0); avirt = _i - bvirt; bround = tj0 - bvirt; around = ti0 - avirt; u[0] = around + bround; _j = (double)(ti1 + _i); bvirt = (double)(_j - ti1); avirt = _j - bvirt; bround = _i - bvirt; around = ti1 - avirt; _0 = around + bround; _i = (double)(_0 + tj1); bvirt = (double)(_i - _0); avirt = _i - bvirt; bround = tj1 - bvirt; around = _0 - avirt; u[1] = around + bround; u3 = (double)(_j + _i); bvirt = (double)(u3 - _j); avirt = u3 - bvirt; bround = _i - bvirt; around = _j - avirt; u[2] = around + bround; + u[3] = u3; + negate = -ady; + ti1 = (double)(bdxtail * negate); c = (double)(splitter * bdxtail); abig = (double)(c - bdxtail); ahi = c - abig; alo = bdxtail - ahi; c = (double)(splitter * negate); abig = (double)(c - negate); bhi = c - abig; blo = negate - bhi; err1 = ti1 - (ahi * bhi); err2 = err1 - (alo * bhi); err3 = err2 - (ahi * blo); ti0 = (alo * blo) - err3; + negate = -adytail; + tj1 = (double)(bdx * negate); c = (double)(splitter * bdx); abig = (double)(c - bdx); ahi = c - abig; alo = bdx - ahi; c = (double)(splitter * negate); abig = (double)(c - negate); bhi = c - abig; blo = negate - bhi; err1 = tj1 - (ahi * bhi); err2 = err1 - (alo * bhi); err3 = err2 - (ahi * blo); tj0 = (alo * blo) - err3; + _i = (double)(ti0 + tj0); bvirt = (double)(_i - ti0); avirt = _i - bvirt; bround = tj0 - bvirt; around = ti0 - avirt; v[0] = around + bround; _j = (double)(ti1 + _i); bvirt = (double)(_j - ti1); avirt = _j - bvirt; bround = _i - bvirt; around = ti1 - avirt; _0 = around + bround; _i = (double)(_0 + tj1); bvirt = (double)(_i - _0); avirt = _i - bvirt; bround = tj1 - bvirt; around = _0 - avirt; v[1] = around + bround; v3 = (double)(_j + _i); bvirt = (double)(v3 - _j); avirt = v3 - bvirt; bround = _i - bvirt; around = _j - avirt; v[2] = around + bround; + v[3] = v3; + abtlen = FastExpansionSumZeroElim(4, u, 4, v, abt); + + ti1 = (double)(adxtail * bdytail); c = (double)(splitter * adxtail); abig = (double)(c - adxtail); ahi = c - abig; alo = adxtail - ahi; c = (double)(splitter * bdytail); abig = (double)(c - bdytail); bhi = c - abig; blo = bdytail - bhi; err1 = ti1 - (ahi * bhi); err2 = err1 - (alo * bhi); err3 = err2 - (ahi * blo); ti0 = (alo * blo) - err3; + tj1 = (double)(bdxtail * adytail); c = (double)(splitter * bdxtail); abig = (double)(c - bdxtail); ahi = c - abig; alo = bdxtail - ahi; c = (double)(splitter * adytail); abig = (double)(c - adytail); bhi = c - abig; blo = adytail - bhi; err1 = tj1 - (ahi * bhi); err2 = err1 - (alo * bhi); err3 = err2 - (ahi * blo); tj0 = (alo * blo) - err3; + _i = (double)(ti0 - tj0); bvirt = (double)(ti0 - _i); avirt = _i + bvirt; bround = bvirt - tj0; around = ti0 - avirt; abtt[0] = around + bround; _j = (double)(ti1 + _i); bvirt = (double)(_j - ti1); avirt = _j - bvirt; bround = _i - bvirt; around = ti1 - avirt; _0 = around + bround; _i = (double)(_0 - tj1); bvirt = (double)(_0 - _i); avirt = _i + bvirt; bround = bvirt - tj1; around = _0 - avirt; abtt[1] = around + bround; abtt3 = (double)(_j + _i); bvirt = (double)(abtt3 - _j); avirt = abtt3 - bvirt; bround = _i - bvirt; around = _j - avirt; abtt[2] = around + bround; + abtt[3] = abtt3; + abttlen = 4; + } + else + { + abt[0] = 0.0; + abtlen = 1; + abtt[0] = 0.0; + abttlen = 1; + } + + if (cdxtail != 0.0) + { + temp16alen = ScaleExpansionZeroElim(cxtablen, cxtab, cdxtail, temp16a); + cxtabtlen = ScaleExpansionZeroElim(abtlen, abt, cdxtail, cxtabt); + temp32alen = ScaleExpansionZeroElim(cxtabtlen, cxtabt, 2.0 * cdx, temp32a); + temp48len = FastExpansionSumZeroElim(temp16alen, temp16a, temp32alen, temp32a, temp48); + finlength = FastExpansionSumZeroElim(finlength, finnow, temp48len, temp48, finother); + finswap = finnow; finnow = finother; finother = finswap; + if (adytail != 0.0) + { + temp8len = ScaleExpansionZeroElim(4, bb, cdxtail, temp8); + temp16alen = ScaleExpansionZeroElim(temp8len, temp8, adytail, temp16a); + finlength = FastExpansionSumZeroElim(finlength, finnow, temp16alen, temp16a, finother); + finswap = finnow; finnow = finother; finother = finswap; + } + if (bdytail != 0.0) + { + temp8len = ScaleExpansionZeroElim(4, aa, -cdxtail, temp8); + temp16alen = ScaleExpansionZeroElim(temp8len, temp8, bdytail, temp16a); + finlength = FastExpansionSumZeroElim(finlength, finnow, temp16alen, temp16a, finother); + finswap = finnow; finnow = finother; finother = finswap; + } + + temp32alen = ScaleExpansionZeroElim(cxtabtlen, cxtabt, cdxtail, temp32a); + cxtabttlen = ScaleExpansionZeroElim(abttlen, abtt, cdxtail, cxtabtt); + temp16alen = ScaleExpansionZeroElim(cxtabttlen, cxtabtt, 2.0 * cdx, temp16a); + temp16blen = ScaleExpansionZeroElim(cxtabttlen, cxtabtt, cdxtail, temp16b); + temp32blen = FastExpansionSumZeroElim(temp16alen, temp16a, temp16blen, temp16b, temp32b); + temp64len = FastExpansionSumZeroElim(temp32alen, temp32a, temp32blen, temp32b, temp64); + finlength = FastExpansionSumZeroElim(finlength, finnow, temp64len, temp64, finother); + finswap = finnow; finnow = finother; finother = finswap; + } + if (cdytail != 0.0) + { + temp16alen = ScaleExpansionZeroElim(cytablen, cytab, cdytail, temp16a); + cytabtlen = ScaleExpansionZeroElim(abtlen, abt, cdytail, cytabt); + temp32alen = ScaleExpansionZeroElim(cytabtlen, cytabt, 2.0 * cdy, temp32a); + temp48len = FastExpansionSumZeroElim(temp16alen, temp16a, temp32alen, temp32a, temp48); + finlength = FastExpansionSumZeroElim(finlength, finnow, temp48len, temp48, finother); + finswap = finnow; finnow = finother; finother = finswap; + + + temp32alen = ScaleExpansionZeroElim(cytabtlen, cytabt, cdytail, temp32a); + cytabttlen = ScaleExpansionZeroElim(abttlen, abtt, cdytail, cytabtt); + temp16alen = ScaleExpansionZeroElim(cytabttlen, cytabtt, 2.0 * cdy, temp16a); + temp16blen = ScaleExpansionZeroElim(cytabttlen, cytabtt, cdytail, temp16b); + temp32blen = FastExpansionSumZeroElim(temp16alen, temp16a, temp16blen, temp16b, temp32b); + temp64len = FastExpansionSumZeroElim(temp32alen, temp32a, temp32blen, temp32b, temp64); + finlength = FastExpansionSumZeroElim(finlength, finnow, temp64len, temp64, finother); + finswap = finnow; finnow = finother; finother = finswap; + } + } + + return finnow[finlength - 1]; + } + + #region Workspace + + // InCircleAdapt workspace: + double[] fin1, fin2, abdet; + + double[] axbc, axxbc, aybc, ayybc, adet; + double[] bxca, bxxca, byca, byyca, bdet; + double[] cxab, cxxab, cyab, cyyab, cdet; + + double[] temp8, temp16a, temp16b, temp16c; + double[] temp32a, temp32b, temp48, temp64; + + private void AllocateWorkspace() + { + fin1 = new double[1152]; + fin2 = new double[1152]; + abdet = new double[64]; + + axbc = new double[8]; + axxbc = new double[16]; + aybc = new double[8]; + ayybc = new double[16]; + adet = new double[32]; + + bxca = new double[8]; + bxxca = new double[16]; + byca = new double[8]; + byyca = new double[16]; + bdet = new double[32]; + + cxab = new double[8]; + cxxab = new double[16]; + cyab = new double[8]; + cyyab = new double[16]; + cdet = new double[32]; + + temp8 = new double[8]; + temp16a = new double[16]; + temp16b = new double[16]; + temp16c = new double[16]; + + temp32a = new double[32]; + temp32b = new double[32]; + temp48 = new double[48]; + temp64 = new double[64]; + } + + private void ClearWorkspace() + { + } + + #endregion + + #endregion + } +} diff --git a/External/Triangle.NET/Triangle/Smoothing/ISmoother.cs b/External/Triangle.NET/Triangle/Smoothing/ISmoother.cs new file mode 100644 index 0000000..621a2c5 --- /dev/null +++ b/External/Triangle.NET/Triangle/Smoothing/ISmoother.cs @@ -0,0 +1,19 @@ +// ----------------------------------------------------------------------- +// +// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/ +// +// ----------------------------------------------------------------------- + +namespace TriangleNet.Smoothing +{ + using TriangleNet.Meshing; + + /// + /// Interface for mesh smoothers. + /// + public interface ISmoother + { + void Smooth(IMesh mesh); + void Smooth(IMesh mesh, int limit); + } +} \ No newline at end of file diff --git a/External/Triangle.NET/Triangle/Smoothing/SimpleSmoother.cs b/External/Triangle.NET/Triangle/Smoothing/SimpleSmoother.cs new file mode 100644 index 0000000..ec5dd76 --- /dev/null +++ b/External/Triangle.NET/Triangle/Smoothing/SimpleSmoother.cs @@ -0,0 +1,171 @@ +// ----------------------------------------------------------------------- +// +// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/ +// +// ----------------------------------------------------------------------- + +namespace TriangleNet.Smoothing +{ + using System.Linq; + using TriangleNet.Geometry; + using TriangleNet.Meshing; + using TriangleNet.Topology.DCEL; + using TriangleNet.Voronoi; + + /// + /// Simple mesh smoother implementation. + /// + /// + /// Vertices wich should not move (e.g. segment vertices) MUST have a + /// boundary mark greater than 0. + /// + public class SimpleSmoother : ISmoother + { + TrianglePool pool; + Configuration config; + + IVoronoiFactory factory; + + ConstraintOptions options; + + /// + /// Initializes a new instance of the class. + /// + public SimpleSmoother() + : this(new VoronoiFactory()) + { + } + + /// + /// Initializes a new instance of the class. + /// + 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 }; + } + + /// + /// Initializes a new instance of the class. + /// + /// Voronoi object factory. + /// Configuration. + 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; + } + } + } + + /// + /// Calculate the centroid of a polygon. + /// + 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; + } + + /// + /// Rebuild the input geometry. + /// + 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()); + + data.Holes.AddRange(mesh.holes); + data.Regions.AddRange(mesh.regions); + + return data; + } + } +} diff --git a/External/Triangle.NET/Triangle/Smoothing/VoronoiFactory.cs b/External/Triangle.NET/Triangle/Smoothing/VoronoiFactory.cs new file mode 100644 index 0000000..8be31aa --- /dev/null +++ b/External/Triangle.NET/Triangle/Smoothing/VoronoiFactory.cs @@ -0,0 +1,201 @@ + +namespace TriangleNet.Smoothing +{ + using System; + using TriangleNet.Topology.DCEL; + using TriangleNet.Voronoi; + + /// + /// Factory which re-uses objects in the smoothing loop to enhance performance. + /// + /// + /// See . + /// + class VoronoiFactory : IVoronoiFactory + { + ObjectPool vertices; + ObjectPool edges; + ObjectPool faces; + + public VoronoiFactory() + { + vertices = new ObjectPool(); + edges = new ObjectPool(); + faces = new ObjectPool(); + } + + 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 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); + } + } + } + } +} diff --git a/External/Triangle.NET/Triangle/Tools/AdjacencyMatrix.cs b/External/Triangle.NET/Triangle/Tools/AdjacencyMatrix.cs new file mode 100644 index 0000000..6cabe33 --- /dev/null +++ b/External/Triangle.NET/Triangle/Tools/AdjacencyMatrix.cs @@ -0,0 +1,285 @@ +// ----------------------------------------------------------------------- +// +// Original Matlab code by John Burkardt, Florida State University +// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/ +// +// ----------------------------------------------------------------------- + +namespace TriangleNet.Tools +{ + using System; + + /// + /// The adjacency matrix of the mesh. + /// + 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; + + /// + /// Gets the number of columns (nodes of the mesh). + /// + public readonly int N; + + /// + /// Gets the column pointers. + /// + public int[] ColumnPointers + { + get { return pcol; } + } + + /// + /// Gets the row indices. + /// + 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(); + } + } + + /// + /// Computes the bandwidth of an adjacency matrix. + /// + /// Bandwidth of the adjacency matrix. + 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 + + /// + /// Counts adjacencies in a triangulation. + /// + /// + /// 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. + /// + 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; + } + + /// + /// Sets adjacencies in a triangulation. + /// + /// + /// 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. + /// + 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 + } +} diff --git a/External/Triangle.NET/Triangle/Tools/CuthillMcKee.cs b/External/Triangle.NET/Triangle/Tools/CuthillMcKee.cs new file mode 100644 index 0000000..722ef49 --- /dev/null +++ b/External/Triangle.NET/Triangle/Tools/CuthillMcKee.cs @@ -0,0 +1,685 @@ +// ----------------------------------------------------------------------- +// +// Original Matlab code by John Burkardt, Florida State University +// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/ +// +// ----------------------------------------------------------------------- + +namespace TriangleNet.Tools +{ + using System; + + /// + /// Applies the Cuthill and McKee renumbering algorithm to reduce the bandwidth of + /// the adjacency matrix associated with the mesh. + /// + public class CuthillMcKee + { + // The adjacency matrix of the mesh. + AdjacencyMatrix matrix; + + /// + /// Gets the permutation vector for the Reverse Cuthill-McKee numbering. + /// + /// The mesh. + /// Permutation vector. + public int[] Renumber(Mesh mesh) + { + // Algorithm needs linear numbering of the nodes. + mesh.Renumber(NodeNumbering.Linear); + + return Renumber(new AdjacencyMatrix(mesh)); + } + + /// + /// Gets the permutation vector for the Reverse Cuthill-McKee numbering. + /// + /// The mesh. + /// Permutation vector. + 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 + + /// + /// Finds the reverse Cuthill-Mckee ordering for a general graph. + /// + /// The RCM ordering. + /// + /// For each connected component in the graph, the routine obtains + /// an ordering by calling RCM. + /// + 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; + } + + /// + /// RCM renumbers a connected component by the reverse Cuthill McKee algorithm. + /// + /// the node that defines the connected component. It is used as the starting + /// point for the RCM ordering. + /// 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. + /// Output, int PERM(NODE_NUM), the RCM ordering. + /// Output, int ICCSZE, the size of the connected component that has been numbered. + /// the number of nodes. + /// + /// 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. + /// + 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; + } + + /// + /// Finds a pseudo-peripheral node. + /// + /// 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. + /// MASK[NODE_NUM], specifies a section subgraph. Nodes for which MASK + /// is zero are ignored by FNROOT. + /// Output, int LEVEL_NUM, is the number of levels in the level + /// structure rooted at the node ROOT. + /// Output, int LEVEL_ROW(NODE_NUM+1), the level structure array pair + /// containing the level structure found. + /// Output, int LEVEL(NODE_NUM), the level structure array pair + /// containing the level structure found. + /// the number of nodes. + /// + /// 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. + /// + 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; + } + + /// + /// Generates the connected level structure rooted at a given node. + /// + /// the node at which the level structure is to be rooted. + /// 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. + /// 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. + /// Output, int LEVEL_ROW[NODE_NUM+1], the rooted level structure. + /// Output, int LEVEL[NODE_NUM], the rooted level structure. + /// the number of nodes. + /// + /// 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. + /// + 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; + } + + /// + /// Computes the degrees of the nodes in the connected component. + /// + /// the node that defines the connected component. + /// MASK[NODE_NUM], is nonzero for those nodes which are to be considered. + /// Output, int DEG[NODE_NUM], contains, for each node in the connected component, its degree. + /// Output, int ICCSIZE, the number of nodes in the connected component. + /// Output, int LS[NODE_NUM], stores in entries 1 through ICCSIZE the nodes in the + /// connected component, starting with ROOT, and proceeding by levels. + /// the number of nodes. + /// + /// 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. + /// + 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 + + /// + /// Computes the bandwidth of a permuted adjacency matrix. + /// + /// The permutation. + /// The inverse permutation. + /// Bandwidth of the permuted adjacency matrix. + /// + /// The matrix is defined by the adjacency information and a permutation. + /// The routine also computes the bandwidth and the size of the envelope. + /// + 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; + } + + /// + /// Produces the inverse of a given permutation. + /// + /// Number of items permuted. + /// PERM[N], a permutation. + /// The inverse permutation. + 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; + } + + /// + /// Reverses the elements of an integer vector. + /// + /// number of entries in the array. + /// the array to be reversed. + /// + /// Input: + /// N = 5, + /// A = ( 11, 12, 13, 14, 15 ). + /// + /// Output: + /// A = ( 15, 14, 13, 12, 11 ). + /// + 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 + } +} diff --git a/External/Triangle.NET/Triangle/Tools/Interpolation.cs b/External/Triangle.NET/Triangle/Tools/Interpolation.cs new file mode 100644 index 0000000..f315b03 --- /dev/null +++ b/External/Triangle.NET/Triangle/Tools/Interpolation.cs @@ -0,0 +1,105 @@ + +namespace TriangleNet.Tools +{ + using TriangleNet.Geometry; + + public static class Interpolation + { +#if USE_ATTRIBS + /// + /// Linear interpolation of vertex attributes. + /// + /// The interpolation vertex. + /// The triangle containing the vertex. + /// The number of vertex attributes. + /// + /// The vertex is expected to lie inside the triangle. + /// + 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 + /// + /// Linear interpolation of a scalar value. + /// + /// The interpolation point. + /// The triangle containing the point. + /// + /// The point is expected to lie inside the triangle. + /// + 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 + } +} diff --git a/External/Triangle.NET/Triangle/Tools/IntersectionHelper.cs b/External/Triangle.NET/Triangle/Tools/IntersectionHelper.cs new file mode 100644 index 0000000..fa70936 --- /dev/null +++ b/External/Triangle.NET/Triangle/Tools/IntersectionHelper.cs @@ -0,0 +1,225 @@ +// ----------------------------------------------------------------------- +// +// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/ +// +// ----------------------------------------------------------------------- + +namespace TriangleNet.Tools +{ + using TriangleNet.Geometry; + + public static class IntersectionHelper + { + /// + /// Compute intersection of two segments. + /// + /// Segment 1 start point. + /// Segment 1 end point. + /// Segment 2 start point. + /// Segment 2 end point. + /// The intersection point. + /// + /// 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. + /// + 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; + } + + /// + /// Intersect segment with a bounding box. + /// + /// The clip rectangle. + /// Segment endpoint. + /// Segment endpoint. + /// The new location of p0. + /// The new location of p1. + /// Returns true, if segment is clipped. + /// + /// Based on Liang-Barsky function by Daniel White: + /// http://www.skytopia.com/project/articles/compsci/clipping.html + /// + 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 + } + + /// + /// Intersect a ray with a bounding box. + /// + /// The clip rectangle. + /// The ray startpoint (inside the box). + /// Any point in ray direction (NOT the direction vector). + /// The intersection point. + /// Returns false, if startpoint is outside the box. + 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); + } + + /// + /// Intersect a ray with a bounding box. + /// + /// The clip rectangle. + /// The ray startpoint (inside the box). + /// X direction. + /// Y direction. + /// Returns false, if startpoint is outside the box. + 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; + } + + /// + /// Intersect a ray with a bounding box. + /// + /// The clip rectangle. + /// The ray startpoint (inside the box). + /// X direction. + /// Y direction. + /// The intersection point. + /// Returns false, if startpoint is outside the box. + 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; + } + } +} diff --git a/External/Triangle.NET/Triangle/Tools/PolygonValidator.cs b/External/Triangle.NET/Triangle/Tools/PolygonValidator.cs new file mode 100644 index 0000000..8ec9f59 --- /dev/null +++ b/External/Triangle.NET/Triangle/Tools/PolygonValidator.cs @@ -0,0 +1,245 @@ +// ----------------------------------------------------------------------- +// +// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/ +// +// ----------------------------------------------------------------------- + +namespace TriangleNet.Tools +{ + using System; + using System.Collections.Generic; + using TriangleNet.Geometry; + + public static class PolygonValidator + { + /// + /// Test the polygon for consistency. + /// + 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; + } + + /// + /// Test the polygon for duplicate vertices. + /// + 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; + } + + /// + /// Test the polygon for 360 degree angles. + /// + /// The polygon. + /// The angle threshold. + 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 . + 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(); + + // 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; + } + } +} diff --git a/External/Triangle.NET/Triangle/Tools/QualityMeasure.cs b/External/Triangle.NET/Triangle/Tools/QualityMeasure.cs new file mode 100644 index 0000000..fc7bdc6 --- /dev/null +++ b/External/Triangle.NET/Triangle/Tools/QualityMeasure.cs @@ -0,0 +1,541 @@ +// ----------------------------------------------------------------------- +// +// Original Matlab code by John Burkardt, Florida State University +// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/ +// +// ----------------------------------------------------------------------- + +namespace TriangleNet.Tools +{ + using System; + using TriangleNet.Geometry; + + /// + /// Provides mesh quality information. + /// + /// + /// 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)) + /// + 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 + + /// + /// Minimum triangle area. + /// + public double AreaMinimum + { + get { return areaMeasure.area_min; } + } + + /// + /// Maximum triangle area. + /// + public double AreaMaximum + { + get { return areaMeasure.area_max; } + } + + /// + /// Ratio of maximum and minimum triangle area. + /// + public double AreaRatio + { + get { return areaMeasure.area_max / areaMeasure.area_min; } + } + + /// + /// Smallest angle. + /// + public double AlphaMinimum + { + get { return alphaMeasure.alpha_min; } + } + + /// + /// Maximum smallest angle. + /// + public double AlphaMaximum + { + get { return alphaMeasure.alpha_max; } + } + + /// + /// Average angle. + /// + public double AlphaAverage + { + get { return alphaMeasure.alpha_ave; } + } + + /// + /// Average angle weighted by area. + /// + public double AlphaArea + { + get { return alphaMeasure.alpha_area; } + } + + /// + /// Smallest aspect ratio. + /// + public double Q_Minimum + { + get { return qMeasure.q_min; } + } + + /// + /// Largest aspect ratio. + /// + public double Q_Maximum + { + get { return qMeasure.q_max; } + } + + /// + /// Average aspect ratio. + /// + public double Q_Average + { + get { return qMeasure.q_ave; } + } + + /// + /// Average aspect ratio weighted by area. + /// + 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); + } + + /// + /// Determines the bandwidth of the coefficient matrix. + /// + /// Bandwidth of the coefficient matrix. + /// + /// 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. + /// + 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; + + /// + /// Reset all values. + /// + public void Reset() + { + area_min = double.MaxValue; + area_max = -double.MaxValue; + area_total = 0; + area_zero = 0; + } + + /// + /// Compute the area of given triangle. + /// + /// Triangle corner a. + /// Triangle corner b. + /// Triangle corner c. + /// Triangle area. + 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; + } + } + + /// + /// The alpha measure determines the triangulated pointset quality. + /// + /// + /// 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. + /// + 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; + + /// + /// Reset all values. + /// + 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); + } + } + + /// + /// Compute q value of given triangle. + /// + /// Side length ab. + /// Side length bc. + /// Side length ca. + /// Triangle area. + /// + 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; + } + + /// + /// Normalize values. + /// + 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; + } + } + } + + /// + /// The Q measure determines the triangulated pointset quality. + /// + /// + /// 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. + /// + 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; + + /// + /// Reset all values. + /// + public void Reset() + { + q_min = double.MaxValue; + q_max = -double.MaxValue; + q_ave = 0; + q_area = 0; + } + + /// + /// Compute q value of given triangle. + /// + /// Side length ab. + /// Side length bc. + /// Side length ca. + /// Triangle area. + /// + 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; + } + + /// + /// Normalize values. + /// + 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; + } + } + } + } +} diff --git a/External/Triangle.NET/Triangle/Tools/Statistic.cs b/External/Triangle.NET/Triangle/Tools/Statistic.cs new file mode 100644 index 0000000..a530f8c --- /dev/null +++ b/External/Triangle.NET/Triangle/Tools/Statistic.cs @@ -0,0 +1,528 @@ +// ----------------------------------------------------------------------- +// +// 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/ +// +// ----------------------------------------------------------------------- + +namespace TriangleNet.Tools +{ + using System; + using TriangleNet.Topology; + using TriangleNet.Geometry; + + /// + /// Gather mesh statistics. + /// + public class Statistic + { + #region Static members + + /// + /// Number of incircle tests performed. + /// + public static long InCircleCount = 0; + public static long InCircleAdaptCount = 0; + + /// + /// Number of counterclockwise tests performed. + /// + public static long CounterClockwiseCount = 0; + public static long CounterClockwiseAdaptCount = 0; + + /// + /// Number of 3D orientation tests performed. + /// + public static long Orient3dCount = 0; + + /// + /// Number of right-of-hyperbola tests performed. + /// + public static long HyperbolaCount = 0; + + /// + /// // Number of circumcenter calculations performed. + /// + public static long CircumcenterCount = 0; + + /// + /// Number of circle top calculations performed. + /// + public static long CircleTopCount = 0; + + /// + /// Number of vertex relocations. + /// + public static long RelocationCount = 0; + + #endregion + + #region Properties + + double minEdge = 0; + /// + /// Gets the shortest edge. + /// + public double ShortestEdge { get { return minEdge; } } + + double maxEdge = 0; + /// + /// Gets the longest edge. + /// + public double LongestEdge { get { return maxEdge; } } + + // + double minAspect = 0; + /// + /// Gets the shortest altitude. + /// + public double ShortestAltitude { get { return minAspect; } } + + double maxAspect = 0; + /// + /// Gets the largest aspect ratio. + /// + public double LargestAspectRatio { get { return maxAspect; } } + + double minArea = 0; + /// + /// Gets the smallest area. + /// + public double SmallestArea { get { return minArea; } } + + double maxArea = 0; + /// + /// Gets the largest area. + /// + public double LargestArea { get { return maxArea; } } + + double minAngle = 0; + /// + /// Gets the smallest angle. + /// + public double SmallestAngle { get { return minAngle; } } + + double maxAngle = 0; + /// + /// Gets the largest angle. + /// + public double LargestAngle { get { return maxAngle; } } + + int[] angleTable; + /// + /// Gets the angle histogram. + /// + public int[] AngleHistogram { get { return angleTable; } } + + int[] minAngles; + /// + /// Gets the min angles histogram. + /// + public int[] MinAngleHistogram { get { return minAngles; } } + + int[] maxAngles; + /// + /// Gets the max angles histogram. + /// + 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 }; + + /// + /// Update statistics about the quality of the mesh. + /// + /// + 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)); + } + } + } + + /// + /// Compute angle information for given triangle. + /// + /// The triangle to check. + /// Array of doubles (length 6). + /// + /// 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]))). + /// + 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; + } + } +} diff --git a/External/Triangle.NET/Triangle/Tools/TriangleQuadTree.cs b/External/Triangle.NET/Triangle/Tools/TriangleQuadTree.cs new file mode 100644 index 0000000..0b7203a --- /dev/null +++ b/External/Triangle.NET/Triangle/Tools/TriangleQuadTree.cs @@ -0,0 +1,426 @@ +// ----------------------------------------------------------------------- +// +// Original code by Frank Dockhorn, [not available anymore: http://sourceforge.net/projects/quadtreesim/] +// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/ +// +// ----------------------------------------------------------------------- + +namespace TriangleNet.Tools +{ + using System.Collections.Generic; + using System.Linq; + using TriangleNet.Geometry; + + /// + /// A Quadtree implementation optimized for triangles. + /// + public class TriangleQuadTree + { + QuadNode root; + + internal ITriangle[] triangles; + + internal int sizeBound; + internal int maxDepth; + + /// + /// Initializes a new instance of the class. + /// + /// Mesh containing triangles. + /// The maximum depth of the tree. + /// The maximum number of triangles contained in a leaf. + /// + /// 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. + /// + 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; + } + + /// + /// Test, if a given point lies inside a triangle. + /// + /// Point to locate. + /// Corner point of triangle. + /// Corner point of triangle. + /// Corner point of triangle. + /// True, if point is inside or on the edge of this triangle. + 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; + } + + /// + /// A node of the quadtree. + /// + 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 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(); + + 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 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]; + } + } + } + } +} diff --git a/External/Triangle.NET/Triangle/Tools/VertexSorter.cs b/External/Triangle.NET/Triangle/Tools/VertexSorter.cs new file mode 100644 index 0000000..72e662e --- /dev/null +++ b/External/Triangle.NET/Triangle/Tools/VertexSorter.cs @@ -0,0 +1,371 @@ +// ----------------------------------------------------------------------- +// +// 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/ +// +// ----------------------------------------------------------------------- + +namespace TriangleNet.Tools +{ + using System; + using TriangleNet.Geometry; + + /// + /// Sort an array of points using quicksort. + /// + 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); + } + + /// + /// Sorts the given vertex array by x-coordinate. + /// + /// The vertex array. + /// Random seed used for pivoting. + public static void Sort(Vertex[] array, int seed = RANDOM_SEED) + { + var qs = new VertexSorter(array, seed); + + qs.QuickSort(0, array.Length - 1); + } + + /// + /// Impose alternating cuts on given vertex array. + /// + /// The vertex array. + /// The number of vertices to sort. + /// Random seed used for pivoting. + 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 + + /// + /// Sort an array of vertices by x-coordinate, using the y-coordinate as a secondary key. + /// + /// + /// + /// + /// Uses quicksort. Randomized O(n log n) time. No, I did not make any of + /// the usual quicksort mistakes. + /// + 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 + + /// + /// Sorts the vertices as appropriate for the divide-and-conquer algorithm with + /// alternating cuts. + /// + /// + /// + /// + /// + /// 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. + /// + 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); + } + } + + /// + /// An order statistic algorithm, almost. Shuffles an array of vertices so that the + /// first 'median' vertices occur lexicographically before the remaining vertices. + /// + /// + /// + /// + /// + /// Uses the x-coordinate as the primary key. Very similar to the QuickSort() + /// procedure, but runs in randomized linear time. + /// + 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); + } + } + + /// + /// An order statistic algorithm, almost. Shuffles an array of vertices so that + /// the first 'median' vertices occur lexicographically before the remaining vertices. + /// + /// + /// + /// + /// + /// Uses the y-coordinate as the primary key. Very similar to the QuickSort() + /// procedure, but runs in randomized linear time. + /// + 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 + } +} diff --git a/External/Triangle.NET/Triangle/Topology/DCEL/DcelMesh.cs b/External/Triangle.NET/Triangle/Topology/DCEL/DcelMesh.cs new file mode 100644 index 0000000..cf301db --- /dev/null +++ b/External/Triangle.NET/Triangle/Topology/DCEL/DcelMesh.cs @@ -0,0 +1,269 @@ +// ----------------------------------------------------------------------- +// +// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/ +// +// ----------------------------------------------------------------------- + +namespace TriangleNet.Topology.DCEL +{ + using System.Collections.Generic; + using TriangleNet.Geometry; + + public class DcelMesh + { + protected List vertices; + protected List edges; + protected List faces; + + /// + /// Initializes a new instance of the class. + /// + public DcelMesh() + : this(true) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// If false, lists will not be initialized. + protected DcelMesh(bool initialize) + { + if (initialize) + { + vertices = new List(); + edges = new List(); + faces = new List(); + } + } + + /// + /// Gets the vertices of the Voronoi diagram. + /// + public List Vertices + { + get { return vertices; } + } + + /// + /// Gets the list of half-edges specify the Voronoi diagram topology. + /// + public List HalfEdges + { + get { return edges; } + } + + /// + /// Gets the faces of the Voronoi diagram. + /// + public List Faces + { + get { return faces; } + } + + /// + /// Gets the collection of edges of the Voronoi diagram. + /// + public IEnumerable Edges + { + get { return EnumerateEdges(); } + } + + /// + /// Check if the DCEL is consistend. + /// + /// If true, faces are assumed to be closed (i.e. all edges must have + /// a valid next pointer). + /// Maximum edge count of faces (default = 0 means skip check). + /// + 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; + } + + /// + /// Search for half-edge without twin and add a twin. Connect twins to form connected + /// boundary contours. + /// + /// + /// This method assumes that all faces are closed (i.e. no edge.next pointers are null). + /// + public void ResolveBoundaryEdges() + { + // Maps vertices to leaving boundary edge. + var map = new Dictionary(); + + // 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); + } + } + + /// + /// Enumerates all edges of the DCEL. + /// + /// + /// This method assumes that each half-edge has a twin (i.e. NOT null). + /// + protected virtual IEnumerable EnumerateEdges() + { + var edges = new List(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; + } + } +} diff --git a/External/Triangle.NET/Triangle/Topology/DCEL/Face.cs b/External/Triangle.NET/Triangle/Topology/DCEL/Face.cs new file mode 100644 index 0000000..7073920 --- /dev/null +++ b/External/Triangle.NET/Triangle/Topology/DCEL/Face.cs @@ -0,0 +1,112 @@ +// ----------------------------------------------------------------------- +// +// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/ +// +// ----------------------------------------------------------------------- + +namespace TriangleNet.Topology.DCEL +{ + using System.Collections.Generic; + using TriangleNet.Geometry; + + /// + /// A face of DCEL mesh. + /// + 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; + + /// + /// Gets or sets the face id. + /// + public int ID + { + get { return id; } + set { id = value; } + } + + /// + /// Gets or sets a half-edge connected to the face. + /// + public HalfEdge Edge + { + get { return edge; } + set { edge = value; } + } + + /// + /// Gets or sets a value, indicating if the face is bounded (for Voronoi diagram). + /// + public bool Bounded + { + get { return bounded; } + set { bounded = value; } + } + + /// + /// Initializes a new instance of the class. + /// + /// The generator of this face (for Voronoi diagram) + public Face(Point generator) + : this(generator, null) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The generator of this face (for Voronoi diagram) + /// The half-edge connected to this face. + public Face(Point generator, HalfEdge edge) + { + this.generator = generator; + this.edge = edge; + this.bounded = true; + + if (generator != null) + { + this.id = generator.ID; + } + } + + /// + /// Enumerates all half-edges of the face boundary. + /// + /// + public IEnumerable 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); + } + } +} diff --git a/External/Triangle.NET/Triangle/Topology/DCEL/HalfEdge.cs b/External/Triangle.NET/Triangle/Topology/DCEL/HalfEdge.cs new file mode 100644 index 0000000..bff3dcc --- /dev/null +++ b/External/Triangle.NET/Triangle/Topology/DCEL/HalfEdge.cs @@ -0,0 +1,101 @@ +// ----------------------------------------------------------------------- +// +// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/ +// +// ----------------------------------------------------------------------- + +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; + + /// + /// Gets or sets the half-edge id. + /// + public int ID + { + get { return id; } + set { id = value; } + } + + public int Boundary + { + get { return mark; } + set { mark = value; } + } + + /// + /// Gets or sets the origin of the half-edge. + /// + public Vertex Origin + { + get { return origin; } + set { origin = value; } + } + + /// + /// Gets or sets the face connected to the half-edge. + /// + public Face Face + { + get { return face; } + set { face = value; } + } + + /// + /// Gets or sets the twin of the half-edge. + /// + public HalfEdge Twin + { + get { return twin; } + set { twin = value; } + } + + /// + /// Gets or sets the next pointer of the half-edge. + /// + public HalfEdge Next + { + get { return next; } + set { next = value; } + } + + /// + /// Initializes a new instance of the class. + /// + /// The origin of this half-edge. + public HalfEdge(Vertex origin) + { + this.origin = origin; + } + + /// + /// Initializes a new instance of the class. + /// + /// The origin of this half-edge. + /// The face connected to this half-edge. + 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); + } + } +} diff --git a/External/Triangle.NET/Triangle/Topology/DCEL/Vertex.cs b/External/Triangle.NET/Triangle/Topology/DCEL/Vertex.cs new file mode 100644 index 0000000..2929e09 --- /dev/null +++ b/External/Triangle.NET/Triangle/Topology/DCEL/Vertex.cs @@ -0,0 +1,68 @@ +// ----------------------------------------------------------------------- +// +// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/ +// +// ----------------------------------------------------------------------- + +namespace TriangleNet.Topology.DCEL +{ + using System.Collections.Generic; + + public class Vertex : TriangleNet.Geometry.Point + { + internal HalfEdge leaving; + + /// + /// Gets or sets a half-edge leaving the vertex. + /// + public HalfEdge Leaving + { + get { return leaving; } + set { leaving = value; } + } + + /// + /// Initializes a new instance of the class. + /// + /// The x coordinate. + /// The y coordinate. + public Vertex(double x, double y) + : base(x, y) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The x coordinate. + /// The y coordinate. + /// A half-edge leaving this vertex. + public Vertex(double x, double y, HalfEdge leaving) + : base(x, y) + { + this.leaving = leaving; + } + + /// + /// Enumerates all half-edges leaving this vertex. + /// + /// + public IEnumerable 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); + } + } +} diff --git a/External/Triangle.NET/Triangle/Topology/Osub.cs b/External/Triangle.NET/Triangle/Topology/Osub.cs new file mode 100644 index 0000000..f60281b --- /dev/null +++ b/External/Triangle.NET/Triangle/Topology/Osub.cs @@ -0,0 +1,256 @@ +// ----------------------------------------------------------------------- +// +// 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/ +// +// ----------------------------------------------------------------------- + +namespace TriangleNet.Topology +{ + using System; + using TriangleNet.Geometry; + + /// + /// An oriented subsegment. + /// + /// + /// 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. + /// + 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 + + /// + /// Reverse the orientation of a subsegment. [sym(ab) -> ba] + /// + public void Sym(ref Osub os) + { + os.seg = seg; + os.orient = 1 - orient; + } + + /// + /// Reverse the orientation of a subsegment. [sym(ab) -> ba] + /// + public void Sym() + { + orient = 1 - orient; + } + + /// + /// Find adjoining subsegment with the same origin. [pivot(ab) -> a*] + /// + /// spivot() finds the other subsegment (from the same segment) + /// that shares the same origin. + /// + public void Pivot(ref Osub os) + { + os = seg.subsegs[orient]; + } + + /// + /// Finds a triangle abutting a subsegment. + /// + internal void Pivot(ref Otri ot) + { + ot = seg.triangles[orient]; + } + + /// + /// Find next subsegment in sequence. [next(ab) -> b*] + /// + public void Next(ref Osub ot) + { + ot = seg.subsegs[1 - orient]; + } + + /// + /// Find next subsegment in sequence. [next(ab) -> b*] + /// + public void Next() + { + this = seg.subsegs[1 - orient]; + } + + /// + /// Get the origin of a subsegment + /// + public Vertex Org() + { + return seg.vertices[orient]; + } + + /// + /// Get the destination of a subsegment + /// + public Vertex Dest() + { + return seg.vertices[1 - orient]; + } + + + #endregion + + #region Osub primitives (internal) + + /// + /// Set the origin or destination of a subsegment. + /// + internal void SetOrg(Vertex vertex) + { + seg.vertices[orient] = vertex; + } + + /// + /// Set destination of a subsegment. + /// + internal void SetDest(Vertex vertex) + { + seg.vertices[1 - orient] = vertex; + } + + /// + /// Get the origin of the segment that includes the subsegment. + /// + internal Vertex SegOrg() + { + return seg.vertices[2 + orient]; + } + + /// + /// Get the destination of the segment that includes the subsegment. + /// + internal Vertex SegDest() + { + return seg.vertices[3 - orient]; + } + + /// + /// Set the origin of the segment that includes the subsegment. + /// + internal void SetSegOrg(Vertex vertex) + { + seg.vertices[2 + orient] = vertex; + } + + /// + /// Set the destination of the segment that includes the subsegment. + /// + internal void SetSegDest(Vertex vertex) + { + seg.vertices[3 - orient] = vertex; + } + + /* Unused primitives. + + /// + /// Find adjoining subsegment with the same origin. [pivot(ab) -> a*] + /// + public void PivotSelf() + { + this = seg.subsegs[orient]; + } + + /// + /// Read a boundary marker. + /// + /// Boundary markers are used to hold user-defined tags for + /// setting boundary conditions in finite element solvers. + public int Mark() + { + return seg.boundary; + } + + /// + /// Set a boundary marker. + /// + public void SetMark(int value) + { + seg.boundary = value; + } + + /// + /// Copy a subsegment. + /// + public void Copy(ref Osub o2) + { + o2.seg = seg; + o2.orient = orient; + } + + //*/ + + /// + /// Bond two subsegments together. [bond(abc, ba)] + /// + internal void Bond(ref Osub os) + { + seg.subsegs[orient] = os; + os.seg.subsegs[os.orient] = this; + } + + /// + /// Dissolve a subsegment bond (from one side). + /// + /// Note that the other subsegment will still think it's + /// connected to this subsegment. + internal void Dissolve(SubSegment dummy) + { + seg.subsegs[orient].seg = dummy; + } + + /// + /// Test for equality of subsegments. + /// + internal bool Equal(Osub os) + { + return ((seg == os.seg) && (orient == os.orient)); + } + + /// + /// Dissolve a bond (from the subsegment side). + /// + internal void TriDissolve(Triangle dummy) + { + seg.triangles[orient].tri = dummy; + } + + /// + /// Check a subsegment's deallocation. + /// + internal static bool IsDead(SubSegment sub) + { + return sub.subsegs[0].seg == null; + } + + /// + /// Set a subsegment's deallocation. + /// + internal static void Kill(SubSegment sub) + { + sub.subsegs[0].seg = null; + sub.subsegs[1].seg = null; + } + + #endregion + } +} diff --git a/External/Triangle.NET/Triangle/Topology/Otri.cs b/External/Triangle.NET/Triangle/Topology/Otri.cs new file mode 100644 index 0000000..ba9efee --- /dev/null +++ b/External/Triangle.NET/Triangle/Topology/Otri.cs @@ -0,0 +1,481 @@ +// ----------------------------------------------------------------------- +// +// 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/ +// +// ----------------------------------------------------------------------- + +namespace TriangleNet.Topology +{ + using System; + using TriangleNet.Geometry; + + /// + /// An oriented triangle. + /// + /// + /// 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. + /// + 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.) + + /// + /// Find the abutting triangle; same edge. [sym(abc) -> ba*] + /// + /// Note that the edge direction is necessarily reversed, because the handle specified + /// by an oriented triangle is directed counterclockwise around the triangle. + /// + public void Sym(ref Otri ot) + { + ot.tri = tri.neighbors[orient].tri; + ot.orient = tri.neighbors[orient].orient; + } + + /// + /// Find the abutting triangle; same edge. [sym(abc) -> ba*] + /// + public void Sym() + { + int tmp = orient; + orient = tri.neighbors[tmp].orient; + tri = tri.neighbors[tmp].tri; + } + + /// + /// Find the next edge (counterclockwise) of a triangle. [lnext(abc) -> bca] + /// + public void Lnext(ref Otri ot) + { + ot.tri = tri; + ot.orient = plus1Mod3[orient]; + } + + /// + /// Find the next edge (counterclockwise) of a triangle. [lnext(abc) -> bca] + /// + public void Lnext() + { + orient = plus1Mod3[orient]; + } + + /// + /// Find the previous edge (clockwise) of a triangle. [lprev(abc) -> cab] + /// + public void Lprev(ref Otri ot) + { + ot.tri = tri; + ot.orient = minus1Mod3[orient]; + } + + /// + /// Find the previous edge (clockwise) of a triangle. [lprev(abc) -> cab] + /// + public void Lprev() + { + orient = minus1Mod3[orient]; + } + + /// + /// Find the next edge counterclockwise with the same origin. [onext(abc) -> ac*] + /// + 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; + } + + /// + /// Find the next edge counterclockwise with the same origin. [onext(abc) -> ac*] + /// + public void Onext() + { + //LprevSelf(); + orient = minus1Mod3[orient]; + + //SymSelf(); + int tmp = orient; + orient = tri.neighbors[tmp].orient; + tri = tri.neighbors[tmp].tri; + } + + /// + /// Find the next edge clockwise with the same origin. [oprev(abc) -> a*b] + /// + 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]; + } + + /// + /// Find the next edge clockwise with the same origin. [oprev(abc) -> a*b] + /// + public void Oprev() + { + //SymSelf(); + int tmp = orient; + orient = tri.neighbors[tmp].orient; + tri = tri.neighbors[tmp].tri; + + //LnextSelf(); + orient = plus1Mod3[orient]; + } + + /// + /// Find the next edge counterclockwise with the same destination. [dnext(abc) -> *ba] + /// + 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]; + } + + /// + /// Find the next edge counterclockwise with the same destination. [dnext(abc) -> *ba] + /// + public void Dnext() + { + //SymSelf(); + int tmp = orient; + orient = tri.neighbors[tmp].orient; + tri = tri.neighbors[tmp].tri; + + //LprevSelf(); + orient = minus1Mod3[orient]; + } + + /// + /// Find the next edge clockwise with the same destination. [dprev(abc) -> cb*] + /// + 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; + } + + /// + /// Find the next edge clockwise with the same destination. [dprev(abc) -> cb*] + /// + public void Dprev() + { + //LnextSelf(); + orient = plus1Mod3[orient]; + + //SymSelf(); + int tmp = orient; + orient = tri.neighbors[tmp].orient; + tri = tri.neighbors[tmp].tri; + } + + /// + /// Find the next edge (counterclockwise) of the adjacent triangle. [rnext(abc) -> *a*] + /// + 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; + } + + /// + /// Find the next edge (counterclockwise) of the adjacent triangle. [rnext(abc) -> *a*] + /// + 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; + } + + /// + /// Find the previous edge (clockwise) of the adjacent triangle. [rprev(abc) -> b**] + /// + 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; + } + + /// + /// Find the previous edge (clockwise) of the adjacent triangle. [rprev(abc) -> b**] + /// + 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; + } + + /// + /// Origin [org(abc) -> a] + /// + public Vertex Org() + { + return tri.vertices[plus1Mod3[orient]]; + } + + /// + /// Destination [dest(abc) -> b] + /// + public Vertex Dest() + { + return tri.vertices[minus1Mod3[orient]]; + } + + /// + /// Apex [apex(abc) -> c] + /// + public Vertex Apex() + { + return tri.vertices[orient]; + } + + /// + /// Copy an oriented triangle. + /// + public void Copy(ref Otri ot) + { + ot.tri = tri; + ot.orient = orient; + } + + /// + /// Test for equality of oriented triangles. + /// + public bool Equals(Otri ot) + { + return ((tri == ot.tri) && (orient == ot.orient)); + } + + #endregion + + #region Otri primitives (internal) + + /// + /// Set Origin + /// + internal void SetOrg(Vertex v) + { + tri.vertices[plus1Mod3[orient]] = v; + } + + /// + /// Set Destination + /// + internal void SetDest(Vertex v) + { + tri.vertices[minus1Mod3[orient]] = v; + } + + /// + /// Set Apex + /// + internal void SetApex(Vertex v) + { + tri.vertices[orient] = v; + } + + /// + /// Bond two triangles together at the resepective handles. [bond(abc, bad)] + /// + 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; + } + + /// + /// Dissolve a bond (from one side). + /// + /// 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. + /// + internal void Dissolve(Triangle dummy) + { + tri.neighbors[orient].tri = dummy; + tri.neighbors[orient].orient = 0; + } + + /// + /// Infect a triangle with the virus. + /// + internal void Infect() + { + tri.infected = true; + } + + /// + /// Cure a triangle from the virus. + /// + internal void Uninfect() + { + tri.infected = false; + } + + /// + /// Test a triangle for viral infection. + /// + internal bool IsInfected() + { + return tri.infected; + } + + /// + /// Finds a subsegment abutting a triangle. + /// + internal void Pivot(ref Osub os) + { + os = tri.subsegs[orient]; + } + + /// + /// Bond a triangle to a subsegment. + /// + internal void SegBond(ref Osub os) + { + tri.subsegs[orient] = os; + os.seg.triangles[os.orient] = this; + } + + /// + /// Dissolve a bond (from the triangle side). + /// + internal void SegDissolve(SubSegment dummy) + { + tri.subsegs[orient].seg = dummy; + } + + /// + /// Check a triangle's deallocation. + /// + internal static bool IsDead(Triangle tria) + { + return tria.neighbors[0].tri == null; + } + + /// + /// Set a triangle's deallocation. + /// + internal static void Kill(Triangle tri) + { + tri.neighbors[0].tri = null; + tri.neighbors[2].tri = null; + } + + #endregion + } +} diff --git a/External/Triangle.NET/Triangle/Topology/SubSegment.cs b/External/Triangle.NET/Triangle/Topology/SubSegment.cs new file mode 100644 index 0000000..b757644 --- /dev/null +++ b/External/Triangle.NET/Triangle/Topology/SubSegment.cs @@ -0,0 +1,96 @@ +// ----------------------------------------------------------------------- +// +// 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/ +// +// ----------------------------------------------------------------------- + +namespace TriangleNet.Topology +{ + using System; + using TriangleNet.Geometry; + + /// + /// The subsegment data structure. + /// + 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 + + /// + /// Gets the first endpoints vertex id. + /// + public int P0 + { + get { return this.vertices[0].id; } + } + + /// + /// Gets the seconds endpoints vertex id. + /// + public int P1 + { + get { return this.vertices[1].id; } + } + + /// + /// Gets the segment boundary mark. + /// + public int Label + { + get { return this.boundary; } + } + + #endregion + + /// + /// Gets the segments endpoint. + /// + public Vertex GetVertex(int index) + { + return this.vertices[index]; // TODO: Check range? + } + + /// + /// Gets an adjoining triangle. + /// + 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); + } + } +} diff --git a/External/Triangle.NET/Triangle/Topology/Triangle.cs b/External/Triangle.NET/Triangle/Topology/Triangle.cs new file mode 100644 index 0000000..d31dff6 --- /dev/null +++ b/External/Triangle.NET/Triangle/Topology/Triangle.cs @@ -0,0 +1,128 @@ +// ----------------------------------------------------------------------- +// +// 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/ +// +// ----------------------------------------------------------------------- + +namespace TriangleNet.Topology +{ + using System; + using TriangleNet.Geometry; + + /// + /// The triangle data structure. + /// + 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; + + /// + /// Initializes a new instance of the class. + /// + 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 + + /// + /// Gets or sets the triangle id. + /// + public int ID + { + get { return this.id; } + set { this.id = value; } + } + + /// + /// Region ID the triangle belongs to. + /// + public int Label + { + get { return this.label; } + set { this.label = value; } + } + + /// + /// Gets the triangle area constraint. + /// + public double Area + { + get { return this.area; } + set { this.area = value; } + } + + /// + /// Gets the specified corners vertex. + /// + public Vertex GetVertex(int index) + { + return this.vertices[index]; // TODO: Check range? + } + + public int GetVertexID(int index) + { + return this.vertices[index].id; + } + + /// + /// Gets a triangles' neighbor. + /// + /// The neighbor index (0, 1 or 2). + /// The neigbbor opposite of vertex with given index. + public ITriangle GetNeighbor(int index) + { + return neighbors[index].tri.hash == Mesh.DUMMY ? null : neighbors[index].tri; + } + + /// + public int GetNeighborID(int index) + { + return neighbors[index].tri.hash == Mesh.DUMMY ? -1 : neighbors[index].tri.id; + } + + /// + /// Gets a triangles segment. + /// + /// The vertex index (0, 1 or 2). + /// The segment opposite of vertex with given index. + 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); + } + } +} diff --git a/External/Triangle.NET/Triangle/TriangleLocator.cs b/External/Triangle.NET/Triangle/TriangleLocator.cs new file mode 100644 index 0000000..e0f3e5e --- /dev/null +++ b/External/Triangle.NET/Triangle/TriangleLocator.cs @@ -0,0 +1,363 @@ +// ----------------------------------------------------------------------- +// +// 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/ +// +// ----------------------------------------------------------------------- + +namespace TriangleNet +{ + using TriangleNet.Geometry; + using TriangleNet.Topology; + + /// + /// Locate triangles in a mesh. + /// + /// + /// 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. + /// + 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); + } + + /// + /// Suggest the given triangle as a starting triangle for point location. + /// + /// + public void Update(ref Otri otri) + { + otri.Copy(ref recenttri); + } + + public void Reset() + { + sampler.Reset(); + recenttri.tri = null; // No triangle has been visited yet. + } + + /// + /// Find a triangle or edge containing a given point. + /// + /// The point to locate. + /// The triangle to start the search at. + /// If 'stopatsubsegment' is set, the search + /// will stop if it tries to walk through a subsegment, and will return OUTSIDE. + /// Location information. + /// + /// 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. + 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(); + } + } + + /// + /// Find a triangle or edge containing a given point. + /// + /// The point to locate. + /// The triangle to start the search at. + /// Location information. + /// + /// 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. + /// + 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); + } + } +} diff --git a/External/Triangle.NET/Triangle/TrianglePool.cs b/External/Triangle.NET/Triangle/TrianglePool.cs new file mode 100644 index 0000000..2a7c0bf --- /dev/null +++ b/External/Triangle.NET/Triangle/TrianglePool.cs @@ -0,0 +1,305 @@ +// ----------------------------------------------------------------------- +// +// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/ +// +// ----------------------------------------------------------------------- + +namespace TriangleNet +{ + using System; + using System.Collections.Generic; + using TriangleNet.Geometry; + using TriangleNet.Topology; + + public class TrianglePool : ICollection + { + // 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 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(BLOCKSIZE); + } + + /// + /// Gets a triangle from the pool. + /// + /// + 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; + } + + /// + /// Restart the triangle pool. + /// + public TrianglePool Restart() + { + foreach (var triangle in stack) + { + // Reset hash to original value. + triangle.hash = -triangle.hash - 1; + } + + stack.Clear(); + + count = 0; + + return this; + } + + /// + /// Samples a number of triangles from the pool. + /// + /// The number of triangles to sample. + /// + /// + internal IEnumerable 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 GetEnumerator() + { + return new Enumerator(this); + } + + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + class Enumerator : IEnumerator + { + // 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; + } + } + } +} diff --git a/External/Triangle.NET/Triangle/TriangleSampler.cs b/External/Triangle.NET/Triangle/TriangleSampler.cs new file mode 100644 index 0000000..8ff42c4 --- /dev/null +++ b/External/Triangle.NET/Triangle/TriangleSampler.cs @@ -0,0 +1,85 @@ +// ----------------------------------------------------------------------- +// +// 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/ +// +// ----------------------------------------------------------------------- + +namespace TriangleNet +{ + using System; + using System.Collections.Generic; + using TriangleNet.Topology; + + /// + /// Used for triangle sampling in the class. + /// + class TriangleSampler : IEnumerable + { + 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; + } + + /// + /// Reset the sampler. + /// + public void Reset() + { + this.samples = 1; + this.triangleCount = 0; + } + + /// + /// Update sampling parameters if mesh changed. + /// + 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 GetEnumerator() + { + return mesh.triangles.Sample(samples, random).GetEnumerator(); + } + + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + } +} diff --git a/External/Triangle.NET/Triangle/Voronoi/BoundedVoronoi.cs b/External/Triangle.NET/Triangle/Voronoi/BoundedVoronoi.cs new file mode 100644 index 0000000..dd7f6ca --- /dev/null +++ b/External/Triangle.NET/Triangle/Voronoi/BoundedVoronoi.cs @@ -0,0 +1,182 @@ +// ----------------------------------------------------------------------- +// +// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/ +// +// ----------------------------------------------------------------------- + +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(); + } + + /// + /// Computes edge intersections with mesh boundary edges. + /// + 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); + } + } + } + + /// + /// Case 1: edge origin lies inside the domain. + /// + 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); + } + + /// + /// Case 2: edge origin lies outside the domain. + /// + 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; + } + //*/ + } +} diff --git a/External/Triangle.NET/Triangle/Voronoi/DefaultVoronoiFactory.cs b/External/Triangle.NET/Triangle/Voronoi/DefaultVoronoiFactory.cs new file mode 100644 index 0000000..6702ff0 --- /dev/null +++ b/External/Triangle.NET/Triangle/Voronoi/DefaultVoronoiFactory.cs @@ -0,0 +1,35 @@ + +namespace TriangleNet.Voronoi +{ + using System; + using TriangleNet.Topology.DCEL; + + /// + /// Default factory for Voronoi / DCEL mesh objects. + /// + 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); + } + } +} diff --git a/External/Triangle.NET/Triangle/Voronoi/IVoronoiFactory.cs b/External/Triangle.NET/Triangle/Voronoi/IVoronoiFactory.cs new file mode 100644 index 0000000..47276e0 --- /dev/null +++ b/External/Triangle.NET/Triangle/Voronoi/IVoronoiFactory.cs @@ -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); + } +} diff --git a/External/Triangle.NET/Triangle/Voronoi/Legacy/BoundedVoronoiLegacy.cs b/External/Triangle.NET/Triangle/Voronoi/Legacy/BoundedVoronoiLegacy.cs new file mode 100644 index 0000000..643fa0b --- /dev/null +++ b/External/Triangle.NET/Triangle/Voronoi/Legacy/BoundedVoronoiLegacy.cs @@ -0,0 +1,692 @@ +// ----------------------------------------------------------------------- +// +// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/ +// +// ----------------------------------------------------------------------- + +namespace TriangleNet.Voronoi.Legacy +{ + using System; + using System.Collections.Generic; + using TriangleNet.Topology; + using TriangleNet.Geometry; + + /// + /// The Bounded Voronoi Diagram is the dual of a PSLG triangulation. + /// + /// + /// 2D Centroidal Voronoi Tessellations with Constraints, 2010, + /// Jane Tournois, Pierre Alliez and Olivier Devillers + /// + [Obsolete("Use TriangleNet.Voronoi.BoundedVoronoi class instead.")] + public class BoundedVoronoiLegacy : IVoronoi + { + IPredicates predicates = RobustPredicates.Default; + + Mesh mesh; + + Point[] points; + List regions; + + // Used for new points on segments. + List segPoints; + int segIndex; + + Dictionary subsegMap; + + bool includeBoundary = true; + + /// + /// Initializes a new instance of the class. + /// + /// Mesh instance. + public BoundedVoronoiLegacy(Mesh mesh) + : this(mesh, true) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Mesh instance. + public BoundedVoronoiLegacy(Mesh mesh, bool includeBoundary) + { + this.mesh = mesh; + this.includeBoundary = includeBoundary; + + Generate(); + } + + /// + /// Gets the list of Voronoi vertices. + /// + public Point[] Points + { + get { return points; } + } + + /// + /// Gets the list of Voronoi regions. + /// + public ICollection Regions + { + get { return regions; } + } + + public IEnumerable Edges + { + get { return EnumerateEdges(); } + } + + /// + /// Computes the bounded voronoi diagram. + /// + private void Generate() + { + mesh.Renumber(); + mesh.MakeVertexMap(); + + // Allocate space for voronoi diagram + this.regions = new List(mesh.vertices.Count); + + this.points = new Point[mesh.triangles.Count]; + this.segPoints = new List(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(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; + } + } + + /// + /// Tag all blind triangles. + /// + /// + /// A triangle is said to be blind if the triangle and its circumcenter + /// lie on two different sides of a constrained edge. + /// + private void TagBlindTriangles() + { + int blinded = 0; + + Stack triangles; + subsegMap = new Dictionary(); + + 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(); + + // 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; + } + + /// + /// Check if given triangle is blinded by given segment. + /// + /// Triangle. + /// Segments + /// Returns true, if the triangle is blinded. + 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 vpoints = new List(); + + // 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 vpoints = new List(); + + // 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); + } + + /// + /// Determines the intersection point of the line segment defined by points A and B with the + /// line segment defined by points C and D. + /// + /// The first segment AB. + /// Endpoint C of second segment. + /// Endpoint D of second segment. + /// Reference to the intersection point. + /// If false, pa and pb represent a line. + /// 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. + /// + 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 EnumerateEdges() + { + // Copy edges + Point first, last; + var edges = new List(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; + } + } +} diff --git a/External/Triangle.NET/Triangle/Voronoi/Legacy/IVoronoi.cs b/External/Triangle.NET/Triangle/Voronoi/Legacy/IVoronoi.cs new file mode 100644 index 0000000..3dd9dc3 --- /dev/null +++ b/External/Triangle.NET/Triangle/Voronoi/Legacy/IVoronoi.cs @@ -0,0 +1,32 @@ +// ----------------------------------------------------------------------- +// +// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/ +// +// ----------------------------------------------------------------------- + +namespace TriangleNet.Voronoi.Legacy +{ + using System.Collections.Generic; + using TriangleNet.Geometry; + + /// + /// Voronoi diagram interface. + /// + public interface IVoronoi + { + /// + /// Gets the list of Voronoi vertices. + /// + Point[] Points { get; } + + /// + /// Gets the list of Voronoi regions. + /// + ICollection Regions { get; } + + /// + /// Gets the list of edges. + /// + IEnumerable Edges { get; } + } +} diff --git a/External/Triangle.NET/Triangle/Voronoi/Legacy/SimpleVoronoi.cs b/External/Triangle.NET/Triangle/Voronoi/Legacy/SimpleVoronoi.cs new file mode 100644 index 0000000..59375b5 --- /dev/null +++ b/External/Triangle.NET/Triangle/Voronoi/Legacy/SimpleVoronoi.cs @@ -0,0 +1,306 @@ +// ----------------------------------------------------------------------- +// +// 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/ +// +// ----------------------------------------------------------------------- + +namespace TriangleNet.Voronoi.Legacy +{ + using System; + using System.Collections.Generic; + using TriangleNet.Topology; + using TriangleNet.Geometry; + using TriangleNet.Tools; + + /// + /// The Voronoi Diagram is the dual of a pointset triangulation. + /// + [Obsolete("Use TriangleNet.Voronoi.StandardVoronoi class instead.")] + public class SimpleVoronoi : IVoronoi + { + IPredicates predicates = RobustPredicates.Default; + + Mesh mesh; + + Point[] points; + Dictionary regions; + + // Stores the endpoints of rays of unbounded Voronoi cells + Dictionary rayPoints; + int rayIndex; + + // Bounding box of the triangles circumcenters. + Rectangle bounds; + + /// + /// Initializes a new instance of the class. + /// + /// + /// + /// Be sure MakeVertexMap has been called (should always be the case). + /// + public SimpleVoronoi(Mesh mesh) + { + this.mesh = mesh; + + Generate(); + } + + /// + /// Gets the list of Voronoi vertices. + /// + public Point[] Points + { + get { return points; } + } + + /// + /// Gets the list of Voronoi regions. + /// + public ICollection Regions + { + get { return regions.Values; } + } + + public IEnumerable Edges + { + get { return EnumerateEdges(); } + } + + /// + /// Gets the Voronoi diagram as raw output data. + /// + /// + /// + /// + /// 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. + /// + 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(mesh.vertices.Count); + + rayPoints = new Dictionary(); + 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); + } + + /// + /// Construct Voronoi region for given vertex. + /// + /// + private void ConstructCell(VoronoiRegion region) + { + var vertex = region.Generator as Vertex; + + var vpoints = new List(); + + 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 EnumerateEdges() + { + // Copy edges + Point first, last; + var edges = new List(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; + } + } +} diff --git a/External/Triangle.NET/Triangle/Voronoi/Legacy/VoronoiRegion.cs b/External/Triangle.NET/Triangle/Voronoi/Legacy/VoronoiRegion.cs new file mode 100644 index 0000000..8d8f9e7 --- /dev/null +++ b/External/Triangle.NET/Triangle/Voronoi/Legacy/VoronoiRegion.cs @@ -0,0 +1,111 @@ +// ----------------------------------------------------------------------- +// +// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/ +// +// ----------------------------------------------------------------------- + +namespace TriangleNet.Voronoi.Legacy +{ + using System; + using System.Collections.Generic; + using TriangleNet.Topology; + using TriangleNet.Geometry; + + /// + /// Represents a region in the Voronoi diagram. + /// + public class VoronoiRegion + { + int id; + Point generator; + List vertices; + bool bounded; + + // A map (vertex id) -> (neighbor across adjacent edge) + Dictionary neighbors; + + /// + /// Gets the Voronoi region id (which is the same as the generators vertex id). + /// + public int ID + { + get { return id; } + } + + /// + /// Gets the Voronoi regions generator. + /// + public Point Generator + { + get { return generator; } + } + + /// + /// Gets the Voronoi vertices on the regions boundary. + /// + public ICollection Vertices + { + get { return vertices; } + } + + /// + /// Gets or sets whether the Voronoi region is bounded. + /// + public bool Bounded + { + get { return bounded; } + set { bounded = value; } + } + + public VoronoiRegion(Vertex generator) + { + this.id = generator.id; + this.generator = generator; + this.vertices = new List(); + this.bounded = true; + + this.neighbors = new Dictionary(); + } + + public void Add(Point point) + { + this.vertices.Add(point); + } + + public void Add(List points) + { + this.vertices.AddRange(points); + } + + /// + /// Returns the neighbouring Voronoi region, that lies across the edge starting at + /// given vertex. + /// + /// Vertex defining an edge of the region. + /// Neighbouring Voronoi region + /// + /// The edge starting at p is well defined (vertices are ordered counterclockwise). + /// + 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); + } + } +} diff --git a/External/Triangle.NET/Triangle/Voronoi/StandardVoronoi.cs b/External/Triangle.NET/Triangle/Voronoi/StandardVoronoi.cs new file mode 100644 index 0000000..b2ac312 --- /dev/null +++ b/External/Triangle.NET/Triangle/Voronoi/StandardVoronoi.cs @@ -0,0 +1,66 @@ +// ----------------------------------------------------------------------- +// +// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/ +// +// ----------------------------------------------------------------------- + +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); + } + + /// + /// Compute edge intersections with bounding box. + /// + 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. + } + } + } + } +} diff --git a/External/Triangle.NET/Triangle/Voronoi/VoronoiBase.cs b/External/Triangle.NET/Triangle/Voronoi/VoronoiBase.cs new file mode 100644 index 0000000..0122d71 --- /dev/null +++ b/External/Triangle.NET/Triangle/Voronoi/VoronoiBase.cs @@ -0,0 +1,291 @@ +// ----------------------------------------------------------------------- +// +// 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/ +// +// ----------------------------------------------------------------------- + +namespace TriangleNet.Voronoi +{ + using System.Collections.Generic; + + using TriangleNet.Topology; + using TriangleNet.Geometry; + using TriangleNet.Topology.DCEL; + + using Vertex = TriangleNet.Topology.DCEL.Vertex; + + /// + /// The Voronoi diagram is the dual of a pointset triangulation. + /// + 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 rays; + + /// + /// Initializes a new instance of the class. + /// + /// Triangle mesh. + /// Voronoi object factory. + /// Geometric predicates implementation. + /// If set to true, the constuctor will call the Generate + /// method, which builds the Voronoi diagram. + protected VoronoiBase(Mesh mesh, IVoronoiFactory factory, IPredicates predicates, + bool generate) + : base(false) + { + this.factory = factory; + this.predicates = predicates; + + if (generate) + { + Generate(mesh); + } + } + + /// + /// Generate the Voronoi diagram from given triangle mesh.. + /// + /// + /// + protected void Generate(Mesh mesh) + { + mesh.Renumber(); + + base.edges = new List(); + this.rays = new List(); + + // 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(vertices); + base.faces = new List(faces); + } + + /// + /// Compute the Voronoi vertices (the circumcenters of the triangles). + /// + /// An empty map, which will map all vertices to a list of leaving edges. + protected List[] 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[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(); + } + + return map; + } + + /// + /// Compute the edges of the Voronoi diagram. + /// + /// + /// + /// + /// Empty vertex map. + protected void ComputeEdges(Mesh mesh, Vertex[] vertices, Face[] faces, List[] 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); + } + } + } + } + + /// + /// Connect all edges of the Voronoi diagram. + /// + /// Maps all vertices to a list of leaving edges. + protected virtual void ConnectEdges(List[] 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 EnumerateEdges() + { + var edges = new List(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; + } + } +} diff --git a/Icons/Rokojori-Action-Library-Logo.svg b/Icons/Rokojori-Action-Library-Logo.svg new file mode 100644 index 0000000..1851829 --- /dev/null +++ b/Icons/Rokojori-Action-Library-Logo.svg @@ -0,0 +1,51 @@ + + + + diff --git a/Icons/Rokojori-Action-Library-Logo.svg.import b/Icons/Rokojori-Action-Library-Logo.svg.import new file mode 100644 index 0000000..87f30d0 --- /dev/null +++ b/Icons/Rokojori-Action-Library-Logo.svg.import @@ -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 diff --git a/Runtime/Files/FilePath.cs b/Runtime/Files/FilePath.cs index 85cf534..7d2b27c 100644 --- a/Runtime/Files/FilePath.cs +++ b/Runtime/Files/FilePath.cs @@ -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; diff --git a/Runtime/Godot/Scenes/SceneFileReader.cs b/Runtime/Godot/Scenes/SceneFileReader.cs index b257069..c008c25 100644 --- a/Runtime/Godot/Scenes/SceneFileReader.cs +++ b/Runtime/Godot/Scenes/SceneFileReader.cs @@ -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(); + var elementMap = new Dictionary(); - 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 ) ); } } diff --git a/Runtime/Html/HtmlAttributeNode.cs b/Runtime/Html/HtmlAttributeNode.cs deleted file mode 100644 index 162a727..0000000 --- a/Runtime/Html/HtmlAttributeNode.cs +++ /dev/null @@ -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; - - - } -} \ No newline at end of file diff --git a/Runtime/Html/HtmlDocument.cs b/Runtime/Html/HtmlDocument.cs deleted file mode 100644 index 256a6d6..0000000 --- a/Runtime/Html/HtmlDocument.cs +++ /dev/null @@ -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 ); - } - - } -} \ No newline at end of file diff --git a/Runtime/Html/HtmlElementNode.cs b/Runtime/Html/HtmlElementNode.cs deleted file mode 100644 index 067c77e..0000000 --- a/Runtime/Html/HtmlElementNode.cs +++ /dev/null @@ -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 _children = new List(); - 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 _attributes = new List(); - - 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 ); - } - } -} \ No newline at end of file diff --git a/Runtime/Html/HtmlElementNodeName.cs b/Runtime/Html/HtmlElementNodeName.cs deleted file mode 100644 index d9be00d..0000000 --- a/Runtime/Html/HtmlElementNodeName.cs +++ /dev/null @@ -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; - } - - - } -} \ No newline at end of file diff --git a/Runtime/Html/HtmlElementSelector.cs b/Runtime/Html/HtmlElementSelector.cs deleted file mode 100644 index d4127b7..0000000 --- a/Runtime/Html/HtmlElementSelector.cs +++ /dev/null @@ -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; } - } -} \ No newline at end of file diff --git a/Runtime/Html/HtmlWalker.cs b/Runtime/Html/HtmlWalker.cs deleted file mode 100644 index 85d59d7..0000000 --- a/Runtime/Html/HtmlWalker.cs +++ /dev/null @@ -1,42 +0,0 @@ -using System.Collections; -using System.Collections.Generic; -using System.Text.RegularExpressions; - -namespace Rokojori -{ - public class HtmlWalker: TreeWalker - { - private static HtmlWalker _instance = new HtmlWalker(); - public static HtmlWalker instance => _instance; - - public override int NumChildren( HtmlNode node ) - { - var elementNode = node as HtmlElementNode; - - if ( elementNode != null ) - { - return elementNode.numChildren; - } - - return 0; - } - - public override HtmlNode ChildAt( HtmlNode node, int index ) - { - var elementNode = node as HtmlElementNode; - - if ( elementNode != null ) - { - return elementNode.GetChildAt( index ); - } - - return null; - } - - public override HtmlNode Parent( HtmlNode node ) - { - return node.parentNode; - } - } - -} \ No newline at end of file diff --git a/Runtime/Logging/Message.cs b/Runtime/Logging/Message.cs new file mode 100644 index 0000000..7ea0cb9 --- /dev/null +++ b/Runtime/Logging/Message.cs @@ -0,0 +1,125 @@ +using System.Collections; +using System.Collections.Generic; +using System.Text; +using System; +using System.Linq; +using Godot; + +namespace Rokojori +{ + public enum MessageType + { + Verbose, + Info, + Warning, + Error + } + + public class Message + { + public MessageType type; + public string content; + + public override string ToString() + { + return "[" + type + "] " + content; + } + + public static Message Create( MessageType type, string content ) + { + var m = new Message(); + m.type = type; + m.content = content; + return m; + } + + public static Message Verbose( string content ) + { + return Create( MessageType.Verbose, content ); + } + + public static Message Info( string content ) + { + return Create( MessageType.Info, content ); + } + + public static Message Warning( string content ) + { + return Create( MessageType.Warning, content ); + } + + public static Message Error( string content ) + { + return Create( MessageType.Error, content ); + } + + } + + public class Messages + { + public static int GetLevel( MessageType type ) + { + if ( MessageType.Verbose == type ) + { + return 0; + } + + if ( MessageType.Info == type ) + { + return 1; + } + + if ( MessageType.Warning == type ) + { + return 2; + } + + if ( MessageType.Error == type ) + { + return 3; + } + + return -1; + } + + public static bool HasError( List messages ) + { + return messages.Find( m => m.type == MessageType.Error ) != null; + } + + public static bool HasWarningsOrErrors( List messages ) + { + return messages.Find( m => m.type == MessageType.Error || m.type == MessageType.Warning ) != null; + } + + public static List GetMessagesWithLevel( List messages, int level ) + { + return Lists.Filter( messages, m => GetLevel( m.type ) >= level ); + } + + public static List GetErrors( List messages) + { + return GetMessagesWithLevel( messages, GetLevel( MessageType.Error ) ); + } + + public static void Verbose( List messages, string content ) + { + messages.Add( Message.Verbose( content ) ); + } + + public static void Info( List messages, string content ) + { + messages.Add( Message.Info( content ) ); + } + + public static void Warning( List messages, string content ) + { + messages.Add( Message.Warning( content ) ); + } + + public static void Error( List messages, string content ) + { + messages.Add( Message.Error( content ) ); + } + } +} \ No newline at end of file diff --git a/Runtime/Logging/RJLog.cs b/Runtime/Logging/RJLog.cs index 997c777..cfdd45b 100644 --- a/Runtime/Logging/RJLog.cs +++ b/Runtime/Logging/RJLog.cs @@ -10,7 +10,7 @@ namespace Rokojori public class RJLog { - static void Stringify( object obj, StringBuilder output ) + public static void Stringify( object obj, StringBuilder output ) { if ( obj == null ) { @@ -22,13 +22,13 @@ namespace Rokojori { var floatValue = (float) obj; var doubleValue = (double) floatValue; - output.Append( RegexUtility.WriteDouble( doubleValue ) ); + output.Append( RegexUtility.NumberToString( doubleValue ) ); return; } if ( obj is double ) { - output.Append( RegexUtility.WriteDouble( (double)obj ) ); + output.Append( RegexUtility.NumberToString( (double)obj ) ); return; } @@ -38,14 +38,14 @@ namespace Rokojori static void LogMessage( string message ) { var trace = GetTrace(); - GD.PrintRich("\n[b]" + message + "[/b]"); + GD.PrintRich("\n[b]" + message + "[/b]" ); GD.PrintRich( trace ); } static void LogMessageWithFullTrace( string message ) { var trace = GetFullTrace(); - GD.PrintRich("\n[b]" + message + "[/b]"); + GD.PrintRich("\n[b]" + message + "[/b]" ); GD.PrintRich( trace ); } @@ -100,41 +100,22 @@ namespace Rokojori public static void LogWithFullTrace( params object[] objects) { - var sb = new StringBuilder(); - - for ( int i = 0; i < objects.Length; i++ ) - { - if ( i != 0 ) - { - sb.Append( " " ); - } - - Stringify( objects[ i ], sb ); - } - - LogMessageWithFullTrace( sb.ToString() ); + LogMessageWithFullTrace( GetLogString( objects ) ); } - public static void Log( params object[] objects) + public static void Log( params object[] objects ) { - var sb = new StringBuilder(); - - for ( int i = 0; i < objects.Length; i++ ) - { - if ( i != 0 ) - { - sb.Append( " " ); - } - - Stringify( objects[ i ], sb ); - } - - LogMessage( sb.ToString() ); + LogMessage( GetLogString( objects ) ); } public static void Error( params object[] objects) { - var sb = new StringBuilder(); + LogErrorMessage( GetLogString( objects ) ); + } + + public static string GetLogString( object[] objects) + { + var sb = new StringBuilder(); for ( int i = 0; i < objects.Length; i++ ) { @@ -146,7 +127,7 @@ namespace Rokojori Stringify( objects[ i ], sb ); } - LogErrorMessage( sb.ToString() ); + return sb.ToString(); } } diff --git a/Runtime/Math/Geometry/Curve3.cs b/Runtime/Math/Geometry/Curve3.cs index 6b5a79e..5528476 100644 --- a/Runtime/Math/Geometry/Curve3.cs +++ b/Runtime/Math/Geometry/Curve3.cs @@ -16,7 +16,6 @@ namespace Rokojori public virtual void SampleMultiple( Range range, int numSamples, List output ) { - var diff = range.length / ( numSamples - 1 ) ; for ( var i = 0 ; i < numSamples; i++ ) @@ -26,6 +25,27 @@ namespace Rokojori } } + public virtual Path2 SampleXZPath( int numSamples ) + { + return SampleXZPath( new Range( 0, 1 ), numSamples ); + } + + public virtual Path2 SampleXZPath( Range range, int numSamples ) + { + var diff = range.length / ( numSamples - 1 ) ; + var points = new List(); + + for ( var i = 0 ; i < numSamples; i++ ) + { + var t = range.min + i * diff; + var p = SampleAt( t ); + points.Add( new Vector2( p.X, p.Z ) ); + + } + + return new Path2( points ); + } + public virtual Vector3 GradientAt( float t ) { return GradientAt( t, _gradientSamplingRange ); diff --git a/Runtime/Math/Geometry/Path2.cs b/Runtime/Math/Geometry/Path2.cs index d07c37e..4095bec 100644 --- a/Runtime/Math/Geometry/Path2.cs +++ b/Runtime/Math/Geometry/Path2.cs @@ -29,19 +29,73 @@ namespace Rokojori this._points = points; } + public Path2( Vector2[] points ) + { + this._points = new List( points ); + } + public int numPoints => _points.Count; public bool empty => _points.Count == 0; - public bool cacheBounds = true; + public bool cacheProperties = true; Vector2? min; Vector2? max; + bool? clockwise; + + public void ClearCachedProperties() + { + min = null; + max = null; + clockwise = null; + } + + public void SetWindingDirection( bool clockwise ) + { + if ( isClockwise == clockwise ) + { + return; + } + + _points.Reverse(); + + this.clockwise = clockwise; + } + + public bool isClockwise + { + get + { + + if ( cacheProperties ) + { + if ( clockwise == null ) + { + clockwise = Geometry2D.IsPolygonClockwise( points.ToArray() ); + } + + return (bool) clockwise; + } + + return Geometry2D.IsPolygonClockwise( points.ToArray() ); + + } + } + + public Shape2 ToShape2() + { + var s = new Shape2(); + s.paths = new List(); + s.paths.Add( this ); + return s; + } + public Vector2 leftTop { get { - if ( cacheBounds && min != null ) + if ( cacheProperties && min != null ) { return (Vector2) min; } @@ -67,7 +121,7 @@ namespace Rokojori get { - if ( cacheBounds && max != null ) + if ( cacheProperties && max != null ) { return (Vector2) max; } @@ -88,6 +142,8 @@ namespace Rokojori } } + public Vector2 center => ( leftTop + rightBottom ) / 2; + public void AddToNavigationPolygon( NavigationPolygon polygon ) { var convexPolygons = Geometry2D.DecomposePolygonInConvex( points.ToArray() ); @@ -289,6 +345,45 @@ namespace Rokojori return intersections % 2 != 0; } + public List AsTriangleFan( bool close = false ) + { + var list = new List(); + + var c = center; + var end = close ? points.Count : ( points.Count - 1 ); + + for ( int i = 0; i < end; i++ ) + { + var n = ( i + 1 ) % points.Count; + + var v0 = points[ i ]; + var v1 = points[ n ]; + var v2 = center; + + list.Add( Math3D.XYasXZ( v0 ) ); + list.Add( Math3D.XYasXZ( v1 ) ); + list.Add( Math3D.XYasXZ( v2 ) ); + + } + + return list; + } + + public MeshGeometry CreateMeshGeometry() + { + var geometry = new MeshGeometry(); + geometry.Initialize(); + + var convexShapes = Convex2.PathToConvexList( this ); + + for ( int i = 0; i < convexShapes.Count; i++ ) + { + geometry.AddConvex2( convexShapes[ i ] ); + } + + return geometry; + } + PointInPathResult CheckPointInPath( Vector2 point, Vector2 endPoint ) { var pointLine = new Line2( point, endPoint ); @@ -376,5 +471,114 @@ namespace Rokojori } + public static Shape2 Union( Path2 a, Path2 b ) + { + var paths = Geometry2D.MergePolygons( a._points.ToArray(), b._points.ToArray() ); + + var s = new Shape2(); + + for ( int i = 0; i < paths.Count; i++ ) + { + s.paths.Add( new Path2( paths[ i ] ) ); + } + + return s; + } + + const double DefaultClipperLibraryScale = 10e8; + + public static Path2 ToLinearXZPath( Path3D path3D ) + { + var curve = path3D.Curve; + + var points = new List(); + + for ( int i = 0; i < curve.PointCount; i++ ) + { + var point = path3D.ToGlobal( curve.GetPointPosition( i ) ); + + points.Add( Math2D.XZ( point ) ); + } + + return new Path2( points ); + + } + + public static List ToClipperPath( Path2 path, double scale = Path2.DefaultClipperLibraryScale ) + { + return Lists.Map( + path.points, p => + { + var x = p.X * scale; + var y = p.Y * scale; + + return new ClipperLib.IntPoint( x, y ); + } + ); + } + + public static Path2 FromClipperPath( List clipperPath, double scale = Path2.DefaultClipperLibraryScale ) + { + var points = Lists.Map( + clipperPath, p => + { + var x = p.X / scale; + var y = p.Y / scale; + + return new Vector2( (float)x, (float)y ); + } + ); + + return new Path2( points ); + } + + public static Shape2 Boolean( Path2 a, Path2 b, Geometry2D.PolyBooleanOperation booleanOperation, bool simplify = true ) + { + // RJLog.Log( "Using Clipper Library" ); + + var clipperPathA = ToClipperPath( a ); + var clipperPathB = ToClipperPath( b ); + + var resultPaths = new List>(); + + var type = ClipperLib.ClipType.ctUnion; + + if ( Geometry2D.PolyBooleanOperation.Difference == booleanOperation ) + { + type = ClipperLib.ClipType.ctDifference; + } + else if ( Geometry2D.PolyBooleanOperation.Intersection == booleanOperation ) + { + type = ClipperLib.ClipType.ctIntersection; + } + else if ( Geometry2D.PolyBooleanOperation.Xor == booleanOperation ) + { + type = ClipperLib.ClipType.ctXor; + } + + var clipper = new ClipperLib.Clipper(); + clipper.AddPath( clipperPathA, ClipperLib.PolyType.ptSubject, true); + clipper.AddPath( clipperPathB, ClipperLib.PolyType.ptClip, true); + clipper.Execute( type, resultPaths ); + + if ( simplify ) + { + resultPaths = ClipperLib.Clipper.SimplifyPolygons( resultPaths ); + } + + + var s = new Shape2(); + + resultPaths.ForEach( + ( r ) => + { + s.paths.Add( FromClipperPath( r ) ); + } + ); + + return s; + } + + } } \ No newline at end of file diff --git a/Runtime/Math/Geometry/Shape2.cs b/Runtime/Math/Geometry/Shape2.cs new file mode 100644 index 0000000..2e2f4ce --- /dev/null +++ b/Runtime/Math/Geometry/Shape2.cs @@ -0,0 +1,170 @@ +using System.Collections; +using System.Collections.Generic; +using Godot; +using TriangleNet.Geometry; +using TriangleNet.Meshing; +using TriangleNet.Meshing.Algorithm; + +namespace Rokojori +{ + public class Shape2 + { + public List paths = new List(); + + public Shape2( params Path2[] paths ) + { + this.paths.AddRange( paths ); + } + + public Shape2( List paths ) + { + this.paths.AddRange( paths ); + } + + Polygon _CreateTNetPolygon() + { + // RJLog.Log( "Creating polygon", paths.Count ); + + var polygon = new Polygon(); + + var index = 0; + paths.ForEach( + ( path )=> + { + var vertices = Lists.Map( path.points, p => new Vertex( p.X, p.Y ) ); + var isHole = path.isClockwise; + + // RJLog.Log( "Adding contour", vertices.Count, isHole ); + + + polygon.Add( new Contour( vertices, index ), isHole ); + + + index ++; + } + ); + + return polygon; + } + + public static List> ToClipperPaths( Shape2 s ) + { + var paths = new List>(); + + s.paths.ForEach( p => paths.Add( Path2.ToClipperPath( p ) ) ); + + return paths; + } + + public static Shape2 FromClipperPaths( List> paths ) + { + var shape = new Shape2(); + + paths.ForEach( p => shape.paths.Add( Path2.FromClipperPath( p ) ) ); + + return shape; + } + + public static Shape2 Boolean( Shape2 a, Shape2 b, Geometry2D.PolyBooleanOperation booleanOperation, bool simplify = true ) + { + // RJLog.Log( "Using Clipper Library" ); + + var clipperPathsA = ToClipperPaths( a ); + var clipperPathsB = ToClipperPaths( b ); + + var resultPaths = new List>(); + + var type = ClipperLib.ClipType.ctUnion; + + if ( Geometry2D.PolyBooleanOperation.Difference == booleanOperation ) + { + type = ClipperLib.ClipType.ctDifference; + } + else if ( Geometry2D.PolyBooleanOperation.Intersection == booleanOperation ) + { + type = ClipperLib.ClipType.ctIntersection; + } + else if ( Geometry2D.PolyBooleanOperation.Xor == booleanOperation ) + { + type = ClipperLib.ClipType.ctXor; + } + + RJLog.Log( "ShapeBool", "type: " + type, "boolOp: " + booleanOperation, "A|B >>",clipperPathsA.Count, clipperPathsB.Count ); + var clipper = new ClipperLib.Clipper(); + clipper.AddPaths( clipperPathsA, ClipperLib.PolyType.ptSubject, true); + clipper.AddPaths( clipperPathsB, ClipperLib.PolyType.ptClip, true); + clipper.Execute( type, resultPaths ); + + if ( simplify ) + { + resultPaths = ClipperLib.Clipper.SimplifyPolygons( resultPaths ); + } + + + var s = new Shape2(); + + resultPaths.ForEach( + ( r ) => + { + s.paths.Add( Path2.FromClipperPath( r ) ); + } + ); + + return s; + } + + public MeshGeometry CreateMeshGeometry() + { + if ( paths.Count == 1 ) + { + // RJLog.Log( "Only 1 path" ); + return paths[ 0 ].CreateMeshGeometry(); + } + + var polygon = _CreateTNetPolygon(); + + var options = new ConstraintOptions(); + var quality = new QualityOptions(); + var triangulator = new Dwyer(); + + IMesh mesh = polygon.Triangulate( options, quality, triangulator ); + + int vertexCount = mesh.Vertices.Count; + int triangleCount = mesh.Triangles.Count; + + var meshGeometry = new MeshGeometry(); + + meshGeometry.vertices = new List( vertexCount ); + meshGeometry.uvs = new List( vertexCount ); + meshGeometry.indices = new List( triangleCount * 3 ); + meshGeometry.normals = new List( vertexCount ); + + foreach ( var v in mesh.Vertices ) + { + var x = (float)v.x; + var y = (float)v.y; + + meshGeometry.vertices.Add( new Vector3( x, 0, y ) ); + meshGeometry.uvs.Add( new Vector2( x, y ) ); + meshGeometry.normals.Add( Vector3.Up ); + } + + + foreach ( var t in mesh.Triangles ) + { + var vertices = t.vertices; + + var indicesList = new List(); + + + for ( int i = 0; i < vertices.Length; i++ ) + { + meshGeometry.indices.Add( t.GetVertexID( i ) ); + } + } + + + return meshGeometry; + } + } +} \ No newline at end of file diff --git a/Runtime/Navigation/NavigationMap.cs b/Runtime/Navigation/NavigationMap.cs new file mode 100644 index 0000000..eb04858 --- /dev/null +++ b/Runtime/Navigation/NavigationMap.cs @@ -0,0 +1,71 @@ +using Godot; +using System.Collections; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Rokojori +{ + public class NavigationMap + { + public Rid map; + public Rid region; + + public Vector3[] GetPath( Vector3 start, Vector3 end ) + { + return NavigationServer3D.MapGetPath( map, start, end, true ); + } + + + public static async Task Create( Node node, NavigationMesh mesh ) + { + RJLog.Log( "Creating map" ); + + var map = NavigationServer3D.MapCreate(); + NavigationServer3D.MapSetUp( map, Vector3.Up ); + NavigationServer3D.MapSetActive( map, true ); + + var mapReady = false; + + NavigationServer3D.MapChanged += ( map )=> + { + RJLog.Log( "Map was changed", map ); + mapReady = true; + }; + + + RJLog.Log( "Creating region" ); + var region = NavigationServer3D.RegionCreate(); + NavigationServer3D.RegionSetTransform( region, Transform3D.Identity ); + NavigationServer3D.RegionSetMap( region, map ); + + + RJLog.Log( "Creating mesh" ); + NavigationServer3D.RegionSetNavigationMesh( region, mesh ); + + + RJLog.Log( "Waiting for Navigation Server" ); + + var waits = 0; + var maxWaits = 100; + + while ( ! mapReady && waits < maxWaits ) + { + await Task.Delay( 10 ); + waits ++; + } + + //await node.ToSignal( node.GetTree(), SceneTree.SignalName.PhysicsFrame ); + + + + RJLog.Log( "Map Created" ); + var mapData = new NavigationMap(); + + mapData.map = map; + mapData.region = region; + + + return mapData; + } + } +} \ No newline at end of file diff --git a/Runtime/Navigation/NavigationMeshes.cs b/Runtime/Navigation/NavigationMeshes.cs new file mode 100644 index 0000000..cd69030 --- /dev/null +++ b/Runtime/Navigation/NavigationMeshes.cs @@ -0,0 +1,75 @@ +using Godot; +using System.Collections; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Rokojori +{ + public class NaviationMeshes + { + public static NavigationMesh FromMeshGeometry( MeshGeometry meshGeometry ) + { + var mesh = new NavigationMesh(); + mesh.SetVertices( meshGeometry.vertices.ToArray() ); + + for ( int i = 0; i < meshGeometry.indices.Count; i += 3 ) + { + var triangle = new int[ 3 ]; + + for ( int j = 0; j < 3; j ++ ) + { + triangle[ j ] = meshGeometry.indices[ i ] + j; + } + + mesh.AddPolygon( triangle ); + } + + return mesh; + } + + public static NavigationMesh FromTrianglePolygons( List> trianglePolygons ) + { + var mesh = new NavigationMesh(); + + var size = 0; + + trianglePolygons.ForEach( p => size += p.Count ); + + var vertices = new Vector3[ size ]; + + var offset = 0; + + var polygons = new List(); + + trianglePolygons.ForEach( + p => + { + var polygon = new int[ p.Count ]; + var polygonOfffset = offset; + + p.ForEach( v => + { + polygon[ offset - polygonOfffset ] = offset; + + vertices[ offset ] = v; + + offset++; + + } + ); + + polygons.Add( polygon ); + + } + ); + + mesh.SetVertices( vertices ); + + polygons.ForEach( p => mesh.AddPolygon( p ) ); + + return mesh; + } + + } + +} \ No newline at end of file diff --git a/Runtime/Procedural/MeshCreationTest.cs b/Runtime/Procedural/MeshCreationTest.cs new file mode 100644 index 0000000..0284397 --- /dev/null +++ b/Runtime/Procedural/MeshCreationTest.cs @@ -0,0 +1,175 @@ +using System.Collections; +using System.Collections.Generic; +using Godot; +using System; + + + +namespace Rokojori +{ + [Tool] + [GlobalClass] + public partial class MeshCreationTest:Node3D + { + + [Export] + public MeshInstance3D meshInstance3D; + + [Export] + public bool createQuad = false; + + [Export] + public float size = 1; + + [Export] + public bool createSpline = false; + + [Export] + public Spline spline; + + [Export] + public int splineResolution = 50; + + + [Export] + public bool createBoolOp = false; + + [Export] + public bool createBoolOpAlways = false; + + [Export] + public Spline splineA; + [Export] + public int splineAResolution = 50; + + [Export] + public Spline splineB; + [Export] + public int splineBResolution = 50; + + [Export] + public Geometry2D.PolyBooleanOperation booleanOperation; + + [Export] + public bool createNavMesh = false; + + [Export] + public Node3D[] countries; + + [Export] + public float navMeshRadius = 100; + + + public override void _Process( double delta ) + { + CreateQuad(); + CreateSpline(); + CreateBoolOp(); + CreateNavMesh(); + } + + void CreateQuad() + { + if ( ! createQuad ) + { + return; + } + + createQuad = false; + + RJLog.Log( "CREATING QUAD" ); + + var meshGeometry = new MeshGeometry(); + meshGeometry.Initialize(); + + meshGeometry.AddQuad( new Vector3( 0, 0, 0 ), size ); + + + meshInstance3D.Mesh = meshGeometry.GenerateMesh(); + + } + + void CreateSpline() + { + if ( ! createSpline ) + { + return; + } + + createSpline = false; + + RJLog.Log( "CREATING SPLINE" ); + + var path = spline.GetCurve().SampleXZPath( splineResolution ); + var meshGeometry = path.CreateMeshGeometry(); + + meshInstance3D.Mesh = meshGeometry.GenerateMesh(); + + } + + void CreateBoolOp() + { + if ( ! ( createBoolOp || createBoolOpAlways ) ) + { + return; + } + + createBoolOp = false; + + + + var pathA = splineA.GetCurve().SampleXZPath( splineResolution ); + var pathB = splineB.GetCurve().SampleXZPath( splineResolution ); + + // RJLog.Log( "CREATING SPLINE", pathA.isClockwise, pathB.isClockwise ); + + pathA.SetWindingDirection( true ); + pathB.SetWindingDirection( true ); + + var shape = Path2.Boolean( pathA, pathB, booleanOperation ); + + var meshGeometry = shape.CreateMeshGeometry(); + + meshInstance3D.Mesh = meshGeometry.GenerateMesh(); + + } + + void CreateNavMesh() + { + if ( ! ( createNavMesh ) ) + { + return; + } + + createNavMesh = false; + + + var circleAreaShape = new Shape2(); + circleAreaShape.paths.Add( Path2.Circle( Vector2.Zero, navMeshRadius) ); + + var islands = new Shape2(); + + foreach ( var c in countries ) + { + var path = Nodes.GetAnyChild( c ); + + if ( path == null ) + { + continue; + } + + var islandPath = Path2.ToLinearXZPath( path ); + islands.paths.Add( islandPath ); + RJLog.Log( "Getting path:", c.Name, islandPath.points.Count ); + } + + var shape = Shape2.Boolean( circleAreaShape, islands, Geometry2D.PolyBooleanOperation.Difference ); + + var meshGeometry = shape.CreateMeshGeometry(); + + meshInstance3D.Mesh = meshGeometry.GenerateMesh(); + + } + + } +} \ No newline at end of file diff --git a/Runtime/Procedural/MeshGeometry.cs b/Runtime/Procedural/MeshGeometry.cs index 97561c6..5470b7a 100644 --- a/Runtime/Procedural/MeshGeometry.cs +++ b/Runtime/Procedural/MeshGeometry.cs @@ -16,7 +16,117 @@ namespace Rokojori public List uvs; public List uv2s; public List colors; - + + public int numTriangles => indices.Count / 3; + + + public void ForEachTriangle( Action callback ) + { + var index = 0; + + for ( int i = 0; i < indices.Count; i += 3 ) + { + var a = indices[ i + 0 ]; + var b = indices[ i + 1 ]; + var c = indices[ i + 2 ]; + + callback( index, a, b, c ); + + index ++; + } + } + + public MeshGeometry UniqueTriangles() + { + var mg = new MeshGeometry(); + + mg.Initialize( true, true, colors != null, uv2s != null ); + + if ( colors != null ) + { + mg.colors = new List(); + } + + if ( uv2s != null ) + { + mg.uv2s = new List(); + } + + ForEachTriangle( + ( index, a, b, c )=> + { + var vA = vertices[ a ]; + var vB = vertices[ b ]; + var vC = vertices[ c ]; + + var uvA = uvs[ a ]; + var uvB = uvs[ b ]; + var uvC = uvs[ c ]; + + var nA = normals[ a ]; + var nB = normals[ b ]; + var nC = normals[ c ]; + + mg.AddTriangle( + vA, vB, vC, + nA, nB, nC, + uvA, uvB, uvC + ); + + if ( colors != null ) + { + Lists.Add( mg.colors, colors[ a ], colors[ b ], colors[ c ] ); + } + + if ( uv2s != null ) + { + Lists.Add( mg.uv2s, uv2s[ a ], uv2s[ b ], uv2s[ c ] ); + } + + } + ); + + return mg; + } + + public void SetTriangleUVs( int triangleIndex, Vector2 uv ) + { + var i = triangleIndex * 3; + + var a = indices[ i + 0 ]; + var b = indices[ i + 1 ]; + var c = indices[ i + 2 ]; + + uvs[ a ] = uv; + uvs[ b ] = uv; + uvs[ c ] = uv; + } + + public void SetTriangleNormal( int triangleIndex, Vector3 normal ) + { + var i = triangleIndex * 3; + + var a = indices[ i + 0 ]; + var b = indices[ i + 1 ]; + var c = indices[ i + 2 ]; + + normals[ a ] = normal; + normals[ b ] = normal; + normals[ c ] = normal; + } + + public void SetTriangleColor( int triangleIndex, Color color ) + { + var i = triangleIndex * 3; + + var a = indices[ i + 0 ]; + var b = indices[ i + 1 ]; + var c = indices[ i + 2 ]; + + colors[ a ] = color; + colors[ b ] = color; + colors[ c ] = color; + } public void Initialize( bool normals = true, bool uvs = true, bool colors = false, bool uvs2 = false) { @@ -29,6 +139,7 @@ namespace Rokojori this.colors = colors ? new List() : null; } + public void AddTriangle( Vector3 va, Vector3 vb, Vector3 vc, Vector3 na, Vector3 nb, Vector3 nc, @@ -42,15 +153,7 @@ namespace Rokojori Lists.Add( indices, index, index + 1, index + 2 ); } - public void AddQuad( - Vector3 va, Vector3 vb, Vector3 vc, Vector3 vd, - Vector3 na, Vector3 nb, Vector3 nc, Vector3 nd, - Vector2 uva, Vector2 uvb, Vector2 uvc, Vector2 uvd - ) - { - AddTriangle( va, vb, vc, na, nb, nc, uva, uvb, uvc ); - AddTriangle( vc, vd, va, nc, nd, na, uvc, uvd, uva ); - } + public void AddTriangle( Vector3 va, Vector3 vb, Vector3 vc, @@ -64,6 +167,27 @@ namespace Rokojori uva, uvb, uvc ); } + + + public void AddTriangle( Vector3 va, Vector3 vb, Vector3 vc ) + { + var n = Vector3.Up; + var uv = Vector2.Zero; + + AddTriangle( va, vb, vc, n, n, n, uv, uv, uv ); + } + + public void AddQuad( + Vector3 va, Vector3 vb, Vector3 vc, Vector3 vd, + Vector3 na, Vector3 nb, Vector3 nc, Vector3 nd, + Vector2 uva, Vector2 uvb, Vector2 uvc, Vector2 uvd + ) + { + AddTriangle( va, vb, vc, na, nb, nc, uva, uvb, uvc ); + AddTriangle( vc, vd, va, nc, nd, na, uvc, uvd, uva ); + } + + public void AddQuad( Vector3 va, Vector3 vb, Vector3 vc, Vector3 vd, Vector2 uva, Vector2 uvb, Vector2 uvc, Vector2 uvd @@ -74,22 +198,54 @@ namespace Rokojori AddTriangle( vc, vd, va, uvc, uvd, uva ); } - public void AddTriangle( Vector3 va, Vector3 vb, Vector3 vc ) - { - var n = Vector3.Up; - var uv = Vector2.Zero; - - AddTriangle( va, vb, vc, n, n, n, uv, uv, uv ); - } - public void AddQuad( Vector3 va, Vector3 vb, Vector3 vc, Vector3 vd ) { - AddTriangle( va, vb, vc ); - AddTriangle( vc, vd, va ); + AddQuad( + va, vb, vc, vd, + new Vector2( 0, 0 ), + new Vector2( 0, 1 ), + new Vector2( 1, 1 ), + new Vector2( 1, 0 ) + ); } + public void AddQuad( Vector3 offset, float size ) + { + var center = new Vector3( 0.5f, 0.5f, 0 ); - public ArrayMesh Generate( Mesh.PrimitiveType type = Mesh.PrimitiveType.Triangles, ArrayMesh arrayMesh = null ) + /* + + [-0.5, -0.5] [0.5, -0.5 ] + [-0.5, 0.5] [0.5, 0.5 ] + + */ + + AddQuad( + new Vector3( -0.5f, -0.5f, 0 ) * size + offset , + new Vector3( -0.5f, 0.5f, 0 ) * size + offset , + new Vector3( 0.5f, 0.5f, 0 ) * size + offset , + new Vector3( 0.5f, -0.5f, 0 ) * size + offset + ); + } + + public void AddConvex2( Convex2 convex ) + { + var points = convex.points; + var tris = points.Count - 2; + + for ( int i = 0; i < tris; i++ ) + { + var p0 = Math3D.XYasXZ( points[ 0 ] ); + var p1 = Math3D.XYasXZ( points[ i + 1 ] ); + var p2 = Math3D.XYasXZ( points[ i + 2 ] ); + + AddTriangle( p0, p1, p2 ); + } + } + + + + public ArrayMesh GenerateMesh( Mesh.PrimitiveType type = Mesh.PrimitiveType.Triangles, ArrayMesh arrayMesh = null ) { if ( arrayMesh == null ) { @@ -99,32 +255,32 @@ namespace Rokojori var surfaceArray = new Godot.Collections.Array(); surfaceArray.Resize( (int) Mesh.ArrayType.Max ); - if ( vertices != null ) + if ( vertices != null && vertices.Count != 0 ) { surfaceArray[ (int) Mesh.ArrayType.Vertex ] = vertices.ToArray(); } - if ( normals != null ) + if ( normals != null && normals.Count != 0 ) { surfaceArray[ (int) Mesh.ArrayType.Normal ] = normals.ToArray(); } - if ( uvs != null ) + if ( uvs != null && uvs.Count != 0 ) { surfaceArray[ (int) Mesh.ArrayType.TexUV ] = uvs.ToArray(); } - if ( uv2s != null ) + if ( uv2s != null && uv2s.Count != 0 ) { surfaceArray[ (int) Mesh.ArrayType.TexUV2 ] = uv2s.ToArray(); } - if ( colors != null ) + if ( colors != null && colors.Count != 0 ) { surfaceArray[ (int) Mesh.ArrayType.Color ] = colors.ToArray(); } - if ( indices != null ) + if ( indices != null && indices.Count != 0 ) { surfaceArray[ (int) Mesh.ArrayType.Index ] = indices.ToArray(); } diff --git a/Runtime/Random/RandomEngine.cs b/Runtime/Random/RandomEngine.cs index 4d6e678..8014722 100644 --- a/Runtime/Random/RandomEngine.cs +++ b/Runtime/Random/RandomEngine.cs @@ -124,6 +124,16 @@ namespace Rokojori return InsideSphere() * ( size * 0.5f ); } + public Color HSL( float hMin = 0 , float hMax = 360, float sMin = 1, float sMax = 1, float lMin = 0.5f, float lMax = 0.5f ) + { + var h = Range( hMin, hMax ); + var s = Range( sMin, sMax ); + var l = Range( lMin, lMax ); + + + return new HSLColor( h, s, l ); + } + public int IntegerInclusive( int min, int max ) { return (int) ( Mathf.Floor( this.Next() * ( max - min + 1 ) ) + min ) ; diff --git a/Runtime/Text/JSON/JSONValue.cs b/Runtime/Text/JSON/JSONValue.cs index 0e56afc..96dc05e 100644 --- a/Runtime/Text/JSON/JSONValue.cs +++ b/Runtime/Text/JSON/JSONValue.cs @@ -69,7 +69,7 @@ namespace Rokojori } else if ( JSONDataType.NUMBER == _dataType ) { - return RegexUtility.WriteDouble( _numberValue ); + return RegexUtility.NumberToString( _numberValue ); } else if ( JSONDataType.BOOLEAN == _dataType ) { diff --git a/Runtime/Text/Lexing/Lexer.cs b/Runtime/Text/Lexing/Lexer.cs index 8594b8e..80bd540 100644 --- a/Runtime/Text/Lexing/Lexer.cs +++ b/Runtime/Text/Lexing/Lexer.cs @@ -6,6 +6,8 @@ namespace Rokojori { public class Lexer { + public static readonly string Default_Mode = ""; + Dictionary> _modes = new Dictionary>(); bool _hasError =false; @@ -66,6 +68,7 @@ namespace Rokojori events.ForEach( ev => { ev.GrabMatch( source ); } ); } + public void Lex( string source, System.Action callback, int offset = 0, string mode = "" ) { var ERROR_FLAG = -1; @@ -94,15 +97,19 @@ namespace Rokojori var matchers = _modes[ mode ]; var foundSomething = false; + // RJLog.Log( "--- MATCHING ----> ", offset, "CHARACTER:", source[ offset ] ); + for ( var i = 0; i < matchers.Count; i++ ) { var matcher = matchers[ i ]; var matchLength = matcher.MatchLength( source, offset ); + // RJLog.Log( matcher.type, ">>", matchLength, matcher.matcher ); + if ( matchLength > 0 ) { - lexerEvent.Set( matcher.type, offset, matchLength ); - //Logs.Log(matcher.type, ">>", "'"+source.Substring( offset, matchLength )+"'", "@", offset, matchLength ); + lexerEvent.Set( matcher.fullType, offset, matchLength ); + // Logs.Log(matcher.type, ">>", "'"+source.Substring( offset, matchLength )+"'", "@", offset, matchLength ); callback( lexerEvent ); foundSomething = true; @@ -121,8 +128,10 @@ namespace Rokojori if ( ! foundSomething ) { - var errorMessage = "@Lexer-Error. No match: '" + mode + "'"; - RJLog.Log(errorMessage, "@", offset ); + var modeInfo = mode == "" ? "default-mode" : " mode: '" + mode + "'"; + + var errorMessage = "@Lexer-Error. No match for " + modeInfo; + RJLog.Log( errorMessage, "@", offset ); lexerEvent.Set( errorMessage, offset, ERROR_FLAG ); _hasError = true; callback( lexerEvent ); diff --git a/Runtime/Text/Lexing/LexerEvent.cs b/Runtime/Text/Lexing/LexerEvent.cs index b5e6837..28d4594 100644 --- a/Runtime/Text/Lexing/LexerEvent.cs +++ b/Runtime/Text/Lexing/LexerEvent.cs @@ -23,6 +23,33 @@ namespace Rokojori public string type { get { return _type; } } public int offset { get { return _offset; } } public int length { get { return _length; } } + + string _mode = null; + + public string mode + { + get + { + if ( _mode != null ) + { + return _mode; + } + + var modeDelimiterPosition = type.IndexOf( LexerMatcher.FullTypeDelimiter ); + + if ( modeDelimiterPosition == -1 ) + { + _mode = ""; + + return _mode; + } + + _mode = type.Substring( 0, modeDelimiterPosition ); + + return _mode; + } + } + public bool isError { diff --git a/Runtime/Text/Lexing/LexerLibrary/XMLLexer.cs b/Runtime/Text/Lexing/LexerLibrary/XMLLexer.cs new file mode 100644 index 0000000..df854dd --- /dev/null +++ b/Runtime/Text/Lexing/LexerLibrary/XMLLexer.cs @@ -0,0 +1,129 @@ +using System.Collections; +using System.Collections.Generic; +using System.Text.RegularExpressions; + +namespace Rokojori +{ + public class XMLLexer:Lexer + { + public static readonly string Inside_Start_Tag = "Inside_Start_Tag"; + + public static readonly LexerMatcher XMLComment = + new LexerMatcher( "XMLComment", @"" ); + + public static readonly LexerMatcher XMLCData = + new LexerMatcher( "XMLCData", @"" ); + + public static readonly LexerMatcher XMLInstruction = + new LexerMatcher( "XMLInstruction", @"" ); + + public static readonly LexerMatcher XMLProcessingInstruction = + new LexerMatcher( "XMLDeclaration", @"<\?((?:.|\r|\n)*?)>" ); + + public static readonly LexerMatcher XMLStartTag = + new LexerMatcher( "XMLStartTag", + XMLRegexExtensions.Extend( @"<(?:\xml-name:)?\xml-name" ), + Lexer.Default_Mode, XMLLexer.Inside_Start_Tag + ); + + public static readonly LexerMatcher XMLEndTag = + new LexerMatcher( "XMLEndTag", + XMLRegexExtensions.Extend( @"<\/(?:\xml-name:)?\xml-name>" ) + ); + + public static readonly LexerMatcher XMLEscapedEntity = + new LexerMatcher( "XMLEscapedEntity", @"&#?\w+;" ); + + + public static readonly LexerMatcher XMLText = + new LexerMatcher( "XMLText", @"([^<].)" ); + + public static readonly LexerMatcher XMLAttributeName = + new LexerMatcher( "XMLAttributeName", + XMLRegexExtensions.Extend( @"(?:\xml-name:)?\xml-name" ), + XMLLexer.Inside_Start_Tag, XMLLexer.Inside_Start_Tag + ); + + public static readonly LexerMatcher XMLStartTagClosing = + new LexerMatcher( "XMLStartTagClosing", @"\/?>", XMLLexer.Inside_Start_Tag, Lexer.Default_Mode ); + + public static readonly LexerMatcher XMLAttributeAssignment = + new LexerMatcher( "XMLAttributeAssignment", @"=", XMLLexer.Inside_Start_Tag, XMLLexer.Inside_Start_Tag ); + + public static readonly LexerMatcher InsideStartTag_SingleQuotedStringMatcher = + LexerMatcherLibrary.SingleQuotedStringMatcher.WithModes( XMLLexer.Inside_Start_Tag ); + + public static readonly LexerMatcher InsideStartTag_DoubleQuotedStringMatcher = + LexerMatcherLibrary.DoubleQuotedStringMatcher.WithModes( XMLLexer.Inside_Start_Tag ); + + public static readonly LexerMatcher InsideStartTag_WhiteSpaceMatcher = + LexerMatcherLibrary.WhiteSpaceMatcher.WithModes( XMLLexer.Inside_Start_Tag ); + + public static readonly LexerMatcher InsideStartTag_BreakMatcher = + LexerMatcherLibrary.BreakMatcher.WithModes( XMLLexer.Inside_Start_Tag ); + + + public static List Lex( string source ) + { + var lexer = new XMLLexer(); + var events = lexer.LexToList( source ); + + if ( lexer.hasError ) + { + return null; + } + + events.ForEach( ev => { ev.GrabMatch( source ); } ); + return events; + } + + public XMLLexer() + { + + /* + + default: + { , COMMENT } + { , INSTRUCTION } + { , XML DEC } + { , CLOSE_TAG } + { &*; ESCAPED_TEXT } + { *, TEXT } + + insideStartTag: + { >, START_TAG_CLOSER, "default" } + { "\*", STRING } + { '\*', STRING } + { NS:\w(-\w), WORD_WITH_HYPHEN } + { =, ASSIGNMENT } + + */ + + AddAllMatchers( + XMLLexer.XMLComment, + XMLLexer.XMLCData, + XMLLexer.XMLInstruction, + XMLLexer.XMLProcessingInstruction, + + XMLLexer.XMLStartTag, + XMLLexer.XMLEndTag, + + XMLLexer.XMLEscapedEntity, + LexerMatcherLibrary.WhiteSpaceMatcher, + LexerMatcherLibrary.BreakMatcher, + XMLLexer.XMLText, + + XMLLexer.XMLStartTagClosing, + + XMLLexer.InsideStartTag_SingleQuotedStringMatcher, + XMLLexer.InsideStartTag_DoubleQuotedStringMatcher, + XMLLexer.XMLAttributeName, + XMLLexer.XMLAttributeAssignment, + XMLLexer.InsideStartTag_WhiteSpaceMatcher, + XMLLexer.InsideStartTag_BreakMatcher + ); + + } + } +} \ No newline at end of file diff --git a/Runtime/Text/Lexing/LexerLibrary/XMLRegexExtension.cs b/Runtime/Text/Lexing/LexerLibrary/XMLRegexExtension.cs new file mode 100644 index 0000000..3d03e4a --- /dev/null +++ b/Runtime/Text/Lexing/LexerLibrary/XMLRegexExtension.cs @@ -0,0 +1,24 @@ +using System.Collections; +using System.Collections.Generic; +using System.Text.RegularExpressions; +using System.Text; + +using System.Globalization; +using Godot; + +namespace Rokojori +{ + public static class XMLRegexExtensions + { + public static string Extend( string source ) + { + source = RegexExtensions.Extend( source ); + + // xml-name + source = RegexUtility.Replace( source, @"\\xml-name", @"((?:\w|_)(?:\w|\-|_|\.)*)" ); + + + return source; + } + } +} \ No newline at end of file diff --git a/Runtime/Text/Lexing/LexerMatcher.cs b/Runtime/Text/Lexing/LexerMatcher.cs index e32ae57..f201b3f 100644 --- a/Runtime/Text/Lexing/LexerMatcher.cs +++ b/Runtime/Text/Lexing/LexerMatcher.cs @@ -6,24 +6,52 @@ namespace Rokojori { public class LexerMatcher { + public static readonly string FullTypeDelimiter = "|"; string _mode; public string mode => _mode; string _type; public string type => _type; + string _fullType; + public string fullType + { + get + { + if ( _fullType != null ) + { + return _fullType; + } + + _fullType = _mode == "" ? _type : ( _mode + LexerMatcher.FullTypeDelimiter + _type ); + + return _fullType; + } + } + + string _nextMode; public string nextMode => _nextMode; Regex _matcher; - Regex matcher + public Regex matcher { get { return this._matcher; } } public bool Matches( LexerEvent le ) { - return type == le.type; + return fullType == le.type; + } + + public RegexMatches GetRegexMatches( string source ) + { + return new RegexMatches( matcher.Match( source, 0 ) ); + } + + public RegexMatches GetRegexMatches( LexerEvent le ) + { + return GetRegexMatches( le.match ); } public LexerMatcher( string type, Regex matcher, string mode = "", string nextMode = "" ) @@ -47,7 +75,22 @@ namespace Rokojori return new LexerMatcher( type, RegexExtensions.Extend( matcher ), mode, nextMode ); } - public LexerMatcher CloneWithMode( string mode, string nextMode ) + public LexerMatcher CloneWithModes( string mode, string nextMode ) + { + return new LexerMatcher( _type, _matcher, mode, nextMode ); + } + + public LexerMatcher WithModes( string mode, string nextMode = null ) + { + if ( nextMode == null ) + { + nextMode = mode; + } + + return new LexerMatcher( _type, _matcher, mode, nextMode ); + } + + public LexerMatcher WithNext( string nextMode ) { return new LexerMatcher( _type, _matcher, mode, nextMode ); } diff --git a/Runtime/Text/Lexing/LexerMatcherLibrary.cs b/Runtime/Text/Lexing/LexerMatcherLibrary.cs index d25d694..f630511 100644 --- a/Runtime/Text/Lexing/LexerMatcherLibrary.cs +++ b/Runtime/Text/Lexing/LexerMatcherLibrary.cs @@ -52,46 +52,51 @@ namespace Rokojori new LexerMatcher( "Null", "null" ); public static readonly LexerMatcher BoolMatcher = - new LexerMatcher( "Bool", "true|false" ); + new LexerMatcher( "Bool", "true|false" ); public static readonly LexerMatcher LogicMatcher = - new LexerMatcher( "Logic", "if|else|switch|do|while|for|break|continue|return" ); + new LexerMatcher( "Logic", "if|else|switch|do|while|for|break|continue|return" ); public static readonly LexerMatcher OperatorMatcher = - new LexerMatcher( "Operator", "(?:\\=\\=)|(?:\\+\\+)|(?:\\-\\-)|\\+|\\-|\\*|\\/|\\^|\\||\\~|\\&|\\%|\\<|\\>|\\=|\\!|\\.|\\:|\\,|\\;" ); + new LexerMatcher( "Operator", "(?:\\=\\=)|(?:\\+\\+)|(?:\\-\\-)|\\+|\\-|\\*|\\/|\\^|\\||\\~|\\&|\\%|\\<|\\>|\\=|\\!|\\.|\\:|\\,|\\;" ); public static readonly LexerMatcher BracketMatcher = - new LexerMatcher( "Bracket", @"\(|\)|\[|\]|\{|\}" ); + new LexerMatcher( "Bracket", @"\(|\)|\[|\]|\{|\}" ); public static readonly LexerMatcher BlockStartMatcher = - new LexerMatcher( "BlockStart", @"\{" ); + new LexerMatcher( "BlockStart", @"\{" ); public static readonly LexerMatcher BlockEndMatcher = new LexerMatcher( "BlockStart", @"\}" ); public static readonly LexerMatcher ClassMatcher = - new LexerMatcher( "Class", @"\bclass\b" ); + new LexerMatcher( "Class", @"\bclass\b" ); public static readonly LexerMatcher AccessModifierMatcher = - new LexerMatcher( "AccessModifier", @"\b(?:public|protected|private)\b" ); - + new LexerMatcher( "AccessModifier", @"\b(?:public|protected|private)\b" ); public static readonly LexerMatcher SingleLineCommentMatcher = - new LexerMatcher( "SingleLineComment", @"//.*" ); + new LexerMatcher( "SingleLineComment", @"//.*" ); public static readonly LexerMatcher CInstructionMatcher = - new LexerMatcher( "CInstruction", @"\#.*" ); + new LexerMatcher( "CInstruction", @"\#.*" ); public static readonly LexerMatcher MultiLineCommentMatcher = - new LexerMatcher( "MultiLineComment", @"\/\*(.|(\r\n|\r|\n))*?\*\/" ); + new LexerMatcher( "MultiLineComment", @"\/\*(.|(\r\n|\r|\n))*?\*\/" ); public static readonly LexerMatcher AnySymbolMatcher = - new LexerMatcher( "AnySymbol", @"." ); + new LexerMatcher( "AnySymbol", @"." ); public static readonly LexerMatcher HashTag = - new LexerMatcher( "HashTag", @"\#(\w|-|\d)+" ); + new LexerMatcher( "HashTag", @"\#(\w|-|\d)+" ); public static readonly LexerMatcher URL = - new LexerMatcher( "URL", @"https?\:\/\/(\w|\.|\-|\?|\=|\+|\/)+" ); - } + new LexerMatcher( "URL", @"https?\:\/\/(\w|\.|\-|\?|\=|\+|\/)+" ); + + + + } + + + } \ No newline at end of file diff --git a/Runtime/Text/RegexExtensions.cs b/Runtime/Text/RegexExtensions.cs index 4ec7227..c039b9d 100644 --- a/Runtime/Text/RegexExtensions.cs +++ b/Runtime/Text/RegexExtensions.cs @@ -8,7 +8,7 @@ using Godot; namespace Rokojori { - public class RegexExtensions + public static class RegexExtensions { public static string Extend( string source ) { @@ -18,7 +18,7 @@ namespace Rokojori source = RegexUtility.Replace( source, @"\\l", @"(?:\r\n|\r|\n)" ); // v: Variable - source = RegexUtility.Replace( source, @"\\l", @"(?:\w|_)+" ); + source = RegexUtility.Replace( source, @"\\v", @"(?:\w|_)+" ); //RJLog.Log( "Extended:", source ); diff --git a/Runtime/Text/RegexMatches.cs b/Runtime/Text/RegexMatches.cs new file mode 100644 index 0000000..ae24fd2 --- /dev/null +++ b/Runtime/Text/RegexMatches.cs @@ -0,0 +1,44 @@ +using System.Collections; +using System.Collections.Generic; +using System.Text.RegularExpressions; +using System.Text; + +using System.Globalization; +using Godot; + +namespace Rokojori +{ + public class RegexMatches + { + public Match regexMatch; + + public RegexMatches( Match regexMatch ) + { + this.regexMatch = regexMatch; + } + + public string Group( int index ) + { + if ( regexMatch == null ) + { + RJLog.Log( "No regexMatch" ); + return null; + } + + if ( ! regexMatch.Success ) + { + RJLog.Log( "Not matching" ); + return null; + } + + if ( regexMatch.Groups.Count <= index ) + { + RJLog.Log( "Invalid Group Index " + index + ", has only " + regexMatch.Groups.Count ); + return null; + } + + + return regexMatch.Groups[ index ].Success ? regexMatch.Groups[ index ].Value : null; + } + } +} \ No newline at end of file diff --git a/Runtime/Text/RegexUtility.cs b/Runtime/Text/RegexUtility.cs index 58b0382..4498985 100644 --- a/Runtime/Text/RegexUtility.cs +++ b/Runtime/Text/RegexUtility.cs @@ -27,7 +27,7 @@ namespace Rokojori return source; } - public static string WriteDouble( double value ) + public static string NumberToString( double value ) { try { @@ -65,11 +65,6 @@ namespace Rokojori return ( float ) ParseDouble( source, alternative ); } - /*public static int ParseInt( string source, int alternative = 0 ) - { - return Mathf.RoundToInt( ( float ) ParseDouble( source, alternative ) ); - }*/ - public static int ParseInt( string source, int alternative = 0 ) { var multiply = 1; @@ -82,10 +77,14 @@ namespace Rokojori if ( i == 0 ) { if ( symbol == '-' ) - { return -value; } + { + return - value; + } if ( symbol == '+' ) - { return value; } + { + return value; + } } var digitValue = ParseDigit( symbol ); diff --git a/Runtime/Tools/Lists.cs b/Runtime/Tools/Lists.cs index 3869349..dbb3e56 100644 --- a/Runtime/Tools/Lists.cs +++ b/Runtime/Tools/Lists.cs @@ -7,6 +7,21 @@ namespace Rokojori { public class Lists { + public static List CollectIndices( List list, Func evaluator ) + { + var output = new List(); + + for ( int i = 0; i < list.Count; i++ ) + { + if ( evaluator( list[ i ] ) ) + { + output.Add( i ); + } + } + + return output; + } + public static List CombineAll( params List[] lists ) { var list = new List(); @@ -144,7 +159,26 @@ namespace Rokojori sb.Append( seperator ); } - sb.Append( array[ i ] ); + var obj = (object) array[ i ]; + + RJLog.Stringify( array[ i ], sb ); + } + + return sb.ToString(); + } + + public static string Join( List array, string seperator = ", " ) + { + var sb = new StringBuilder(); + + for ( var i = 0; i < array.Count; i++ ) + { + if ( i != 0 ) + { + sb.Append( seperator ); + } + + sb.Append( RegexUtility.NumberToString( array[ i ] ) ); } return sb.ToString(); diff --git a/Runtime/XML/HTML/HTMLElementName.cs b/Runtime/XML/HTML/HTMLElementName.cs new file mode 100644 index 0000000..4ca9a20 --- /dev/null +++ b/Runtime/XML/HTML/HTMLElementName.cs @@ -0,0 +1,17 @@ +using System.Collections; +using System.Collections.Generic; +using System.Text.RegularExpressions; + +namespace Rokojori +{ + public class HTMLElementName + { + public static readonly XMLElementNodeName html = XMLElementNodeName.Create( "html" ); + public static readonly XMLElementNodeName head = XMLElementNodeName.Create( "head" ); + public static readonly XMLElementNodeName body = XMLElementNodeName.Create( "body" ); + public static readonly XMLElementNodeName br = XMLElementNodeName.Create( "br" ); + public static readonly XMLElementNodeName a = XMLElementNodeName.Create( "a" ); + public static readonly XMLElementNodeName style = XMLElementNodeName.Create( "style" ); + public static readonly XMLElementNodeName script = XMLElementNodeName.Create( "script" ); + } +} \ No newline at end of file diff --git a/Runtime/XML/Nodes/XMLAttributeNode.cs b/Runtime/XML/Nodes/XMLAttributeNode.cs new file mode 100644 index 0000000..3a7df07 --- /dev/null +++ b/Runtime/XML/Nodes/XMLAttributeNode.cs @@ -0,0 +1,51 @@ +using System.Collections; +using System.Collections.Generic; +using System.Text.RegularExpressions; + +namespace Rokojori +{ + public class XMLAttributeNode:XMLNode + { + public XMLAttributeNode( XMLDocument document, XMLElementNode parent, string name, string value, string nameSpace = null ):base( document, XMLNode.NodeType.Attribute ) + { + _name = name; + _nameSpace = nameSpace; + _value = value; + _parent = parent; + } + + XMLElementNode _parent; + string _name; + string _nameSpace; + string _value; + string _fullName; + + public string name => _name; + public string value => _value; + public string nameSpace => _nameSpace; + + public override string nodeValue => value; + + public string fullName + { + get + { + if ( _fullName != null ) + { + return _fullName; + } + + _fullName = _name; + + if ( _nameSpace != null ) + { + _fullName = _nameSpace + ":" + _name; + } + + + return _fullName; + } + } + + } +} \ No newline at end of file diff --git a/Runtime/XML/Nodes/XMLCDataSectionNode.cs b/Runtime/XML/Nodes/XMLCDataSectionNode.cs new file mode 100644 index 0000000..628a634 --- /dev/null +++ b/Runtime/XML/Nodes/XMLCDataSectionNode.cs @@ -0,0 +1,18 @@ +using System.Collections; +using System.Collections.Generic; +using System.Text.RegularExpressions; + +namespace Rokojori +{ + public class XMLCDataSectionNode:XMLNode + { + public XMLCDataSectionNode( XMLDocument document, string textContent ):base( document, XMLNode.NodeType.CDataSection ) + { + _textContent = textContent; + } + + string _textContent; + + public override string nodeValue => _textContent; + } +} \ No newline at end of file diff --git a/Runtime/XML/Nodes/XMLCommentNode.cs b/Runtime/XML/Nodes/XMLCommentNode.cs new file mode 100644 index 0000000..f6d31d7 --- /dev/null +++ b/Runtime/XML/Nodes/XMLCommentNode.cs @@ -0,0 +1,18 @@ +using System.Collections; +using System.Collections.Generic; +using System.Text.RegularExpressions; + +namespace Rokojori +{ + public class XMLCommentNode:XMLNode + { + public XMLCommentNode( XMLDocument document, string textContent ):base( document, XMLNode.NodeType.Comment ) + { + _textContent = textContent; + } + + string _textContent; + + public override string nodeValue => _textContent; + } +} \ No newline at end of file diff --git a/Runtime/XML/Nodes/XMLDocument.cs b/Runtime/XML/Nodes/XMLDocument.cs new file mode 100644 index 0000000..bbd9665 --- /dev/null +++ b/Runtime/XML/Nodes/XMLDocument.cs @@ -0,0 +1,107 @@ +using System.Collections; +using System.Collections.Generic; +using System.Text.RegularExpressions; + +namespace Rokojori +{ + public class XMLDocument:XMLNode + { + XMLElementNode _documentElement; + List _roots = new List(); + // public List roots => _roots; + + public void AppendChild( XMLNode node ) + { + _roots.Add( node ); + + if ( XMLNode.NodeType.Element == node.nodeType ) + { + _documentElement = (XMLElementNode) node; + } + + node._SetParent( this ); + } + + public int numRoots => _roots.Count; + + public XMLNode GetChild( int index ) + { + return _roots[ index ]; + } + + public XMLDocument():base( null, XMLNode.NodeType.Comment ) + { + _document = this; + } + + public XMLElementNode documentElement + { + get => _documentElement; + set + { + _documentElement = value; + } + } + + public string Serialize() + { + var serializer = new XMLSerializer(); + + return serializer.SerializeXMLNode( document ); + } + + + public static XMLDocument CreateHtmlDocument( bool addHeadAndBody = true ) + { + var htmlDocument = new XMLDocument(); + + htmlDocument._documentElement = HTMLElementName.html.Create( htmlDocument ); + htmlDocument._roots.Add( htmlDocument._documentElement ); + + if ( addHeadAndBody ) + { + htmlDocument._documentElement.AppendChild( HTMLElementName.head.Create( htmlDocument ) ); + htmlDocument._documentElement.AppendChild( HTMLElementName.body.Create( htmlDocument ) ); + } + + return htmlDocument; + } + + public XMLElementNode Create( XMLElementNodeName nodeName, string text = null ) + { + var element = nodeName.Create( this ); + + if ( text != null ) + { + element.AddText( text ); + } + + return element; + } + + public XMLTextNode CreateText( string text ) + { + return new XMLTextNode( this, text ); + } + + public XMLElementNode querySelector( XMLElementSelector selector ) + { + return XMLQuery.SelectElement( this, selector ); + } + + public XMLElementNode querySelector( string selector ) + { + return querySelector( XMLElementSelectors.From( selector ) ); + } + + public List querySelectorAll( XMLElementSelector selector ) + { + return XMLQuery.SelectAllElements( this, selector ); + } + + public List querySelectorAll( string selector ) + { + return querySelectorAll( XMLElementSelectors.From( selector ) ); + } + } +} \ No newline at end of file diff --git a/Runtime/XML/Nodes/XMLDocumentFragmentNode.cs b/Runtime/XML/Nodes/XMLDocumentFragmentNode.cs new file mode 100644 index 0000000..e298fb7 --- /dev/null +++ b/Runtime/XML/Nodes/XMLDocumentFragmentNode.cs @@ -0,0 +1,12 @@ +using System.Collections; +using System.Collections.Generic; +using System.Text.RegularExpressions; + +namespace Rokojori +{ + public class XMLDocumentFragmentNode:XMLNode + { + public XMLDocumentFragmentNode( XMLDocument document ):base( document, XMLNode.NodeType.DocumentFragment ) + {} + } +} \ No newline at end of file diff --git a/Runtime/XML/Nodes/XMLDocumentTypeNode.cs b/Runtime/XML/Nodes/XMLDocumentTypeNode.cs new file mode 100644 index 0000000..1b2d5a3 --- /dev/null +++ b/Runtime/XML/Nodes/XMLDocumentTypeNode.cs @@ -0,0 +1,18 @@ +using System.Collections; +using System.Collections.Generic; +using System.Text.RegularExpressions; + +namespace Rokojori +{ + public class XMLDocumentTypeNode:XMLNode + { + public XMLDocumentTypeNode( XMLDocument document, string textContent ):base( document, XMLNode.NodeType.DocumentType ) + { + _textContent = textContent; + } + + string _textContent; + + public override string nodeValue => _textContent; + } +} \ No newline at end of file diff --git a/Runtime/XML/Nodes/XMLElementNode.cs b/Runtime/XML/Nodes/XMLElementNode.cs new file mode 100644 index 0000000..da575b4 --- /dev/null +++ b/Runtime/XML/Nodes/XMLElementNode.cs @@ -0,0 +1,191 @@ +using System.Collections; +using System.Collections.Generic; +using System.Text.RegularExpressions; + +namespace Rokojori +{ + public class XMLElementNode:XMLNode + { + public XMLElementNode( XMLDocument document, string nodeName, string nameSpace = null ):base( document, XMLNode.NodeType.Element ) + { + _nodeName = nodeName; + _nameSpace = nameSpace; + } + + string _nodeName; + public string nodeName => _nodeName; + + string _nameSpace; + public string nameSpace => _nameSpace; + + List _children = new List(); + public int numChildren => _children.Count; + + public string fullNodeName + { + get + { + if ( _nameSpace == null ) + { + return _nodeName; + } + + return _nameSpace + ":" + _nodeName; + } + } + + public XMLNode GetChildAt( int index ) + { + return _children[ index ]; + } + + public override string ToString() + { + return GetInfo(); + } + + public override string GetInfo() + { + if ( _attributes.Count > 0 ) + { + var atts = ""; + + for ( int i = 0; i < _attributes.Count; i++ ) + { + if ( i != 0 ) + { + atts += " "; + } + + var value = _attributes[ i ].nodeValue; + + var attValue = _attributes[ i ].fullName; + + if ( value != null ) + { + attValue += "=\"" + XMLSerializer.Escape( value ) + "\""; + } + + atts += attValue; + } + + return "<" + fullNodeName + " " + atts + " >"; + + } + + return "<" + fullNodeName + ">"; + } + + 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( XMLNode node ) + { + var childIndex = _children.IndexOf( node ); + + if ( childIndex == -1 ) + { + return; + } + + node._SetParent( null ); + _children.RemoveAt( childIndex ); + } + + public void AppendChild( XMLNode node ) + { + if ( node.parentNode == this ) + { + return; + } + + if ( node.parentNode != null ) + { + var element = node.parentNode as XMLElementNode; + element.RemoveChild( node ); + } + + _children.Add( node ); + + node._SetParent( this ); + + } + + public XMLTextNode AddText( string text ) + { + var textNode = document.CreateText( text ); + AppendChild( textNode ); + + return textNode; + } + + public XMLElementNode AddHTMLScript( string script ) + { + return AddElement( HTMLElementName.script, script ); + } + + public XMLElementNode AddHTMLStyle( string style ) + { + return AddElement( HTMLElementName.style, style ); + } + + public XMLElementNode AddElement( XMLElementNodeName name, string text = null ) + { + var en = document.Create( name, text ); + AppendChild( en ); + return en; + } + + public void SetAttribute( string name, string value, string nameSpace = null ) + { + var att = new XMLAttributeNode( document, this, name, value, nameSpace ); + _attributes.Add( att ); + } + + + List _attributes = new List(); + + public int numAttributes => _attributes.Count; + + public XMLAttributeNode GetAttributeAt( int index ) + { + return _attributes[ index ]; + } + + public XMLAttributeNode GetAttribute( string name, string nameSpace = null ) + { + return _attributes.Find( a => a.name == name && a.nameSpace == nameSpace ); + } + + public XMLElementNode querySelector( XMLElementSelector selector ) + { + return XMLQuery.SelectElement( this, selector ); + } + + public XMLElementNode querySelector( string selector ) + { + return querySelector( XMLElementSelectors.From( selector ) ); + } + + public List querySelectorAll( XMLElementSelector selector ) + { + return XMLQuery.SelectAllElements( this, selector ); + } + + public List querySelectorAll( string selector ) + { + return querySelectorAll( XMLElementSelectors.From( selector ) ); + } + + } +} \ No newline at end of file diff --git a/Runtime/XML/Nodes/XMLProcessingInstructionNode.cs b/Runtime/XML/Nodes/XMLProcessingInstructionNode.cs new file mode 100644 index 0000000..842bce4 --- /dev/null +++ b/Runtime/XML/Nodes/XMLProcessingInstructionNode.cs @@ -0,0 +1,18 @@ +using System.Collections; +using System.Collections.Generic; +using System.Text.RegularExpressions; + +namespace Rokojori +{ + public class XMLProcessingInstructionNode:XMLNode + { + public XMLProcessingInstructionNode( XMLDocument document, string textContent ):base( document, XMLNode.NodeType.ProcessingInstruction ) + { + _declaration = textContent; + } + + string _declaration; + + public override string nodeValue => _declaration; + } +} \ No newline at end of file diff --git a/Runtime/Html/HtmlTextNode.cs b/Runtime/XML/Nodes/XMLTextNode.cs similarity index 63% rename from Runtime/Html/HtmlTextNode.cs rename to Runtime/XML/Nodes/XMLTextNode.cs index 3028c49..a015ca8 100644 --- a/Runtime/Html/HtmlTextNode.cs +++ b/Runtime/XML/Nodes/XMLTextNode.cs @@ -4,9 +4,9 @@ using System.Text.RegularExpressions; namespace Rokojori { - public class HtmlTextNode:HtmlNode + public class XMLTextNode:XMLNode { - public HtmlTextNode( HtmlDocument document, string textContent ):base( document, HtmlNode.NodeType.Text ) + public XMLTextNode( XMLDocument document, string textContent ):base( document, XMLNode.NodeType.Text ) { _textContent = textContent; } diff --git a/Runtime/XML/SVG/Arc-Test.svg b/Runtime/XML/SVG/Arc-Test.svg new file mode 100644 index 0000000..6484473 --- /dev/null +++ b/Runtime/XML/SVG/Arc-Test.svg @@ -0,0 +1,68 @@ + + + + diff --git a/Runtime/XML/SVG/Arc-Test.svg.import b/Runtime/XML/SVG/Arc-Test.svg.import new file mode 100644 index 0000000..d67adc8 --- /dev/null +++ b/Runtime/XML/SVG/Arc-Test.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dl0a70xdmboor" +path="res://.godot/imported/Arc-Test.svg-26823712ea0acab6c0061bccaa916650.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://Scripts/Rokojori/Rokojori-Action-Library/Runtime/XML/SVG/Arc-Test.svg" +dest_files=["res://.godot/imported/Arc-Test.svg-26823712ea0acab6c0061bccaa916650.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 diff --git a/Runtime/XML/SVG/SVGAttributeName.cs b/Runtime/XML/SVG/SVGAttributeName.cs new file mode 100644 index 0000000..7257aa0 --- /dev/null +++ b/Runtime/XML/SVG/SVGAttributeName.cs @@ -0,0 +1,11 @@ +using System.Collections; +using System.Collections.Generic; +using System.Text.RegularExpressions; + +namespace Rokojori +{ + public class SVGAttributeName + { + public static readonly XMLAttributeName d = XMLAttributeName.Create( "d" ); + } +} \ No newline at end of file diff --git a/Runtime/XML/SVG/SVGElementName.cs b/Runtime/XML/SVG/SVGElementName.cs new file mode 100644 index 0000000..9f2c0b9 --- /dev/null +++ b/Runtime/XML/SVG/SVGElementName.cs @@ -0,0 +1,15 @@ +using System.Collections; +using System.Collections.Generic; +using System.Text.RegularExpressions; + +namespace Rokojori +{ + public class SVGElementName + { + public static readonly XMLElementNodeName svg = XMLElementNodeName.Create( "svg" ); + public static readonly XMLElementNodeName g = XMLElementNodeName.Create( "g" ); + public static readonly XMLElementNodeName rect = XMLElementNodeName.Create( "rect" ); + public static readonly XMLElementNodeName path = XMLElementNodeName.Create( "path" ); + + } +} \ No newline at end of file diff --git a/Runtime/XML/SVG/SVGPathCommand.cs b/Runtime/XML/SVG/SVGPathCommand.cs new file mode 100644 index 0000000..8a82492 --- /dev/null +++ b/Runtime/XML/SVG/SVGPathCommand.cs @@ -0,0 +1,57 @@ +using System.Collections; +using System.Collections.Generic; +using System.Text.RegularExpressions; + +namespace Rokojori +{ + public class SVGPathCommand + { + public string type; + public List paramaters = new List(); + + public override string ToString() + { + if ( type == "z" || type == "Z" ) + { + if ( paramaters.Count > 0 ) + { + return "Invalid! " + type + " " + Lists.Join( paramaters, " " ); + } + + return type; + } + + var pLength = GetParameterLengthForCommand( type ); + var blocks = paramaters.Count / pLength; + var valid = blocks * pLength == paramaters.Count; + + if ( ! valid ) + { + return "Invalid! " + type + " " + Lists.Join( paramaters, " " ); + } + + return type + "(" + blocks + ") " + Lists.Join( paramaters, " " ); + } + + public static int GetParameterLengthForCommand( string commandType ) + { + var ct = commandType.ToLower(); + + switch ( ct ) + { + case "z": return 0; + + case "h": case "v" : return 1; + + case "s": case "q": return 4; + + case "c": return 6; + + case "a": return 7; + } + + + return 2; + } + } +} \ No newline at end of file diff --git a/Runtime/XML/SVG/SVGPathExtractor.cs b/Runtime/XML/SVG/SVGPathExtractor.cs new file mode 100644 index 0000000..f536d10 --- /dev/null +++ b/Runtime/XML/SVG/SVGPathExtractor.cs @@ -0,0 +1,396 @@ +using Godot; +using System; +using System.Collections.Generic; + +namespace Rokojori +{ + public enum SVGPathInstructionType + { + MoveTo, + LineTo, + QuadraticBezierTo, + CubicBezierTo, + ArcTo, + Close + } + + public class SVGPathInstruction + { + public SVGPathInstructionType type = SVGPathInstructionType.MoveTo; + public Vector2 startPoint = Vector2.Zero; + public Vector2 controlPoint1 = Vector2.Zero; + public Vector2 controlPoint2 = Vector2.Zero; + public Vector2 endPoint = Vector2.Zero; + public Vector2 radius = Vector2.Zero; + public float angle = 0; + public bool largeArcFlag = false; + public bool sweepFlag = false; + + public override string ToString() + { + return GetInfo(); + } + + public string GetInfo() + { + var infos = new List(); + infos.Add( type ); + + if ( SVGPathInstructionType.MoveTo == type ) + { + infos.Add( endPoint ); + } + else if ( SVGPathInstructionType.LineTo == type ) + { + infos.Add( startPoint ); + infos.Add( endPoint ); + } + else if ( SVGPathInstructionType.QuadraticBezierTo == type ) + { + infos.Add( startPoint ); + infos.Add( controlPoint1 ); + infos.Add( endPoint ); + } + else if ( SVGPathInstructionType.CubicBezierTo == type ) + { + infos.Add( startPoint ); + infos.Add( controlPoint1 ); + infos.Add( controlPoint2 ); + infos.Add( endPoint ); + } + else if ( SVGPathInstructionType.ArcTo == type ) + { + infos.Add( startPoint ); + infos.Add( radius ); + infos.Add( angle ); + infos.Add( largeArcFlag ); + infos.Add( sweepFlag ); + infos.Add( endPoint ); + } + + return RJLog.GetLogString( infos.ToArray() ); + } + } + + public class SVGPathExtractor + { + Vector2 currentPoint = new Vector2(); + Vector2 lastControlPoint = new Vector2(); + Vector2 startPoint = new Vector2(); + SVGPathInstruction instruction = new SVGPathInstruction(); + public readonly EventSlot onInstruction = new EventSlot(); + + void DispatchInstruction() + { + onInstruction.DispatchEvent( instruction ); + } + + public void Process( List commands ) + { + commands.ForEach( c => ProcessNext( c ) ); + } + + public void ProcessNext( SVGPathCommand command ) + { + switch ( command.type) + { + case "m": case "M": + { + ProcessMoveTo( command ); + } + break; + + case "l": case "L": + { + ProcessLineTo( command ); + } + break; + + case "v": case "V": + { + ProcessVerticalTo( command ); + } + break; + + case "h": case "H": + { + ProcessHorizonatlTo( command ); + } + break; + + case "c": case "C": + { + ProcessCubicTo( command ); + } + break; + + case "q": case "Q": + { + ProcessQuadraticTo( command ); + } + break; + + case "a": case "A": + { + ProcessArcTo( command ); + } + break; + + case "z": case "Z": + { + ProcessClose(); + } + break; + + + } + } + + void ProcessClose() + { + instruction.type = SVGPathInstructionType.Close; + instruction.startPoint = currentPoint; + instruction.endPoint = startPoint; + } + + void ProcessMoveTo( SVGPathCommand command ) + { + var relative = command.type == "m"; + var parameters = command.paramaters; + + for ( int i = 0; i < parameters.Count; i+= 2 ) + { + ProcessToEndpoint( relative, SVGPathInstructionType.MoveTo, parameters[ i ], parameters[ i + 1 ] ); + + if ( i == 0 ) + { + startPoint = currentPoint; + } + } + } + + void ProcessLineTo( SVGPathCommand command ) + { + var relative = command.type == "l"; + var parameters = command.paramaters; + + for ( int i = 0; i < parameters.Count; i+= 2 ) + { + ProcessToEndpoint( relative, SVGPathInstructionType.LineTo, parameters[ i ], parameters[ i + 1 ] ); + } + } + + void ProcessVerticalTo( SVGPathCommand command ) + { + var relative = command.type == "v"; + var parameters = command.paramaters; + + for ( int i = 0; i < parameters.Count; i++ ) + { + var x = relative ? 0 : currentPoint.X; + ProcessToEndpoint( relative, SVGPathInstructionType.LineTo, x, parameters[ i ] ); + } + } + + void ProcessHorizonatlTo( SVGPathCommand command ) + { + var relative = command.type == "v"; + var parameters = command.paramaters; + + for ( int i = 0; i < parameters.Count; i++ ) + { + var y = relative ? 0 : currentPoint.Y; + ProcessToEndpoint( relative, SVGPathInstructionType.LineTo, parameters[ i ], y ); + } + } + + void ProcessArcTo( SVGPathCommand command ) + { + var relative = command.type == "a"; + var parameters = command.paramaters; + + for ( int i = 0; i < parameters.Count; i+= 7 ) + { + ProcessArc( relative, + parameters[ i ], parameters[ i + 1 ], + parameters[ i + 2 ], parameters[ i + 3 ], + parameters[ i + 4 ], parameters[ i + 5 ], + parameters[ i + 7 ] + ); + } + } + + void ProcessArc( bool relative, float rx, float ry, float angle, float largeArc, float sweepArc, float x, float y ) + { + instruction.type = SVGPathInstructionType.ArcTo; + + instruction.startPoint = currentPoint; + + instruction.radius.X = rx; + instruction.radius.Y = ry; + instruction.angle = angle; + + instruction.largeArcFlag = largeArc > 0; + instruction.sweepFlag = sweepArc > 0; + + + + instruction.endPoint.X = x; + instruction.endPoint.Y = y; + + + if ( relative ) + { + instruction.endPoint += currentPoint; + } + + currentPoint = instruction.endPoint; + DispatchInstruction(); + } + + void ProcessCubicTo( SVGPathCommand command ) + { + var relative = command.type == "c"; + var parameters = command.paramaters; + + for ( int i = 0; i < parameters.Count; i+= 6 ) + { + ProcessCubic( relative, + parameters[ i ], parameters[ i + 1 ], + parameters[ i + 2 ], parameters[ i + 3 ], + parameters[ i + 4 ], parameters[ i + 5 ] + ); + } + } + + void ProcessSCubicTo( SVGPathCommand command ) + { + var relative = command.type == "s"; + var parameters = command.paramaters; + + for ( int i = 0; i < parameters.Count; i+= 4 ) + { + var cpDiff = currentPoint - lastControlPoint; + var cpNext = relative ? cpDiff : ( currentPoint + cpDiff ); + + ProcessCubic( relative, + cpNext.X, cpNext.Y, + parameters[ i ], parameters[ i + 1 ], + parameters[ i + 2 ], parameters[ i + 3 ] + ); + } + } + + void ProcessCubic( bool relative, float cx1, float cy1, float cx2, float cy2, float x, float y ) + { + instruction.type = SVGPathInstructionType.CubicBezierTo; + + instruction.startPoint = currentPoint; + + instruction.controlPoint1.X = cx1; + instruction.controlPoint1.Y = cy1; + + instruction.controlPoint2.X = cx2; + instruction.controlPoint2.Y = cy2; + + instruction.endPoint.X = x; + instruction.endPoint.Y = y; + + lastControlPoint = instruction.controlPoint2; + + if ( relative ) + { + instruction.controlPoint1 += currentPoint; + instruction.controlPoint2 += currentPoint; + instruction.endPoint += currentPoint; + } + + currentPoint = instruction.endPoint; + DispatchInstruction(); + } + + + void ProcessQuadraticTo( SVGPathCommand command ) + { + var relative = command.type == "q"; + var parameters = command.paramaters; + + for ( int i = 0; i < parameters.Count; i+= 4 ) + { + + ProcessQuadratic( relative, + parameters[ i ], parameters[ i + 1 ], + parameters[ i + 2 ], parameters[ i + 3 ] + ); + } + } + + void ProcessTQuadraticTo( SVGPathCommand command ) + { + var relative = command.type == "t"; + var parameters = command.paramaters; + + for ( int i = 0; i < parameters.Count; i+= 2 ) + { + var cpDiff = currentPoint - lastControlPoint; + var cpNext = relative ? cpDiff : ( currentPoint + cpDiff ); + + ProcessQuadratic( relative, + cpNext.X, cpNext.Y, + parameters[ i ], parameters[ i + 1 ] + ); + } + } + + void ProcessQuadratic( bool relative, float cx1, float cy1, float x, float y ) + { + instruction.type = SVGPathInstructionType.QuadraticBezierTo; + + instruction.startPoint = currentPoint; + + instruction.controlPoint1.X = cx1; + instruction.controlPoint1.Y = cy1; + + instruction.endPoint.X = x; + instruction.endPoint.Y = y; + + + lastControlPoint = instruction.controlPoint1; + + if ( relative ) + { + instruction.controlPoint1 += currentPoint; + instruction.endPoint += currentPoint; + } + + currentPoint = instruction.endPoint; + DispatchInstruction(); + } + + + void ProcessToEndpoint( bool relative, SVGPathInstructionType type, float x, float y ) + { + instruction.type = type; + + instruction.startPoint = currentPoint; + + instruction.endPoint.X = x; + instruction.endPoint.Y = y; + + if ( relative ) + { + instruction.endPoint += currentPoint; + } + + currentPoint = instruction.endPoint; + DispatchInstruction(); + + } + + + + + + + } +} \ No newline at end of file diff --git a/Runtime/XML/SVG/SVGPathParser.cs b/Runtime/XML/SVG/SVGPathParser.cs new file mode 100644 index 0000000..d50ed3b --- /dev/null +++ b/Runtime/XML/SVG/SVGPathParser.cs @@ -0,0 +1,164 @@ +using System.Collections; +using System.Collections.Generic; +using System.Text.RegularExpressions; + +namespace Rokojori +{ + public class SVGPathParser + { + public static readonly string SVGPathCommands = "mlhvcsqtaz"; + + List commands = new List(); + List messages = new List(); + + public static List Parse( string d ) + { + var svgPath = new SVGPathParser(); + var commands = svgPath.ParseCommands( d ); + + if ( Messages.HasError( svgPath.messages ) ) + { + return null; + } + + return commands; + } + + public static List GetMessages( string d ) + { + var svgPath = new SVGPathParser(); + var commands = svgPath.ParseCommands( d ); + + return svgPath.messages; + } + + public List ParseCommands( string d ) + { + for ( int i = 0; i < d.Length; i++ ) + { + if ( d[ i ] == ' ' ) + { + continue; + } + + var lowerCase = ( d[ i ] + "" ).ToLower(); + + var commandIndex = SVGPathCommands.IndexOf( lowerCase ); + + if ( commandIndex == -1 ) + { + Messages.Error( messages, "Unknown command at '" + i + "'. Found " + d[ i ] ); + return null; + } + + var commandEnd = ProcessCommand( d, i ); + + if ( commandEnd == -1 || Messages.HasError( messages ) ) + { + Messages.Error( messages, "Command couldn't be processed at '" + i + "'. Processed " + d[ i ] ); + return null; + } + + i = commandEnd; + } + + return commands; + } + + int ProcessCommand( string d, int offset) + { + var end = FindEnd( d, offset + 1 ); + + var pc = new SVGPathCommand(); + pc.type = d[ offset ] + ""; + + ReadParameters( pc, d, offset + 1, end ); + + commands.Add( pc ); + + return end; + } + + int FindEnd( string d, int offset ) + { + for ( int i = offset; i < d.Length; i++ ) + { + var chr = ( d[ i ] + "" ).ToLower(); + + if ( SVGPathParser.SVGPathCommands.IndexOf( chr ) != - 1 ) + { + return i - 1; + } + } + + return d.Length - 1; + } + + void ReadParameters( SVGPathCommand command, string d, int start, int end ) + { + var matcher = LexerMatcherLibrary.NumberMatcher; + var offset = start; + var isNegative = false; + + while ( offset <= end ) + { + if ( d[ offset ] == ' ' || d[ offset ] == ',' ) + { + offset ++; + isNegative = false; + continue; + } + + if ( d[ offset ] == '-' ) + { + offset ++; + isNegative = true; + continue; + } + + + var matchLength = matcher.MatchLength( d, offset ); + + if ( matchLength == -1 ) + { + Messages.Error( messages, + "Found no number." + + " Offset:" + offset + + " Start: " + start + + " End: " + end + + " Part: " + "'" + d.Substring( start, end - start ) + "'" + ); + return; + } + + var endIndex = offset + matchLength; + + if ( endIndex > end ) + { + Messages.Error( messages, + "Number parsing exceeded expected end." + + " Offset:" + offset + + " Start: " + start + + " End: " + end + + " Match End: " + endIndex + + " Part: " + "'" + d.Substring( start, end - start ) + "'" + ); + return; + } + + var number = RegexUtility.ParseFloat( d.Substring( offset, matchLength ) ); + + if ( isNegative ) + { + number = -number; + } + + offset += matchLength; + + command.paramaters.Add( number ); + + isNegative = false; + } + } + } +} \ No newline at end of file diff --git a/Runtime/XML/XMLAttributeName.cs b/Runtime/XML/XMLAttributeName.cs new file mode 100644 index 0000000..85adaa9 --- /dev/null +++ b/Runtime/XML/XMLAttributeName.cs @@ -0,0 +1,64 @@ +using System.Collections; +using System.Collections.Generic; +using System.Text.RegularExpressions; + +namespace Rokojori +{ + public class XMLAttributeName:XMLElementSelector + { + string _attributeName; + string _nameSpace; + + string _fullName; + + public string fullName + { + get + { + if ( _fullName != null ) + { + return _fullName; + } + + _fullName = _attributeName; + + if ( _nameSpace != null ) + { + _fullName = _nameSpace + ":" + _attributeName; + } + + return _fullName; + } + } + + public string selector => "[" + fullName + "]"; + + public static XMLAttributeName Create( string type, string nameSpace = null ) + { + var elementNodeType = new XMLAttributeName(); + elementNodeType._attributeName = type; + elementNodeType._nameSpace = nameSpace; + + return elementNodeType; + } + + public XMLAttributeNode GetAttribute( XMLElementNode elementNode ) + { + return elementNode.GetAttribute( _attributeName, _nameSpace ); + } + + public string Get( XMLElementNode elementNode ) + { + var atttribute = GetAttribute( elementNode ); + return atttribute == null ? null : atttribute.nodeValue; + } + + + public bool Selects( XMLElementNode elementNode ) + { + return elementNode.GetAttribute( _attributeName, _nameSpace ) != null; + } + + + } +} \ No newline at end of file diff --git a/Runtime/XML/XMLElementNodeName.cs b/Runtime/XML/XMLElementNodeName.cs new file mode 100644 index 0000000..8b90f1b --- /dev/null +++ b/Runtime/XML/XMLElementNodeName.cs @@ -0,0 +1,57 @@ +using System.Collections; +using System.Collections.Generic; +using System.Text.RegularExpressions; + +namespace Rokojori +{ + public class XMLElementNodeName:XMLElementSelector + { + string _nodeName; + string _nameSpace; + + string _fullName; + + public string fullName + { + get + { + if ( _fullName != null ) + { + return _fullName; + } + + _fullName = _nodeName; + + if ( _nameSpace != null ) + { + _fullName = _nameSpace + ":" + _nodeName; + } + + return _fullName; + } + } + + public string selector => fullName; + + public static XMLElementNodeName Create( string type, string nameSpace = null ) + { + var elementNodeType = new XMLElementNodeName(); + elementNodeType._nodeName = type; + elementNodeType._nameSpace = nameSpace; + + return elementNodeType; + } + + public XMLElementNode Create( XMLDocument document ) + { + return new XMLElementNode( document, _nodeName ); + } + + public bool Selects( XMLElementNode elementNode ) + { + return elementNode.nodeName == _nodeName && elementNode.nameSpace == _nameSpace; + } + + + } +} \ No newline at end of file diff --git a/Runtime/XML/XMLElementSelector.cs b/Runtime/XML/XMLElementSelector.cs new file mode 100644 index 0000000..07c0efc --- /dev/null +++ b/Runtime/XML/XMLElementSelector.cs @@ -0,0 +1,22 @@ +using System.Collections; +using System.Collections.Generic; +using System.Text.RegularExpressions; +using System.Text; + +namespace Rokojori +{ + public interface XMLElementSelector + { + bool Selects( XMLElementNode elementNode ); + + string selector { get; } + } + + public class XMLElementSelectors + { + public static XMLElementSelector From( string selector ) + { + return XMLElementNodeName.Create( selector ); + } + } +} \ No newline at end of file diff --git a/Runtime/Html/HtmlNode.cs b/Runtime/XML/XMLNode.cs similarity index 57% rename from Runtime/Html/HtmlNode.cs rename to Runtime/XML/XMLNode.cs index 2d51a5c..b637838 100644 --- a/Runtime/Html/HtmlNode.cs +++ b/Runtime/XML/XMLNode.cs @@ -4,7 +4,7 @@ using System.Text.RegularExpressions; namespace Rokojori { - public abstract class HtmlNode + public abstract class XMLNode { public enum NodeType { @@ -22,13 +22,13 @@ namespace Rokojori NodeType _nodeType; public NodeType nodeType => _nodeType; - HtmlDocument _document; - public HtmlDocument document => _document; + protected XMLDocument _document; + public XMLDocument document => _document; - protected HtmlNode _parentNode; - public HtmlNode parentNode => _parentNode; - public HtmlElementNode parentElement => _parentNode as HtmlElementNode; - public void _SetParent( HtmlNode node ) + protected XMLNode _parentNode; + public XMLNode parentNode => _parentNode; + public XMLElementNode parentElement => _parentNode as XMLElementNode; + public void _SetParent( XMLNode node ) { _parentNode = node; } @@ -36,11 +36,16 @@ namespace Rokojori public virtual string nodeValue => null; public virtual string textContent => ""; - public HtmlNode( HtmlDocument document, NodeType type ) + public virtual string GetInfo() + { + return nodeType + ":" + nodeValue; + } + + public XMLNode( XMLDocument document, NodeType type ) { _document = document; _nodeType = type; - } + } } } \ No newline at end of file diff --git a/Runtime/XML/XMLQuery.cs b/Runtime/XML/XMLQuery.cs new file mode 100644 index 0000000..88ce49e --- /dev/null +++ b/Runtime/XML/XMLQuery.cs @@ -0,0 +1,58 @@ +using System.Collections; +using System.Collections.Generic; +using System.Text.RegularExpressions; +using Godot; + +namespace Rokojori +{ + public class XMLQuery + { + public static XMLElementNode SelectElement( XMLNode node, XMLElementSelector selector ) + { + var element = XMLWalker.instance.Find( node, + n => + { + var element = n as XMLElementNode; + + if ( element == null ) + { + return false; + } + + return selector.Selects( element ); + }, + + false + ); + + return element == null ? null : ( element as XMLElementNode ); + } + + public static List SelectAllElements( XMLNode node, XMLElementSelector selector ) + { + var list = new List(); + + XMLWalker.instance.Iterate( node, + n => + { + var element = n as XMLElementNode; + + if ( element == null ) + { + return; + } + + if ( selector.Selects( element ) ) + { + list.Add( element ); + } + }, + + false + ); + + return list; + } + } + +} \ No newline at end of file diff --git a/Runtime/XML/XMLReader.cs b/Runtime/XML/XMLReader.cs new file mode 100644 index 0000000..e13efe0 --- /dev/null +++ b/Runtime/XML/XMLReader.cs @@ -0,0 +1,238 @@ + using System.Collections; + using System.Collections.Generic; + using System.Text.RegularExpressions; + using System.Xml; + + namespace Rokojori + { + public class XMLReader + { + XMLLexer lexer = new XMLLexer(); + TextLinesMapper linesMapper = new TextLinesMapper(); + List events; + XMLDocument document; + XMLNode node; + + string text; + + public XMLDocument Read( string text ) + { + this.text = text; + events = lexer.LexToList( text ); + + if ( lexer.hasError ) + { + linesMapper.Map( text ); + + var error = events.Find( e => e.isError ); + var line = linesMapper.GetLine( error.offset ); + + var errorLine = ""; + var lineCharacterIndex = error.offset - line.contentOffset; + + for ( int i = 0; i < lineCharacterIndex; i++ ) + { + errorLine += " "; + } + + errorLine += "~"; + + RJLog.Error( line.GetContent( text ) + "\n" + errorLine ); + + return null; + + } + + CreateDocument(); + + RJLog.Log( document.Serialize() ); + + return document; + } + + List insideTagElements = new List(); + + void CreateDocument() + { + document = new XMLDocument(); + + node = document; + + lexer.GrabMatches( events, text ); + + + + events.ForEach( + ( e )=> + { + if ( e.isDone ) + { + return; + } + + var type = e.type; + + //RJLog.Log( "Token ", "m:", e.mode, "t:", e.type, ">>", e.match ); + + if ( XMLLexer.Inside_Start_Tag == e.mode ) + { + if ( XMLLexer.XMLStartTagClosing.Matches( e ) ) + { + SetElementAttributes(); + } + else + { + insideTagElements.Add( e ); + } + } + else if ( XMLLexer.XMLProcessingInstruction.Matches( e ) ) + { + var instruction = new XMLProcessingInstructionNode( document, e.match ); + Add( instruction ); + } + else if ( XMLLexer.XMLInstruction.Matches( e ) ) + { + var instruction = new XMLProcessingInstructionNode( document, e.match ); + Add( instruction ); + } + else if ( XMLLexer.XMLComment.Matches( e ) ) + { + var comment = new XMLCommentNode( document, e.match ); + Add( comment ); + } + else if ( XMLLexer.XMLText.Matches( e ) || + LexerMatcherLibrary.WhiteSpaceMatcher.Matches( e ) || + LexerMatcherLibrary.BreakMatcher.Matches( e ) ) + { + var unescapedMatch = XMLSerializer.Unescape( e.match ); + var element = new XMLTextNode( document, unescapedMatch ); + Add( element ); + } + else if ( XMLLexer.XMLStartTag.Matches( e ) ) + { + insideTagElements.Clear(); + + var regexMatch = XMLLexer.XMLStartTag.GetRegexMatches( e ); + + var ns = regexMatch.Group( 1 ); + var elementName = regexMatch.Group( 2 ); + + var element = new XMLElementNode( document, elementName, ns ); + Add( element ); + + var parentName = node == document ? "document" : ((XMLElementNode)node).fullNodeName; + RJLog.Log( "Adding Element", element.fullNodeName, parentName ); + node = element; + + } + else if ( XMLLexer.XMLEndTag.Matches( e ) ) + { + var el = (XMLElementNode) node; + node = el.parentNode; + } + + } + + ); + } + + void SetElementAttributes() + { + var element = (XMLElementNode) node; + + var infos = Lists.Join( Lists.Map( insideTagElements, i => i.match ), "" ); + RJLog.Log( "Set Attributes", element.fullNodeName, insideTagElements.Count, infos ); + + var attributeNameIndices = Lists.CollectIndices( insideTagElements, le => XMLLexer.XMLAttributeName.Matches( le ) ); + + attributeNameIndices.ForEach( + ( attIndex )=> + { + var valueIndex = FindAttributeValue( attIndex + 1 ); + var value = ""; + + if ( valueIndex != -1 ) + { + var stringMatch = insideTagElements[ valueIndex ].match; + value = stringMatch.Substring( 1, stringMatch.Length - 2 ); + value = XMLSerializer.Unescape( value ); + } + + var regexMatch = XMLLexer.XMLAttributeName.GetRegexMatches( insideTagElements[ attIndex ] ); + + var nameSpace = regexMatch.Group( 1 ); + var attributeName = regexMatch.Group( 2 ); + + // RJLog.Log( "Attribute", insideTagElements[ attIndex ].match, regexMatch.regexMatch, nameSpace, attributeName ); + + element.SetAttribute( attributeName, value, nameSpace ); + } + ); + + insideTagElements.Clear(); + } + + int FindAttributeValue( int index ) + { + var hasAssignment = false; + + for ( int i = index; i < insideTagElements.Count; i++ ) + { + if ( XMLLexer.XMLAttributeName.Matches( insideTagElements[ i ] ) ) + { + return -1; + } + + if ( XMLLexer.XMLAttributeAssignment.Matches( insideTagElements[ i ] ) ) + { + if ( hasAssignment ) + { + return -1; + } + + hasAssignment = true; + } + + if ( XMLLexer.XMLAttributeAssignment.Matches( insideTagElements[ i ] ) ) + { + hasAssignment = true; + } + + if ( ! hasAssignment ) + { + continue; + } + + if ( + XMLLexer.InsideStartTag_SingleQuotedStringMatcher.Matches( insideTagElements[ i ] ) || + XMLLexer.InsideStartTag_DoubleQuotedStringMatcher.Matches( insideTagElements[ i ] ) + ) + { + return i; + } + } + + return -1; + } + + void Add( XMLNode childNode ) + { + if ( node == document ) + { + document.AppendChild( childNode ); + + // RJLog.Log( "Adding to Doc", childNode.GetInfo(), "is doc element:", document.documentElement == childNode ); + } + else if ( XMLNode.NodeType.Element == node.nodeType ) + { + var elementNode = (XMLElementNode) node; + elementNode.AppendChild( childNode ); + } + else + { + RJLog.Log( "Unexpected parent" ); + } + } + + } + } \ No newline at end of file diff --git a/Runtime/XML/XMLReaderTest.cs b/Runtime/XML/XMLReaderTest.cs new file mode 100644 index 0000000..6b4cc4c --- /dev/null +++ b/Runtime/XML/XMLReaderTest.cs @@ -0,0 +1,85 @@ +using System.Collections; +using System.Collections.Generic; +using System.Text.RegularExpressions; +using Godot; + +namespace Rokojori +{ + [Tool] + [GlobalClass] + public partial class XMLReaderTest:Node + { + [Export] + public string path; + + [Export] + public bool loadFile; + + public override void _Process( double delta ) + { + if ( ! loadFile ) + { + return; + } + + Load(); + } + + void Load() + { + loadFile = false; + + var text = FilesSync.LoadUTF8( path ); + var reader = new XMLReader(); + + var doc = reader.Read( text ); + + var paths = doc.querySelectorAll( SVGElementName.path ); + + RJLog.Log( "paths:", paths.Count ); + + paths.ForEach( + ( p ) => + { + var pathData = SVGAttributeName.d.Get( p ); + + if ( pathData == null ) + { + RJLog.Log( "No path data found:", p ); + return; + } + + RJLog.Log( "pathData:", pathData ); + var commands = SVGPathParser.Parse( pathData ); + + + if ( commands != null ) + { + RJLog.Log( "Commands:", commands.Count, "\n" + Lists.Join( commands, "\n" ) ); + + + var extractor = new SVGPathExtractor(); + extractor.onInstruction.AddAction( + ( si )=> + { + RJLog.Log( si ); + } + ); + extractor.Process( commands ); + + + } + else + { + var messages = SVGPathParser.GetMessages( pathData ); + RJLog.Log( "Commands failed parsing:", Lists.Join( messages, "\n" ) ); + } + + + + } + ); + + } + } +} \ No newline at end of file diff --git a/Runtime/Html/HtmlSerializer.cs b/Runtime/XML/XMLSerializer.cs similarity index 62% rename from Runtime/Html/HtmlSerializer.cs rename to Runtime/XML/XMLSerializer.cs index c70dfa1..97c70f9 100644 --- a/Runtime/Html/HtmlSerializer.cs +++ b/Runtime/XML/XMLSerializer.cs @@ -5,15 +5,14 @@ using System.Text; namespace Rokojori { - public class HtmlSerializer - { - + public class XMLSerializer + { StringBuilder sb = new StringBuilder(); - HtmlWalker walker = new HtmlWalker(); + XMLWalker walker = new XMLWalker(); string indent = " "; - Dictionary depthMap = new Dictionary(); + Dictionary depthMap = new Dictionary(); Dictionary indentMap = new Dictionary(); - List stack = new List(); + List stack = new List(); public static string Escape( string rawText ) @@ -27,10 +26,19 @@ namespace Rokojori return rawText; } - public string Serialize( HtmlNode node ) + public static string Unescape( string escapedText ) + { + escapedText = RegexUtility.Replace( escapedText, "&", "&" ); + escapedText = RegexUtility.Replace( escapedText, "<", "<" ); + escapedText = RegexUtility.Replace( escapedText, ">,", ">" ); + escapedText = RegexUtility.Replace( escapedText, "'", "\'" ); + escapedText = RegexUtility.Replace( escapedText, ""","\"" ); + + return escapedText; + } + + public string SerializeXMLNode( XMLNode node ) { - - sb.Append( "" ); walker.DepthIterate( node, ( n, d ) => @@ -38,21 +46,23 @@ namespace Rokojori var depth = GetDepth( n ); ClosePreviousElements( depth ); - var element = n as HtmlElementNode; + var element = n as XMLElementNode; + + RJLog.Log( "Processing:", n ); if ( element != null ) { sb.Append( "\n" ); sb.Append( GetIndent( depth ) ); - sb.Append( "<" + element.nodeName ); + sb.Append( "<" + element.fullNodeName ); for ( int i = 0; i < element.numAttributes; i++ ) { var attribute = element.GetAttributeAt( i ); sb.Append( " " ); - sb.Append( attribute.name ); + sb.Append( attribute.fullName ); sb.Append( "=\"" ); sb.Append( Escape( attribute.value ) ); sb.Append( "\""); @@ -65,16 +75,31 @@ namespace Rokojori } else { - if ( - HtmlElementNodeName.style.Selects( n.parentElement ) || - HtmlElementNodeName.script.Selects( n.parentElement ) + if ( n.parentElement != null && ( + HTMLElementName.style.Selects( n.parentElement ) || + HTMLElementName.script.Selects( n.parentElement ) ) ) { sb.Append( n.nodeValue ); } else { - sb.Append( Escape( n.nodeValue ) ); + var nodeValue = n.nodeValue; + + if ( nodeValue != null ) + { + if ( XMLNode.NodeType.Text == n.nodeType ) + { + sb.Append( Escape( n.nodeValue ) ); + + } + else + { + sb.Append( n.nodeValue ); + } + + } + } } @@ -88,7 +113,12 @@ namespace Rokojori ClosePreviousElements( -1 ); return sb.ToString(); + } + public string SerializeHtml( XMLNode node ) + { + sb.Append( "" ); + return SerializeXMLNode( node ); } string GetIndent( int depth ) @@ -124,7 +154,7 @@ namespace Rokojori } - int GetDepth( HtmlNode n ) + int GetDepth( XMLNode n ) { return walker.GetDepth( n, depthMap ); } @@ -146,7 +176,7 @@ namespace Rokojori sb.Append( GetIndent( stackDepth ) ); } - sb.Append( "" ); + sb.Append( "" ); } else { @@ -155,13 +185,13 @@ namespace Rokojori } } - bool NeedsLineBreak( HtmlElementNode elementNode ) + bool NeedsLineBreak( XMLElementNode elementNode ) { if ( elementNode.numChildren == 0 || elementNode.HasOnlyTextNodes() && - ! ( HtmlElementNodeName.script.Selects( elementNode ) || - HtmlElementNodeName.style.Selects( elementNode ) + ! ( HTMLElementName.script.Selects( elementNode ) || + HTMLElementName.style.Selects( elementNode ) ) ) { diff --git a/Runtime/XML/XMLWalker.cs b/Runtime/XML/XMLWalker.cs new file mode 100644 index 0000000..b61ecf7 --- /dev/null +++ b/Runtime/XML/XMLWalker.cs @@ -0,0 +1,52 @@ +using System.Collections; +using System.Collections.Generic; +using System.Text.RegularExpressions; + +namespace Rokojori +{ + public class XMLWalker: TreeWalker + { + private static XMLWalker _instance = new XMLWalker(); + public static XMLWalker instance => _instance; + + public override int NumChildren( XMLNode node ) + { + if ( node is XMLDocument ) + { + return ( (XMLDocument ) node ).numRoots; + } + + var elementNode = node as XMLElementNode; + + if ( elementNode != null ) + { + return elementNode.numChildren; + } + + return 0; + } + + public override XMLNode ChildAt( XMLNode node, int index ) + { + if ( node is XMLDocument ) + { + return ( (XMLDocument)node).GetChild( index ); + } + + var elementNode = node as XMLElementNode; + + if ( elementNode != null ) + { + return elementNode.GetChildAt( index ); + } + + return null; + } + + public override XMLNode Parent( XMLNode node ) + { + return node.parentNode; + } + } + +} \ No newline at end of file