public override void Run() { // Algorithm Setup: save input points var layer = History.CreateAndAddNewLayer("Setup (sorting input)"); layer.AddCommand(new UpdatePointSetCommand { Label = "Input", Points = AlgorithmUtil.CopyVectorList(InputPoints) }); layer.AddCommand(new HighlightLiveCodeSectionsCommand(AlgorithmId, SECTION_SORT_START, SECTION_SORT)); SortedInput = new List <Vector>(); // algorithm step 1: sort, saved sorted input points foreach (var v in InputPoints) { SortedInput.Add(new Vector { X = v.X, Y = v.Y, Alternates = v.Alternates }); } SortedInput.Sort(GrahamSort); layer.AddCommand(new UpdatePointSetCommand { Label = "Sorted Input", Points = AlgorithmUtil.CopyVectorList(SortedInput) }); // check degenerate case #1: 0,1,2 points layer = History.CreateAndAddNewLayer("Degenerate Case Checks"); if (SortedInput.Count <= 2) { layer.AddCommand(new AddTextStatusCommand { AssociatedAlgorithm = this, Comments = "Algorithm cannot execute, it requires at least 3 points" }); layer.AddCommand(new HighlightLiveCodeSectionsCommand(AlgorithmId, SECTION_DEGENERATE)); return; } // check degenerate case #2: all points on same line if (AllPointsCollinear()) { layer.AddCommand(new AddTextStatusCommand { AssociatedAlgorithm = this, Comments = "Algorithm cannot execute if all points are collinear" }); layer.AddCommand(new HighlightLiveCodeSectionsCommand(AlgorithmId, SECTION_DEGENERATE)); return; } // points cannot be collinear, points must be at least 3 Stack <Vector> grahamStack = new Stack <Vector>(); grahamStack.Push(SortedInput[0]); grahamStack.Push(SortedInput[1]); // starting points: first two sorted points, pushed onto stack // STATIC LAYER: "Starting Points", has only input points // layer commentary: if 0, 1 or 2 points, cannot perform graham scan // if all points on same line, cannot perform graham scan layer = History.CreateAndAddNewLayer("Initiailzation"); layer.AddCommand(new AlgorithmStartingCommand { AssociatedAlgorithm = this }); layer.AddCommand(new HighlightLiveCodeSectionsCommand(AlgorithmId, SECTION_PRE)); var hip = new HighlightPointsCommand { AssociatedAlgorithm = this, HighlightLevel = 1 }; hip.Points.Add(new Vector { X = SortedInput[0].X, Y = SortedInput[0].Y }); hip.Points.Add(new Vector { X = SortedInput[1].X, Y = SortedInput[1].Y }); layer.AddCommand(hip); layer.AddCommand(new VectorProcessingStackUpdatedCommand { AssociatedAlgorithm = this, ProcessingStack = AlgorithmUtil.CopyVectorStack(grahamStack), Comments = "Initial stack" }); AddStackLinesToLayer(layer, grahamStack); for (int i = 2; i < SortedInput.Count; i++) { layer = History.CreateAndAddNewLayer("Processing Point, index=" + i); layer.AddCommand(new AlgorithmStepStartingCommand { AssociatedAlgorithm = this, Comments = "Step Starting" }); layer.AddCommand(new ClearPointHighlightsCommand { AssociatedAlgorithm = this, Comments = "" }); layer.AddCommand(new ClearTextStatusCommand { AssociatedAlgorithm = this, Comments = "" }); // NORMAL LAYER: "Algorithm Layer (i-2)" // Commentary: "We examine the next point in sorted order with the top two // elements from the stack status structure, popping the first one" // stack visualization: highlight top of stack, label it "tail" // standalone visualization: show SortedInput[i] as "head" // show popped element as "middle" // highlight the "head" point in yellow, and the "middle" / "tail" points in green // layer commentary: // loop iteration "i" Vector head = SortedInput[i]; Vector middle = grahamStack.Pop(); Vector tail = grahamStack.Peek(); layer.AddCommand(new HighlightInputPointCommand { AssociatedAlgorithm = this, Comments = "Next input point", X = head.X, Y = head.Y, HightlightLevel = 1 }); hip = new HighlightPointsCommand { AssociatedAlgorithm = this, HighlightLevel = 1 }; hip.Points.Add(new Vector { X = head.X, Y = head.Y }); hip.Points.Add(new Vector { X = middle.X, Y = middle.Y }); hip.Points.Add(new Vector { X = tail.X, Y = tail.Y }); layer.AddCommand(hip); layer.AddCommand(new HighlightInputPointCommand { AssociatedAlgorithm = this, Comments = "Freshly popped from top of stack", X = middle.X, Y = middle.Y, HightlightLevel = 2 }); layer.AddCommand(new HighlightInputPointCommand { AssociatedAlgorithm = this, Comments = "Top of stack, which is left on stack and peeked", X = tail.X, Y = tail.Y, HightlightLevel = 2 }); // we examine next point in sorted list, with top element of stack (which we pop) // and 2nd element of stack (which we leave as new top of stack) // Commentary: "The turn direction of these three points is calculated using // the cross product of these three points" int turn = GeomMath.GetTurnDirection(tail, middle, head); // determine "turn" of these 3 points, in sequence tail / middle / head string turnString = "Counter-clockwise"; if (turn == GeomMath.DIRECTION_CLOCKWISE) { turnString = "Clockwise"; } else if (turn == GeomMath.DIRECTION_NONE) { turnString = "None"; } layer.AddCommand(new AddTextStatusCommand { AssociatedAlgorithm = this, Comments = "Computed turn: " + turnString }); // Standalone visualization: "The turn direction for these three points is: " switch (turn) { case GeomMath.DIRECTION_COUNTERCLOCKWISE: layer.AddCommand(new AddTextStatusCommand { AssociatedAlgorithm = this, Comments = "The point popped is pushed back onto stack since it is part of the hull" }); layer.AddCommand(new AddTextStatusCommand { AssociatedAlgorithm = this, Comments = "The input point is also pushed since it is potentially part of the hull" }); layer.AddCommand(new HighlightLiveCodeSectionsCommand(AlgorithmId, SECTION_COUNTERCLOCKWISE)); grahamStack.Push(middle); grahamStack.Push(head); layer.AddCommand(new VectorProcessingStackUpdatedCommand { AssociatedAlgorithm = this, ProcessingStack = AlgorithmUtil.CopyVectorStack(grahamStack), Comments = "Updated processing stack" }); break; case GeomMath.DIRECTION_CLOCKWISE: layer.AddCommand(new AddTextStatusCommand { AssociatedAlgorithm = this, Comments = "The point on the top of the stack is discarded, but the input point is preserved for re-consideration" }); layer.AddCommand(new HighlightLiveCodeSectionsCommand(AlgorithmId, SECTION_CLOCKWISE)); i--; break; case GeomMath.DIRECTION_NONE: layer.AddCommand(new AddTextStatusCommand { AssociatedAlgorithm = this, Comments = "Input point is co-linear with other points, so it is part of the hull" }); layer.AddCommand(new HighlightLiveCodeSectionsCommand(AlgorithmId, SECTION_NONE)); grahamStack.Push(head); layer.AddCommand(new VectorProcessingStackUpdatedCommand { AssociatedAlgorithm = this, ProcessingStack = AlgorithmUtil.CopyVectorStack(grahamStack), Comments = "Updated processing stack" }); break; } AddStackLinesToLayer(layer, grahamStack); } layer = History.CreateAndAddNewLayer("Final Results"); layer.AddCommand(new AlgorithmCompleteCommand { AssociatedAlgorithm = this }); layer.AddCommand(new ClearPointHighlightsCommand { AssociatedAlgorithm = this, Comments = "" }); layer.AddCommand(new ClearTextStatusCommand { AssociatedAlgorithm = this, Comments = "" }); layer.AddCommand(new HighlightLiveCodeSectionsCommand(AlgorithmId, SECTION_POST)); grahamStack.Push(SortedInput[0]); layer.AddCommand(new VectorProcessingStackUpdatedCommand { AssociatedAlgorithm = this, ProcessingStack = AlgorithmUtil.CopyVectorStack(grahamStack), Comments = "Final stack, first input point is pushed to complete the hull" }); AddStackLinesToLayer(layer, grahamStack); Hull = new PolygonModel(); var a = grahamStack.ToArray <Vector>(); for (int i = 0; i < a.Length - 1; i++) { var ap = a[i].Alternates; var ap2 = a[i + 1].Alternates; if (ap != null && ap2 != null) { // Main operation Hull.Lines.Add(new LineModel { StartPoint = new Vector { X = a[i].X, Y = a[i].Y, Alternates = new CanvasPoint { DotIndexLeft = ap.DotIndexLeft, DotIndexTop = ap.DotIndexTop } }, EndPoint = new Vector { X = a[i + 1].X, Y = a[i + 1].Y, Alternates = new CanvasPoint { DotIndexLeft = ap2.DotIndexLeft, DotIndexTop = ap2.DotIndexTop } } }); } else { // Side operation that doesn't involve points from grid Hull.Lines.Add(new LineModel { StartPoint = new Vector { X = a[i].X, Y = a[i].Y }, EndPoint = new Vector { X = a[i + 1].X, Y = a[i + 1].Y } }); } } }
public override void Run() { if (InputPoints == null || InputPoints.Count < 3) { return; } double minx = InputPoints[0].X; double miny = InputPoints[0].Y; double maxx = minx; double maxy = miny; for (int i = 0; i < InputPoints.Count; i++) { var v = InputPoints[i]; if (v.X < minx) { minx = v.X; } if (v.Y < miny) { miny = v.Y; } if (v.X > maxx) { maxx = v.X; } if (v.Y > maxy) { maxy = v.Y; } } double dx = maxx - minx; double dy = maxy - miny; double deltaMax = Math.Max(dx, dy); double midx = 0.5 * (minx + maxx); double midy = 0.5 * (miny + maxy); Vector p1 = new Vector { X = midx - 20 * deltaMax, Y = midy - deltaMax }; Vector p2 = new Vector { X = midx, Y = midy + 20 * deltaMax }; Vector p3 = new Vector { X = midx + 20 * deltaMax, Y = midy - deltaMax }; Triangles.Clear(); Triangles.Add(new DelaunayTriangle { V1 = p1, V2 = p2, V3 = p3 }); for (int i = 0; i < InputPoints.Count; i++) { Vector p = InputPoints[i]; List <DelaunayEdge> polygon = new List <DelaunayEdge>(); var badTrianglesLayer = History.CreateAndAddNewLayer("Identifying bad triangles"); AddTriangleLinesToLayer(badTrianglesLayer); foreach (var t in Triangles) { if (t.CircumcircleContainsVertex(p)) { t.IsBad = true; polygon.Add(new DelaunayEdge(t.V1, t.V2)); polygon.Add(new DelaunayEdge(t.V2, t.V3)); polygon.Add(new DelaunayEdge(t.V3, t.V1)); var hip = new HighlightPointsCommand { AssociatedAlgorithm = this, HighlightLevel = 1 }; hip.Points.Add(new Vector { X = t.V1.X, Y = t.V1.Y }); hip.Points.Add(new Vector { X = t.V2.X, Y = t.V2.Y }); hip.Points.Add(new Vector { X = t.V3.X, Y = t.V3.Y }); badTrianglesLayer.AddCommand(hip); } } RemoveBadTriangles(); for (int c = 0; c < polygon.Count; c++) { for (int d = c + 1; d < polygon.Count; d++) { if (polygon[c].AlmostEquals(polygon[d])) { polygon[c].BadEdge = true; polygon[d].BadEdge = true; } } } RemoveBadEdges(polygon); foreach (var poly in polygon) { Triangles.Add(new DelaunayTriangle { V1 = poly.VStart, V2 = poly.VEnd, V3 = p }); } var triLayer = History.CreateAndAddNewLayer("for loop iteration i=" + i); AddTriangleLinesToLayer(triLayer); } RemoveTrianglesWithVertex(p1, p2, p3); foreach (var t in Triangles) { Edges.Add(new DelaunayEdge { VStart = t.V1, VEnd = t.V2 }); Edges.Add(new DelaunayEdge { VStart = t.V2, VEnd = t.V3 }); Edges.Add(new DelaunayEdge { VStart = t.V3, VEnd = t.V1 }); } var layer = History.CreateAndAddNewLayer("Final Result"); AddEdgesToLayer(layer); }