DTSweep.cs 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906
  1. /* Poly2Tri
  2. * Copyright (c) 2009-2010, Poly2Tri Contributors
  3. * http://code.google.com/p/poly2tri/
  4. *
  5. * All rights reserved.
  6. *
  7. * Redistribution and use in source and binary forms, with or without modification,
  8. * are permitted provided that the following conditions are met:
  9. *
  10. * * Redistributions of source code must retain the above copyright notice,
  11. * this list of conditions and the following disclaimer.
  12. * * Redistributions in binary form must reproduce the above copyright notice,
  13. * this list of conditions and the following disclaimer in the documentation
  14. * and/or other materials provided with the distribution.
  15. * * Neither the name of Poly2Tri nor the names of its contributors may be
  16. * used to endorse or promote products derived from this software without specific
  17. * prior written permission.
  18. *
  19. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  20. * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  21. * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  22. * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
  23. * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
  24. * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
  25. * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
  26. * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
  27. * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
  28. * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  29. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  30. */
  31. /*
  32. * Sweep-line, Constrained Delauney Triangulation (CDT) See: Domiter, V. and
  33. * Zalik, B.(2008)'Sweep-line algorithm for constrained Delaunay triangulation',
  34. * International Journal of Geographical Information Science
  35. *
  36. * "FlipScan" Constrained Edge Algorithm invented by author of this code.
  37. *
  38. * Author: Thomas Åhlén, thahlen@gmail.com
  39. */
  40. /// Changes from the Java version
  41. /// Turned DTSweep into a static class
  42. /// Lots of deindentation via early bailout
  43. /// Future possibilities
  44. /// Comments!
  45. using System;
  46. using System.Collections.Generic;
  47. using System.Diagnostics;
  48. using System.Linq;
  49. namespace Veldrid.Common.Poly2Tri.Triangulation.Delaunay.Sweep
  50. {
  51. public static class DTSweep {
  52. private const double PI_div2 = Math.PI / 2;
  53. private const double PI_3div4 = 3 * Math.PI / 4;
  54. /// <summary>
  55. /// Triangulate simple polygon with holes
  56. /// </summary>
  57. public static void Triangulate( DTSweepContext tcx ) {
  58. tcx.CreateAdvancingFront();
  59. Sweep(tcx);
  60. // TODO: remove temporary
  61. // Check if the sweep algorithm is legalize robust
  62. // By doing a legalize on all triangles and see if anything happens
  63. // we know if the sweep algorithm missed some legalizations
  64. // Console.WriteLine("============================");
  65. // foreach ( DelaunayTriangle t in tcx.Triangles )
  66. // {
  67. // if( Legalize( tcx, t ) )
  68. // {
  69. // tcx.getDebugContext().setPrimaryTriangle( t );
  70. // Console.WriteLine("[FIX] Triangle needed legalization after sweep");
  71. // }
  72. // }
  73. // Finalize triangulation
  74. if (tcx.TriangulationMode == TriangulationMode.Polygon) {
  75. FinalizationPolygon(tcx);
  76. } else {
  77. FinalizationConvexHull(tcx);
  78. }
  79. tcx.Done();
  80. }
  81. /// <summary>
  82. /// Start sweeping the Y-sorted point set from bottom to top
  83. /// </summary>
  84. private static void Sweep( DTSweepContext tcx ) {
  85. var points = tcx.Points;
  86. TriangulationPoint point;
  87. AdvancingFrontNode node;
  88. for (int i = 1; i < points.Count; i++) {
  89. point = points[i];
  90. node = PointEvent(tcx, point);
  91. if (point.HasEdges) foreach (DTSweepConstraint e in point.Edges) {
  92. if (tcx.IsDebugEnabled) tcx.DTDebugContext.ActiveConstraint = e;
  93. EdgeEvent(tcx, e, node);
  94. }
  95. tcx.Update(null);
  96. }
  97. }
  98. /// <summary>
  99. /// If this is a Delaunay Triangulation of a pointset we need to fill so the triangle mesh gets a ConvexHull
  100. /// </summary>
  101. private static void FinalizationConvexHull( DTSweepContext tcx ) {
  102. AdvancingFrontNode n1, n2, n3;
  103. DelaunayTriangle t1;
  104. TriangulationPoint first, p1;
  105. n1 = tcx.Front.Head.Next;
  106. n2 = n1.Next;
  107. n3 = n2.Next;
  108. first = n1.Point;
  109. TurnAdvancingFrontConvex(tcx, n1, n2);
  110. n1 = tcx.Front.Tail.Prev;
  111. if (n1.Triangle.Contains(n1.Next.Point) && n1.Triangle.Contains(n1.Prev.Point)) {
  112. t1 = n1.Triangle.NeighborAcrossFrom(n1.Point);
  113. RotateTrianglePair(n1.Triangle, n1.Point, t1, t1.OppositePoint(n1.Triangle, n1.Point));
  114. tcx.MapTriangleToNodes(n1.Triangle);
  115. tcx.MapTriangleToNodes(t1);
  116. }
  117. n1 = tcx.Front.Head.Next;
  118. if (n1.Triangle.Contains(n1.Prev.Point) && n1.Triangle.Contains(n1.Next.Point)) {
  119. t1 = n1.Triangle.NeighborAcrossFrom(n1.Point);
  120. RotateTrianglePair(n1.Triangle, n1.Point, t1, t1.OppositePoint(n1.Triangle, n1.Point));
  121. tcx.MapTriangleToNodes(n1.Triangle);
  122. tcx.MapTriangleToNodes(t1);
  123. }
  124. // TODO: implement ConvexHull for lower right and left boundary
  125. // Lower right boundary
  126. first = tcx.Front.Head.Point;
  127. n2 = tcx.Front.Tail.Prev;
  128. t1 = n2.Triangle;
  129. p1 = n2.Point;
  130. do {
  131. tcx.RemoveFromList(t1);
  132. p1 = t1.PointCCWFrom(p1);
  133. if (p1 == first) break;
  134. t1 = t1.NeighborCCWFrom(p1);
  135. } while (true);
  136. // Lower left boundary
  137. first = tcx.Front.Head.Next.Point;
  138. p1 = t1.PointCWFrom(tcx.Front.Head.Point);
  139. t1 = t1.NeighborCWFrom(tcx.Front.Head.Point);
  140. do {
  141. tcx.RemoveFromList(t1);
  142. p1 = t1.PointCCWFrom(p1);
  143. t1 = t1.NeighborCCWFrom(p1);
  144. } while (p1 != first);
  145. tcx.FinalizeTriangulation();
  146. }
  147. /// <summary>
  148. /// We will traverse the entire advancing front and fill it to form a convex hull.
  149. /// </summary>
  150. private static void TurnAdvancingFrontConvex( DTSweepContext tcx, AdvancingFrontNode b, AdvancingFrontNode c ) {
  151. AdvancingFrontNode first = b;
  152. while (c != tcx.Front.Tail) {
  153. if (tcx.IsDebugEnabled) tcx.DTDebugContext.ActiveNode = c;
  154. if (TriangulationUtil.Orient2d(b.Point, c.Point, c.Next.Point) == Orientation.CCW) {
  155. // [b,c,d] Concave - fill around c
  156. Fill(tcx, c);
  157. c = c.Next;
  158. } else {
  159. // [b,c,d] Convex
  160. if (b != first && TriangulationUtil.Orient2d(b.Prev.Point, b.Point, c.Point) == Orientation.CCW) {
  161. // [a,b,c] Concave - fill around b
  162. Fill(tcx, b);
  163. b = b.Prev;
  164. } else {
  165. // [a,b,c] Convex - nothing to fill
  166. b = c;
  167. c = c.Next;
  168. }
  169. }
  170. }
  171. }
  172. private static void FinalizationPolygon( DTSweepContext tcx ) {
  173. // Get an Internal triangle to start with
  174. DelaunayTriangle t = tcx.Front.Head.Next.Triangle;
  175. TriangulationPoint p = tcx.Front.Head.Next.Point;
  176. while (!t.GetConstrainedEdgeCW(p)) t = t.NeighborCCWFrom(p);
  177. // Collect interior triangles constrained by edges
  178. tcx.MeshClean(t);
  179. }
  180. /// <summary>
  181. /// Find closes node to the left of the new point and
  182. /// create a new triangle. If needed new holes and basins
  183. /// will be filled to.
  184. /// </summary>
  185. private static AdvancingFrontNode PointEvent( DTSweepContext tcx, TriangulationPoint point ) {
  186. AdvancingFrontNode node, newNode;
  187. node = tcx.LocateNode(point);
  188. if (tcx.IsDebugEnabled) tcx.DTDebugContext.ActiveNode = node;
  189. newNode = NewFrontTriangle(tcx, point, node);
  190. // Only need to check +epsilon since point never have smaller
  191. // x value than node due to how we fetch nodes from the front
  192. if (point.X <= node.Point.X + TriangulationUtil.EPSILON) Fill(tcx, node);
  193. tcx.AddNode(newNode);
  194. FillAdvancingFront(tcx, newNode);
  195. return newNode;
  196. }
  197. /// <summary>
  198. /// Creates a new front triangle and legalize it
  199. /// </summary>
  200. private static AdvancingFrontNode NewFrontTriangle( DTSweepContext tcx, TriangulationPoint point, AdvancingFrontNode node ) {
  201. AdvancingFrontNode newNode;
  202. DelaunayTriangle triangle;
  203. triangle = new DelaunayTriangle(point, node.Point, node.Next.Point);
  204. triangle.MarkNeighbor(node.Triangle);
  205. tcx.Triangles.Add(triangle);
  206. newNode = new AdvancingFrontNode(point);
  207. newNode.Next = node.Next;
  208. newNode.Prev = node;
  209. node.Next.Prev = newNode;
  210. node.Next = newNode;
  211. tcx.AddNode(newNode); // XXX: BST
  212. if (tcx.IsDebugEnabled) tcx.DTDebugContext.ActiveNode = newNode;
  213. if (!Legalize(tcx, triangle)) tcx.MapTriangleToNodes(triangle);
  214. return newNode;
  215. }
  216. private static void EdgeEvent( DTSweepContext tcx, DTSweepConstraint edge, AdvancingFrontNode node ) {
  217. try {
  218. tcx.EdgeEvent.ConstrainedEdge = edge;
  219. tcx.EdgeEvent.Right = edge.P.X > edge.Q.X;
  220. if (tcx.IsDebugEnabled) { tcx.DTDebugContext.PrimaryTriangle = node.Triangle; }
  221. if (IsEdgeSideOfTriangle(node.Triangle, edge.P, edge.Q)) return;
  222. // For now we will do all needed filling
  223. // TODO: integrate with flip process might give some better performance
  224. // but for now this avoid the issue with cases that needs both flips and fills
  225. FillEdgeEvent(tcx, edge, node);
  226. EdgeEvent(tcx, edge.P, edge.Q, node.Triangle, edge.Q);
  227. } catch ( PointOnEdgeException e) {
  228. //Debug.WriteLine( String.Format( "Warning: Skipping Edge: {0}", e.Message ) );
  229. throw;
  230. }
  231. }
  232. private static void FillEdgeEvent( DTSweepContext tcx, DTSweepConstraint edge, AdvancingFrontNode node ) {
  233. if (tcx.EdgeEvent.Right) {
  234. FillRightAboveEdgeEvent(tcx, edge, node);
  235. } else {
  236. FillLeftAboveEdgeEvent(tcx, edge, node);
  237. }
  238. }
  239. private static void FillRightConcaveEdgeEvent( DTSweepContext tcx, DTSweepConstraint edge, AdvancingFrontNode node ) {
  240. Fill(tcx, node.Next);
  241. if (node.Next.Point != edge.P) {
  242. // Next above or below edge?
  243. if (TriangulationUtil.Orient2d(edge.Q, node.Next.Point, edge.P) == Orientation.CCW) {
  244. // Below
  245. if (TriangulationUtil.Orient2d(node.Point, node.Next.Point, node.Next.Next.Point) == Orientation.CCW) {
  246. // Next is concave
  247. FillRightConcaveEdgeEvent(tcx, edge, node);
  248. } else {
  249. // Next is convex
  250. }
  251. }
  252. }
  253. }
  254. private static void FillRightConvexEdgeEvent( DTSweepContext tcx, DTSweepConstraint edge, AdvancingFrontNode node ) {
  255. // Next concave or convex?
  256. if (TriangulationUtil.Orient2d(node.Next.Point, node.Next.Next.Point, node.Next.Next.Next.Point) == Orientation.CCW) {
  257. // Concave
  258. FillRightConcaveEdgeEvent(tcx, edge, node.Next);
  259. } else {
  260. // Convex
  261. // Next above or below edge?
  262. if (TriangulationUtil.Orient2d(edge.Q, node.Next.Next.Point, edge.P) == Orientation.CCW) {
  263. // Below
  264. FillRightConvexEdgeEvent(tcx, edge, node.Next);
  265. } else {
  266. // Above
  267. }
  268. }
  269. }
  270. private static void FillRightBelowEdgeEvent( DTSweepContext tcx, DTSweepConstraint edge, AdvancingFrontNode node ) {
  271. if (tcx.IsDebugEnabled) tcx.DTDebugContext.ActiveNode = node;
  272. if (node.Point.X < edge.P.X) { // needed?
  273. if (TriangulationUtil.Orient2d(node.Point, node.Next.Point, node.Next.Next.Point) == Orientation.CCW) {
  274. // Concave
  275. FillRightConcaveEdgeEvent(tcx, edge, node);
  276. } else {
  277. // Convex
  278. FillRightConvexEdgeEvent(tcx, edge, node);
  279. // Retry this one
  280. FillRightBelowEdgeEvent(tcx, edge, node);
  281. }
  282. }
  283. }
  284. private static void FillRightAboveEdgeEvent( DTSweepContext tcx, DTSweepConstraint edge, AdvancingFrontNode node ) {
  285. while (node.Next.Point.X < edge.P.X) {
  286. if (tcx.IsDebugEnabled) { tcx.DTDebugContext.ActiveNode = node; }
  287. // Check if next node is below the edge
  288. Orientation o1 = TriangulationUtil.Orient2d(edge.Q, node.Next.Point, edge.P);
  289. if (o1 == Orientation.CCW) {
  290. FillRightBelowEdgeEvent(tcx, edge, node);
  291. } else {
  292. node = node.Next;
  293. }
  294. }
  295. }
  296. private static void FillLeftConvexEdgeEvent( DTSweepContext tcx, DTSweepConstraint edge, AdvancingFrontNode node ) {
  297. // Next concave or convex?
  298. if (TriangulationUtil.Orient2d(node.Prev.Point, node.Prev.Prev.Point, node.Prev.Prev.Prev.Point) == Orientation.CW) {
  299. // Concave
  300. FillLeftConcaveEdgeEvent(tcx, edge, node.Prev);
  301. } else {
  302. // Convex
  303. // Next above or below edge?
  304. if (TriangulationUtil.Orient2d(edge.Q, node.Prev.Prev.Point, edge.P) == Orientation.CW) {
  305. // Below
  306. FillLeftConvexEdgeEvent(tcx, edge, node.Prev);
  307. } else {
  308. // Above
  309. }
  310. }
  311. }
  312. private static void FillLeftConcaveEdgeEvent( DTSweepContext tcx, DTSweepConstraint edge, AdvancingFrontNode node ) {
  313. Fill(tcx, node.Prev);
  314. if (node.Prev.Point != edge.P) {
  315. // Next above or below edge?
  316. if (TriangulationUtil.Orient2d(edge.Q, node.Prev.Point, edge.P) == Orientation.CW) {
  317. // Below
  318. if (TriangulationUtil.Orient2d(node.Point, node.Prev.Point, node.Prev.Prev.Point) == Orientation.CW) {
  319. // Next is concave
  320. FillLeftConcaveEdgeEvent(tcx, edge, node);
  321. } else {
  322. // Next is convex
  323. }
  324. }
  325. }
  326. }
  327. private static void FillLeftBelowEdgeEvent( DTSweepContext tcx, DTSweepConstraint edge, AdvancingFrontNode node ) {
  328. if (tcx.IsDebugEnabled) tcx.DTDebugContext.ActiveNode = node;
  329. if (node.Point.X > edge.P.X) {
  330. if (TriangulationUtil.Orient2d(node.Point, node.Prev.Point, node.Prev.Prev.Point) == Orientation.CW) {
  331. // Concave
  332. FillLeftConcaveEdgeEvent(tcx, edge, node);
  333. } else {
  334. // Convex
  335. FillLeftConvexEdgeEvent(tcx, edge, node);
  336. // Retry this one
  337. FillLeftBelowEdgeEvent(tcx, edge, node);
  338. }
  339. }
  340. }
  341. private static void FillLeftAboveEdgeEvent( DTSweepContext tcx, DTSweepConstraint edge, AdvancingFrontNode node ) {
  342. while (node.Prev.Point.X > edge.P.X) {
  343. if (tcx.IsDebugEnabled) tcx.DTDebugContext.ActiveNode = node;
  344. // Check if next node is below the edge
  345. Orientation o1 = TriangulationUtil.Orient2d(edge.Q, node.Prev.Point, edge.P);
  346. if (o1 == Orientation.CW) {
  347. FillLeftBelowEdgeEvent(tcx, edge, node);
  348. } else {
  349. node = node.Prev;
  350. }
  351. }
  352. }
  353. private static bool IsEdgeSideOfTriangle( DelaunayTriangle triangle, TriangulationPoint ep, TriangulationPoint eq ) {
  354. int index = triangle.EdgeIndex(ep, eq);
  355. if ( index == -1 ) return false;
  356. triangle.MarkConstrainedEdge(index);
  357. triangle = triangle.Neighbors[index];
  358. if (triangle != null) triangle.MarkConstrainedEdge(ep, eq);
  359. return true;
  360. }
  361. private static void EdgeEvent( DTSweepContext tcx, TriangulationPoint ep, TriangulationPoint eq, DelaunayTriangle triangle, TriangulationPoint point ) {
  362. TriangulationPoint p1, p2;
  363. if (triangle == null)
  364. return;
  365. if (tcx.IsDebugEnabled) tcx.DTDebugContext.PrimaryTriangle=triangle;
  366. if (IsEdgeSideOfTriangle(triangle, ep, eq)) return;
  367. p1 = triangle.PointCCWFrom(point);
  368. Orientation o1 = TriangulationUtil.Orient2d(eq, p1, ep);
  369. if (o1 == Orientation.Collinear) {
  370. // TODO: Split edge in two
  371. //// splitEdge( ep, eq, p1 );
  372. // edgeEvent( tcx, p1, eq, triangle, point );
  373. // edgeEvent( tcx, ep, p1, triangle, p1 );
  374. // return;
  375. throw new PointOnEdgeException("EdgeEvent - Point on constrained edge not supported yet",eq,p1,ep);
  376. }
  377. p2 = triangle.PointCWFrom(point);
  378. Orientation o2 = TriangulationUtil.Orient2d(eq, p2, ep);
  379. if (o2 == Orientation.Collinear) {
  380. // TODO: Split edge in two
  381. // edgeEvent( tcx, p2, eq, triangle, point );
  382. // edgeEvent( tcx, ep, p2, triangle, p2 );
  383. // return;
  384. throw new PointOnEdgeException("EdgeEvent - Point on constrained edge not supported yet",eq,p2,ep);
  385. }
  386. if (o1 == o2) {
  387. // Need to decide if we are rotating CW or CCW to get to a triangle
  388. // that will cross edge
  389. if (o1 == Orientation.CW) {
  390. triangle = triangle.NeighborCCWFrom(point);
  391. } else {
  392. triangle = triangle.NeighborCWFrom(point);
  393. }
  394. EdgeEvent(tcx, ep, eq, triangle, point);
  395. } else {
  396. // This triangle crosses constraint so lets flippin start!
  397. FlipEdgeEvent(tcx, ep, eq, triangle, point);
  398. }
  399. }
  400. /// <summary>
  401. /// In the case of a pointset with some constraint edges. If a triangle side is collinear
  402. /// with a part of the constraint we split the constraint into two constraints. This could
  403. /// happen when the given constraint migth intersect a point in the set.<br>
  404. /// This can never happen in the case when we are working with a polygon.
  405. ///
  406. /// Think of two triangles that have non shared sides that are collinear and the constraint
  407. /// is set from a point in triangle A to a point in triangle B so that the constraint is
  408. /// the union of both those sides. We then have to split the constraint into two so we get
  409. /// one constraint for each triangle.
  410. /// </summary>
  411. /// <param name="ep"></param>
  412. /// <param name="eq"></param>
  413. /// <param name="p">point on the edge between ep->eq</param>
  414. private static void SplitEdge( TriangulationPoint ep, TriangulationPoint eq, TriangulationPoint p ) {
  415. DTSweepConstraint edge = eq.Edges.First( e => e.Q==ep || e.P==ep );
  416. edge.P = p;
  417. new DTSweepConstraint(ep, p); // Et tu, Brute? --MM
  418. // // Redo this edge now that we have split the constraint
  419. // newEdgeEvent( tcx, edge, triangle, point );
  420. // // Continue with new edge
  421. // newEdgeEvent( tcx, edge, triangle, p2 );
  422. }
  423. private static void FlipEdgeEvent( DTSweepContext tcx, TriangulationPoint ep, TriangulationPoint eq, DelaunayTriangle t, TriangulationPoint p ) {
  424. DelaunayTriangle ot = t.NeighborAcrossFrom(p);
  425. TriangulationPoint op = ot.OppositePoint(t, p);
  426. if (ot == null) {
  427. // If we want to integrate the fillEdgeEvent do it here
  428. // With current implementation we should never get here
  429. throw new InvalidOperationException("[BUG:FIXME] FLIP failed due to missing triangle");
  430. }
  431. if (tcx.IsDebugEnabled) {
  432. tcx.DTDebugContext.PrimaryTriangle = t;
  433. tcx.DTDebugContext.SecondaryTriangle = ot;
  434. } // TODO: remove
  435. bool inScanArea = TriangulationUtil.InScanArea(p, t.PointCCWFrom(p), t.PointCWFrom(p), op);
  436. if (inScanArea) {
  437. // Lets rotate shared edge one vertex CW
  438. RotateTrianglePair(t, p, ot, op);
  439. tcx.MapTriangleToNodes(t);
  440. tcx.MapTriangleToNodes(ot);
  441. if (p == eq && op == ep) {
  442. if (eq == tcx.EdgeEvent.ConstrainedEdge.Q
  443. && ep == tcx.EdgeEvent.ConstrainedEdge.P) {
  444. if (tcx.IsDebugEnabled) Console.WriteLine("[FLIP] - constrained edge done"); // TODO: remove
  445. t.MarkConstrainedEdge(ep, eq);
  446. ot.MarkConstrainedEdge(ep, eq);
  447. Legalize(tcx, t);
  448. Legalize(tcx, ot);
  449. } else {
  450. if (tcx.IsDebugEnabled) Console.WriteLine("[FLIP] - subedge done"); // TODO: remove
  451. // XXX: I think one of the triangles should be legalized here?
  452. }
  453. } else {
  454. if (tcx.IsDebugEnabled) Console.WriteLine("[FLIP] - flipping and continuing with triangle still crossing edge"); // TODO: remove
  455. Orientation o = TriangulationUtil.Orient2d(eq, op, ep);
  456. t = NextFlipTriangle(tcx, o, t, ot, p, op);
  457. FlipEdgeEvent(tcx, ep, eq, t, p);
  458. }
  459. } else {
  460. TriangulationPoint newP = NextFlipPoint(ep, eq, ot, op);
  461. FlipScanEdgeEvent(tcx, ep, eq, t, ot, newP);
  462. EdgeEvent(tcx, ep, eq, t, p);
  463. }
  464. }
  465. /// <summary>
  466. /// When we need to traverse from one triangle to the next we need
  467. /// the point in current triangle that is the opposite point to the next
  468. /// triangle.
  469. /// </summary>
  470. private static TriangulationPoint NextFlipPoint( TriangulationPoint ep, TriangulationPoint eq, DelaunayTriangle ot, TriangulationPoint op ) {
  471. Orientation o2d = TriangulationUtil.Orient2d(eq, op, ep);
  472. switch ( o2d ) {
  473. case Orientation.CW: return ot.PointCCWFrom(op);
  474. case Orientation.CCW: return ot.PointCWFrom(op);
  475. case Orientation.Collinear:
  476. // TODO: implement support for point on constraint edge
  477. throw new PointOnEdgeException("Point on constrained edge not supported yet",eq,op,ep);
  478. default:
  479. throw new NotImplementedException("Orientation not handled");
  480. }
  481. }
  482. /// <summary>
  483. /// After a flip we have two triangles and know that only one will still be
  484. /// intersecting the edge. So decide which to contiune with and legalize the other
  485. /// </summary>
  486. /// <param name="tcx"></param>
  487. /// <param name="o">should be the result of an TriangulationUtil.orient2d( eq, op, ep )</param>
  488. /// <param name="t">triangle 1</param>
  489. /// <param name="ot">triangle 2</param>
  490. /// <param name="p">a point shared by both triangles</param>
  491. /// <param name="op">another point shared by both triangles</param>
  492. /// <returns>returns the triangle still intersecting the edge</returns>
  493. private static DelaunayTriangle NextFlipTriangle( DTSweepContext tcx, Orientation o, DelaunayTriangle t, DelaunayTriangle ot, TriangulationPoint p, TriangulationPoint op ) {
  494. int edgeIndex;
  495. if (o == Orientation.CCW) {
  496. // ot is not crossing edge after flip
  497. edgeIndex = ot.EdgeIndex(p, op);
  498. ot.EdgeIsDelaunay[edgeIndex] = true;
  499. Legalize(tcx, ot);
  500. ot.EdgeIsDelaunay.Clear();
  501. return t;
  502. }
  503. // t is not crossing edge after flip
  504. edgeIndex = t.EdgeIndex(p, op);
  505. t.EdgeIsDelaunay[edgeIndex] = true;
  506. Legalize(tcx, t);
  507. t.EdgeIsDelaunay.Clear();
  508. return ot;
  509. }
  510. /// <summary>
  511. /// Scan part of the FlipScan algorithm<br>
  512. /// When a triangle pair isn't flippable we will scan for the next
  513. /// point that is inside the flip triangle scan area. When found
  514. /// we generate a new flipEdgeEvent
  515. /// </summary>
  516. /// <param name="tcx"></param>
  517. /// <param name="ep">last point on the edge we are traversing</param>
  518. /// <param name="eq">first point on the edge we are traversing</param>
  519. /// <param name="flipTriangle">the current triangle sharing the point eq with edge</param>
  520. /// <param name="t"></param>
  521. /// <param name="p"></param>
  522. private static void FlipScanEdgeEvent( DTSweepContext tcx, TriangulationPoint ep, TriangulationPoint eq, DelaunayTriangle flipTriangle, DelaunayTriangle t, TriangulationPoint p ) {
  523. DelaunayTriangle ot;
  524. TriangulationPoint op, newP;
  525. bool inScanArea;
  526. ot = t.NeighborAcrossFrom(p);
  527. op = ot.OppositePoint(t, p);
  528. if (ot == null) {
  529. // If we want to integrate the fillEdgeEvent do it here
  530. // With current implementation we should never get here
  531. throw new Exception("[BUG:FIXME] FLIP failed due to missing triangle");
  532. }
  533. if (tcx.IsDebugEnabled) {
  534. Console.WriteLine("[FLIP:SCAN] - scan next point"); // TODO: remove
  535. tcx.DTDebugContext.PrimaryTriangle = t;
  536. tcx.DTDebugContext.SecondaryTriangle = ot;
  537. }
  538. inScanArea = TriangulationUtil.InScanArea(eq, flipTriangle.PointCCWFrom(eq), flipTriangle.PointCWFrom(eq), op);
  539. if (inScanArea) {
  540. // flip with new edge op->eq
  541. FlipEdgeEvent(tcx, eq, op, ot, op);
  542. // TODO: Actually I just figured out that it should be possible to
  543. // improve this by getting the next ot and op before the the above
  544. // flip and continue the flipScanEdgeEvent here
  545. // set new ot and op here and loop back to inScanArea test
  546. // also need to set a new flipTriangle first
  547. // Turns out at first glance that this is somewhat complicated
  548. // so it will have to wait.
  549. } else {
  550. newP = NextFlipPoint(ep, eq, ot, op);
  551. FlipScanEdgeEvent(tcx, ep, eq, flipTriangle, ot, newP);
  552. }
  553. }
  554. /// <summary>
  555. /// Fills holes in the Advancing Front
  556. /// </summary>
  557. private static void FillAdvancingFront( DTSweepContext tcx, AdvancingFrontNode n ) {
  558. AdvancingFrontNode node;
  559. double angle;
  560. // Fill right holes
  561. node = n.Next;
  562. while (node.HasNext) {
  563. angle = HoleAngle(node);
  564. if (angle > PI_div2 || angle < -PI_div2) break;
  565. Fill(tcx, node);
  566. node = node.Next;
  567. }
  568. // Fill left holes
  569. node = n.Prev;
  570. while (node.HasPrev) {
  571. angle = HoleAngle(node);
  572. if (angle > PI_div2 || angle < -PI_div2) break;
  573. Fill(tcx, node);
  574. node = node.Prev;
  575. }
  576. // Fill right basins
  577. if (n.HasNext && n.Next.HasNext) {
  578. angle = BasinAngle(n);
  579. if (angle < PI_3div4) FillBasin(tcx, n);
  580. }
  581. }
  582. /// <summary>
  583. /// Fills a basin that has formed on the Advancing Front to the right
  584. /// of given node.<br>
  585. /// First we decide a left,bottom and right node that forms the
  586. /// boundaries of the basin. Then we do a reqursive fill.
  587. /// </summary>
  588. /// <param name="tcx"></param>
  589. /// <param name="node">starting node, this or next node will be left node</param>
  590. private static void FillBasin( DTSweepContext tcx, AdvancingFrontNode node ) {
  591. if (TriangulationUtil.Orient2d(node.Point, node.Next.Point, node.Next.Next.Point) == Orientation.CCW) {
  592. // tcx.basin.leftNode = node.next.next;
  593. tcx.Basin.leftNode = node;
  594. } else {
  595. tcx.Basin.leftNode = node.Next;
  596. }
  597. // Find the bottom and right node
  598. tcx.Basin.bottomNode = tcx.Basin.leftNode;
  599. while (tcx.Basin.bottomNode.HasNext && tcx.Basin.bottomNode.Point.Y >= tcx.Basin.bottomNode.Next.Point.Y) tcx.Basin.bottomNode = tcx.Basin.bottomNode.Next;
  600. if (tcx.Basin.bottomNode == tcx.Basin.leftNode) return; // No valid basin
  601. tcx.Basin.rightNode = tcx.Basin.bottomNode;
  602. while (tcx.Basin.rightNode.HasNext && tcx.Basin.rightNode.Point.Y < tcx.Basin.rightNode.Next.Point.Y) tcx.Basin.rightNode = tcx.Basin.rightNode.Next;
  603. if (tcx.Basin.rightNode == tcx.Basin.bottomNode) return; // No valid basins
  604. tcx.Basin.width = tcx.Basin.rightNode.Point.X - tcx.Basin.leftNode.Point.X;
  605. tcx.Basin.leftHighest = tcx.Basin.leftNode.Point.Y > tcx.Basin.rightNode.Point.Y;
  606. FillBasinReq(tcx, tcx.Basin.bottomNode);
  607. }
  608. /// <summary>
  609. /// Recursive algorithm to fill a Basin with triangles
  610. /// </summary>
  611. private static void FillBasinReq( DTSweepContext tcx, AdvancingFrontNode node ) {
  612. if (IsShallow(tcx, node)) return; // if shallow stop filling
  613. Fill(tcx, node);
  614. if (node.Prev == tcx.Basin.leftNode && node.Next == tcx.Basin.rightNode) {
  615. return;
  616. } else if (node.Prev == tcx.Basin.leftNode) {
  617. Orientation o = TriangulationUtil.Orient2d(node.Point, node.Next.Point, node.Next.Next.Point);
  618. if (o == Orientation.CW) return;
  619. node = node.Next;
  620. } else if (node.Next == tcx.Basin.rightNode) {
  621. Orientation o = TriangulationUtil.Orient2d(node.Point, node.Prev.Point, node.Prev.Prev.Point);
  622. if (o == Orientation.CCW) return;
  623. node = node.Prev;
  624. } else {
  625. // Continue with the neighbor node with lowest Y value
  626. if (node.Prev.Point.Y < node.Next.Point.Y) {
  627. node = node.Prev;
  628. } else {
  629. node = node.Next;
  630. }
  631. }
  632. FillBasinReq(tcx, node);
  633. }
  634. private static bool IsShallow( DTSweepContext tcx, AdvancingFrontNode node ) {
  635. double height;
  636. if (tcx.Basin.leftHighest) {
  637. height = tcx.Basin.leftNode.Point.Y - node.Point.Y;
  638. } else {
  639. height = tcx.Basin.rightNode.Point.Y - node.Point.Y;
  640. }
  641. if (tcx.Basin.width > height) {
  642. return true;
  643. }
  644. return false;
  645. }
  646. /// <summary>
  647. /// ???
  648. /// </summary>
  649. /// <param name="node">middle node</param>
  650. /// <returns>the angle between 3 front nodes</returns>
  651. private static double HoleAngle( AdvancingFrontNode node ) {
  652. // XXX: do we really need a signed angle for holeAngle?
  653. // could possible save some cycles here
  654. /* Complex plane
  655. * ab = cosA +i*sinA
  656. * ab = (ax + ay*i)(bx + by*i) = (ax*bx + ay*by) + i(ax*by-ay*bx)
  657. * atan2(y,x) computes the principal value of the argument function
  658. * applied to the complex number x+iy
  659. * Where x = ax*bx + ay*by
  660. * y = ax*by - ay*bx
  661. */
  662. double px = node.Point.X;
  663. double py = node.Point.Y;
  664. double ax = node.Next.Point.X - px;
  665. double ay = node.Next.Point.Y - py;
  666. double bx = node.Prev.Point.X - px;
  667. double by = node.Prev.Point.Y - py;
  668. return Math.Atan2(ax * by - ay * bx, ax * bx + ay * by);
  669. }
  670. /// <summary>
  671. /// The basin angle is decided against the horizontal line [1,0]
  672. /// </summary>
  673. private static double BasinAngle( AdvancingFrontNode node ) {
  674. double ax = node.Point.X - node.Next.Next.Point.X;
  675. double ay = node.Point.Y - node.Next.Next.Point.Y;
  676. return Math.Atan2(ay, ax);
  677. }
  678. /// <summary>
  679. /// Adds a triangle to the advancing front to fill a hole.
  680. /// </summary>
  681. /// <param name="tcx"></param>
  682. /// <param name="node">middle node, that is the bottom of the hole</param>
  683. private static void Fill( DTSweepContext tcx, AdvancingFrontNode node ) {
  684. DelaunayTriangle triangle = new DelaunayTriangle(node.Prev.Point, node.Point, node.Next.Point);
  685. // TODO: should copy the cEdge value from neighbor triangles
  686. // for now cEdge values are copied during the legalize
  687. triangle.MarkNeighbor(node.Prev.Triangle);
  688. triangle.MarkNeighbor(node.Triangle);
  689. tcx.Triangles.Add(triangle);
  690. // Update the advancing front
  691. node.Prev.Next = node.Next;
  692. node.Next.Prev = node.Prev;
  693. tcx.RemoveNode(node);
  694. // If it was legalized the triangle has already been mapped
  695. if (!Legalize(tcx, triangle)) tcx.MapTriangleToNodes(triangle);
  696. }
  697. /// <summary>
  698. /// Returns true if triangle was legalized
  699. /// </summary>
  700. private static bool Legalize( DTSweepContext tcx, DelaunayTriangle t ) {
  701. // To legalize a triangle we start by finding if any of the three edges
  702. // violate the Delaunay condition
  703. for (int i = 0; i < 3; i++) {
  704. // TODO: fix so that cEdge is always valid when creating new triangles then we can check it here
  705. // instead of below with ot
  706. if (t.EdgeIsDelaunay[i]) continue;
  707. DelaunayTriangle ot = t.Neighbors[i];
  708. if (ot == null) continue;
  709. TriangulationPoint p = t.Points[i];
  710. TriangulationPoint op = ot.OppositePoint(t, p);
  711. int oi = ot.IndexOf(op);
  712. // If this is a Constrained Edge or a Delaunay Edge(only during recursive legalization)
  713. // then we should not try to legalize
  714. if (ot.EdgeIsConstrained[oi] || ot.EdgeIsDelaunay[oi]) {
  715. t.EdgeIsConstrained[i] = ot.EdgeIsConstrained[oi]; // XXX: have no good way of setting this property when creating new triangles so lets set it here
  716. continue;
  717. }
  718. if (!TriangulationUtil.SmartIncircle(p,t.PointCCWFrom(p),t.PointCWFrom(p),op)) continue;
  719. // Lets mark this shared edge as Delaunay
  720. t.EdgeIsDelaunay[i] = true;
  721. ot.EdgeIsDelaunay[oi] = true;
  722. // Lets rotate shared edge one vertex CW to legalize it
  723. RotateTrianglePair(t, p, ot, op);
  724. // We now got one valid Delaunay Edge shared by two triangles
  725. // This gives us 4 new edges to check for Delaunay
  726. // Make sure that triangle to node mapping is done only one time for a specific triangle
  727. if (!Legalize(tcx, t)) tcx.MapTriangleToNodes(t);
  728. if (!Legalize(tcx, ot)) tcx.MapTriangleToNodes(ot);
  729. // Reset the Delaunay edges, since they only are valid Delaunay edges
  730. // until we add a new triangle or point.
  731. // XXX: need to think about this. Can these edges be tried after we
  732. // return to previous recursive level?
  733. t.EdgeIsDelaunay[i] = false;
  734. ot.EdgeIsDelaunay[oi] = false;
  735. // If triangle have been legalized no need to check the other edges since
  736. // the recursive legalization will handles those so we can end here.
  737. return true;
  738. }
  739. return false;
  740. }
  741. /// <summary>
  742. /// Rotates a triangle pair one vertex CW
  743. /// n2 n2
  744. /// P +-----+ P +-----+
  745. /// | t /| |\ t |
  746. /// | / | | \ |
  747. /// n1| / |n3 n1| \ |n3
  748. /// | / | after CW | \ |
  749. /// |/ oT | | oT \|
  750. /// +-----+ oP +-----+
  751. /// n4 n4
  752. /// </summary>
  753. private static void RotateTrianglePair( DelaunayTriangle t, TriangulationPoint p, DelaunayTriangle ot, TriangulationPoint op ) {
  754. DelaunayTriangle n1, n2, n3, n4;
  755. n1 = t.NeighborCCWFrom(p);
  756. n2 = t.NeighborCWFrom(p);
  757. n3 = ot.NeighborCCWFrom(op);
  758. n4 = ot.NeighborCWFrom(op);
  759. bool ce1, ce2, ce3, ce4;
  760. ce1 = t.GetConstrainedEdgeCCW(p);
  761. ce2 = t.GetConstrainedEdgeCW(p);
  762. ce3 = ot.GetConstrainedEdgeCCW(op);
  763. ce4 = ot.GetConstrainedEdgeCW(op);
  764. bool de1, de2, de3, de4;
  765. de1 = t.GetDelaunayEdgeCCW(p);
  766. de2 = t.GetDelaunayEdgeCW(p);
  767. de3 = ot.GetDelaunayEdgeCCW(op);
  768. de4 = ot.GetDelaunayEdgeCW(op);
  769. t.Legalize(p, op);
  770. ot.Legalize(op, p);
  771. // Remap dEdge
  772. ot.SetDelaunayEdgeCCW(p, de1);
  773. t.SetDelaunayEdgeCW(p, de2);
  774. t.SetDelaunayEdgeCCW(op, de3);
  775. ot.SetDelaunayEdgeCW(op, de4);
  776. // Remap cEdge
  777. ot.SetConstrainedEdgeCCW(p, ce1);
  778. t.SetConstrainedEdgeCW(p, ce2);
  779. t.SetConstrainedEdgeCCW(op, ce3);
  780. ot.SetConstrainedEdgeCW(op, ce4);
  781. // Remap neighbors
  782. // XXX: might optimize the markNeighbor by keeping track of
  783. // what side should be assigned to what neighbor after the
  784. // rotation. Now mark neighbor does lots of testing to find
  785. // the right side.
  786. t.Neighbors.Clear();
  787. ot.Neighbors.Clear();
  788. if (n1 != null) ot.MarkNeighbor(n1);
  789. if (n2 != null) t.MarkNeighbor(n2);
  790. if (n3 != null) t.MarkNeighbor(n3);
  791. if (n4 != null) ot.MarkNeighbor(n4);
  792. t.MarkNeighbor(ot);
  793. }
  794. }
  795. }