/// <summary> /// Hit-testing for stroke erase scenario. /// </summary> /// <param name="iterator">the stroke nodes to iterate</param> /// <returns>true if the strokes intersect, false otherwise</returns> internal bool HitTest(StrokeNodeIterator iterator) { System.Diagnostics.Debug.Assert(iterator != null); if ((_erasingStrokeNodes == null) || (_erasingStrokeNodes.Count == 0)) { return(false); } Rect inkSegmentBounds = Rect.Empty; for (int i = 0; i < iterator.Count; i++) { StrokeNode inkStrokeNode = iterator[i]; Rect inkNodeBounds = inkStrokeNode.GetBounds(); inkSegmentBounds.Union(inkNodeBounds); if (inkSegmentBounds.IntersectsWith(_bounds)) { // foreach (StrokeNode erasingStrokeNode in _erasingStrokeNodes) { if (inkSegmentBounds.IntersectsWith(erasingStrokeNode.GetBoundsConnected()) && erasingStrokeNode.HitTest(inkStrokeNode)) { return(true); } } } } return(false); }
/// <summary> /// Merge two crossings into one. /// </summary> /// <param name="crossing"></param> /// <returns>Return true if these two crossings are actually overlapping and merged; false otherwise</returns> public bool Merge(LassoCrossing crossing) { if (crossing.IsEmpty) { return(false); } if (FIndices.IsEmpty && !crossing.IsEmpty) { FIndices = crossing.FIndices; StartNode = crossing.StartNode; EndNode = crossing.EndNode; return(true); } if (DoubleUtil.GreaterThanOrClose(crossing.FIndices.EndFIndex, FIndices.BeginFIndex) && DoubleUtil.GreaterThanOrClose(FIndices.EndFIndex, crossing.FIndices.BeginFIndex)) { if (DoubleUtil.LessThan(crossing.FIndices.BeginFIndex, FIndices.BeginFIndex)) { FIndices.BeginFIndex = crossing.FIndices.BeginFIndex; StartNode = crossing.StartNode; } if (DoubleUtil.GreaterThan(crossing.FIndices.EndFIndex, FIndices.EndFIndex)) { FIndices.EndFIndex = crossing.FIndices.EndFIndex; EndNode = crossing.EndNode; } return(true); } return(false); }
/// <summary> /// Generates stroke nodes along a given path. /// Drops any previously genererated nodes. /// </summary> /// <param name="path"></param> internal void MoveTo(IEnumerable <Point> path) { System.Diagnostics.Debug.Assert((path != null) && (IEnumerablePointHelper.GetCount(path) != 0)); Point[] points = IEnumerablePointHelper.GetPointArray(path); if (_erasingStrokeNodes == null) { _erasingStrokeNodes = new List <StrokeNode>(points.Length); } else { _erasingStrokeNodes.Clear(); } _bounds = Rect.Empty; _nodeIterator = _nodeIterator.GetIteratorForNextSegment(points.Length > 1 ? FilterPoints(points) : points); for (int i = 0; i < _nodeIterator.Count; i++) { StrokeNode strokeNode = _nodeIterator[i]; _bounds.Union(strokeNode.GetBoundsConnected()); _erasingStrokeNodes.Add(strokeNode); } #if POINTS_FILTER_TRACE _totalPointsAdded += path.Length; System.Diagnostics.Debug.WriteLine(String.Format("Total Points added: {0} screened: {1} collinear screened: {2}", _totalPointsAdded, _totalPointsScreened, _collinearPointsScreened)); #endif }
/// <summary> /// Hit-testing for stroke erase scenario. /// </summary> /// <param name="iterator">the stroke nodes to iterate</param> /// <returns>true if the strokes intersect, false otherwise</returns> internal bool HitTest(StrokeNodeIterator iterator) { System.Diagnostics.Debug.Assert(iterator != null); if ((_erasingStrokeNodes == null) || (_erasingStrokeNodes.Count == 0)) { return(false); } Rect inkSegmentBounds = Rect.Empty; for (int i = 0; i < iterator.Count; i++) { StrokeNode inkStrokeNode = iterator[i]; Rect inkNodeBounds = inkStrokeNode.GetBounds(); inkSegmentBounds.Union(inkNodeBounds); if (inkSegmentBounds.IntersectsWith(_bounds)) { for (var index = 0; index < _erasingStrokeNodes.Count; index++) { var erasingStrokeNodeBound = _erasingStrokeNodeBounds[index]; if (inkSegmentBounds.IntersectsWith(erasingStrokeNodeBound)) { StrokeNode erasingStrokeNode = _erasingStrokeNodes[index]; if (erasingStrokeNode.HitTest(inkStrokeNode)) { return(true); } } } } } return(false); }
/// <summary> /// Constructor /// </summary> /// <param name="newFIndices"></param> /// <param name="strokeNode"></param> public LassoCrossing(StrokeFIndices newFIndices, StrokeNode strokeNode) { System.Diagnostics.Debug.Assert(!newFIndices.IsEmpty); System.Diagnostics.Debug.Assert(strokeNode.IsValid); FIndices = newFIndices; StartNode = EndNode = strokeNode; }
/// <summary> /// This method tells whether the contour of a given stroke node /// intersects with the contour of this node. The contours of both nodes /// include their connecting quadrangles. /// </summary> /// <param name="hitNode"></param> /// <returns></returns> internal bool HitTest(StrokeNode hitNode) { if (!IsValid || !hitNode.IsValid) { return(false); } IEnumerable <ContourSegment> hittingContour = hitNode.GetContourSegments(); return(_operations.HitTest(_lastNode, _thisNode, ConnectingQuad, hittingContour)); }
/// <summary> /// Finds out if a given node intersects with this one, /// and returns findices of the intersection. /// </summary> /// <param name="hitNode"></param> /// <returns></returns> internal StrokeFIndices CutTest(StrokeNode hitNode) { if ((IsValid == false) || (hitNode.IsValid == false)) { return(StrokeFIndices.Empty); } IEnumerable <ContourSegment> hittingContour = hitNode.GetContourSegments(); // If the node contours intersect, the result is a pair of findices // this segment should be cut at to let the hitNode's contour through it. StrokeFIndices cutAt = _operations.CutTest(_lastNode, _thisNode, ConnectingQuad, hittingContour); return((_index == 0) ? cutAt : BindFIndices(cutAt)); }
/// <summary> /// Helper function to find out whether a point is inside the lasso /// </summary> private bool SegmentWithinLasso(StrokeNode strokeNode, double fIndex) { bool currentSegmentWithinLasso; if (DoubleUtil.AreClose(fIndex, StrokeFIndices.BeforeFirst)) { // This should check against the very first stroke node currentSegmentWithinLasso = this.Contains(strokeNode.GetPointAt(0f)); } else if (DoubleUtil.AreClose(fIndex, StrokeFIndices.AfterLast)) { // This should check against the last stroke node currentSegmentWithinLasso = this.Contains(strokeNode.Position); } else { currentSegmentWithinLasso = this.Contains(strokeNode.GetPointAt(fIndex)); } return(currentSegmentWithinLasso); }
/// <summary> /// GetPointsAtMiddleSegment /// </summary> internal void GetPointsAtMiddleSegment( StrokeNode previous, double angleBetweenNodes, List<Point> abPoints, List<Point> dcPoints, out bool missingIntersection #if DEBUG_RENDERING_FEEDBACK , DrawingContext debugDC, double feedbackSize, bool showFeedback #endif ) { missingIntersection = false; if (IsValid && previous.IsValid) { Quad quad1 = previous.ConnectingQuad; if (!quad1.IsEmpty) { Quad quad2 = ConnectingQuad; if (!quad2.IsEmpty) { if (IsEllipse) { Rect node1Bounds = _operations.GetNodeBounds(previous._lastNode); Rect node2Bounds = _operations.GetNodeBounds(_lastNode); Rect node3Bounds = _operations.GetNodeBounds(_thisNode); #if DEBUG_RENDERING_FEEDBACK if (showFeedback) { debugDC.DrawEllipse(null, new Pen(Brushes.Pink, feedbackSize / 2), _lastNode.Position, node2Bounds.Width / 2, node2Bounds.Height / 2); } #endif if (angleBetweenNodes == 0.0d || ((quad1.B == quad2.A) && (quad1.C == quad2.D))) { //quads connections are the same, just add them abPoints.Add(quad1.B); dcPoints.Add(quad1.C); #if DEBUG_RENDERING_FEEDBACK if (showFeedback) { debugDC.DrawEllipse(Brushes.Green, null, quad1.B, feedbackSize, feedbackSize); debugDC.DrawEllipse(Brushes.Yellow, null, quad1.C, feedbackSize, feedbackSize); } #endif } else if (angleBetweenNodes > 0.0) { //the stroke angled towards the AB side //this part is easy if (quad1.B == quad2.A) { abPoints.Add(quad1.B); #if DEBUG_RENDERING_FEEDBACK if (showFeedback) { debugDC.DrawEllipse(Brushes.Green, null, quad1.B, feedbackSize, feedbackSize); } #endif } else { Point intersection = GetIntersection(quad1.A, quad1.B, quad2.A, quad2.B); Rect union = Rect.Union(node1Bounds, node2Bounds); union.Inflate(1.0, 1.0); //make sure we're not off in space #if DEBUG_RENDERING_FEEDBACK if (showFeedback) { debugDC.DrawEllipse(Brushes.Green, null, quad1.B, feedbackSize * 1.5, feedbackSize * 1.5); debugDC.DrawEllipse(Brushes.Red, null, quad2.A, feedbackSize, feedbackSize); } #endif if (union.Contains(intersection)) { abPoints.Add(intersection); #if DEBUG_RENDERING_FEEDBACK if (showFeedback) { debugDC.DrawEllipse(Brushes.Orange, null, intersection, feedbackSize, feedbackSize); } #endif } else { //if we missed the intersection we'll need to close the stroke segment //this work is done in StrokeRenderer missingIntersection = true; return; //we're done. } } if (quad1.C == quad2.D) { dcPoints.Add(quad1.C); #if DEBUG_RENDERING_FEEDBACK if (showFeedback) { debugDC.DrawEllipse(Brushes.Yellow, null, quad1.C, feedbackSize, feedbackSize); } #endif } else { //add instructions to arc from quad1.C to quad2.D in reverse order (since we walk this array backwards to render) dcPoints.Add(quad1.C); dcPoints.Add(new Point(node2Bounds.Width, node2Bounds.Height)); dcPoints.Add(StrokeRenderer.ArcToMarker); dcPoints.Add(quad2.D); #if DEBUG_RENDERING_FEEDBACK if (showFeedback) { debugDC.DrawEllipse(Brushes.Yellow, null, quad1.C, feedbackSize, feedbackSize); debugDC.DrawEllipse(Brushes.Blue, null, quad2.D, feedbackSize, feedbackSize); } #endif } } else { //the stroke angled towards the CD side //this part is easy if (quad1.C == quad2.D) { dcPoints.Add(quad1.C); #if DEBUG_RENDERING_FEEDBACK if (showFeedback) { debugDC.DrawEllipse(Brushes.Yellow, null, quad1.C, feedbackSize, feedbackSize); } #endif } else { Point intersection = GetIntersection(quad1.D, quad1.C, quad2.D, quad2.C); Rect union = Rect.Union(node1Bounds, node2Bounds); union.Inflate(1.0, 1.0); //make sure we're not off in space #if DEBUG_RENDERING_FEEDBACK if (showFeedback) { debugDC.DrawEllipse(Brushes.Yellow, null, quad1.C, feedbackSize * 1.5, feedbackSize * 1.5); debugDC.DrawEllipse(Brushes.Blue, null, quad2.D, feedbackSize, feedbackSize); } #endif if (union.Contains(intersection)) { dcPoints.Add(intersection); #if DEBUG_RENDERING_FEEDBACK if (showFeedback) { debugDC.DrawEllipse(Brushes.Orange, null, intersection, feedbackSize, feedbackSize); } #endif } else { //if we missed the intersection we'll need to close the stroke segment //this work is done in StrokeRenderer missingIntersection = true; return; //we're done. } } if (quad1.B == quad2.A) { abPoints.Add(quad1.B); #if DEBUG_RENDERING_FEEDBACK if (showFeedback) { debugDC.DrawEllipse(Brushes.Green, null, quad1.B, feedbackSize, feedbackSize); } #endif } else { //we need to arc between quad1.B and quad2.A along node2 abPoints.Add(quad1.B); abPoints.Add(StrokeRenderer.ArcToMarker); abPoints.Add(new Point(node2Bounds.Width, node2Bounds.Height)); abPoints.Add(quad2.A); #if DEBUG_RENDERING_FEEDBACK if (showFeedback) { debugDC.DrawEllipse(Brushes.Green, null, quad1.B, feedbackSize, feedbackSize); debugDC.DrawEllipse(Brushes.Red, null, quad2.A, feedbackSize, feedbackSize); } #endif } } } else { //rectangle int indexA = -1; int indexB = -1; int indexC = -1; int indexD = -1; Vector[] vertices = _operations.GetVertices(); double pressureFactor = _lastNode.PressureFactor; for (int i = 0; i < vertices.Length; i++) { Point point = _lastNode.Position + (vertices[i % vertices.Length] * pressureFactor); if (point == quad2.A) { indexA = i; } if (point == quad1.B) { indexB = i; } if (point == quad1.C) { indexC = i; } if (point == quad2.D) { indexD = i; } } if (indexA == -1 || indexB == -1 || indexC == -1 || indexD == -1) { Debug.Assert(false, "Couldn't find all 4 indexes in StrokeNodeOperations.GetPointsAtMiddleSegment"); return; } #if DEBUG_RENDERING_FEEDBACK if (showFeedback) { debugDC.DrawRectangle(null, new Pen(Brushes.Pink, feedbackSize / 2), _operations.GetNodeBounds(_lastNode)); debugDC.DrawEllipse(Brushes.Red, null, quad2.A, feedbackSize, feedbackSize); debugDC.DrawEllipse(Brushes.Green, null, quad1.B, feedbackSize, feedbackSize); debugDC.DrawEllipse(Brushes.Yellow, null, quad1.C, feedbackSize, feedbackSize); debugDC.DrawEllipse(Brushes.Blue, null, quad2.D, feedbackSize, feedbackSize); } #endif Rect node3Rect = _operations.GetNodeBounds(_thisNode); //take care of a-b first if (indexA == indexB) { //quad connection is the same, just add it if (!node3Rect.Contains(quad1.B)) { abPoints.Add(quad1.B); } } else if ((indexA == 0 && indexB == 3) || ((indexA != 3 || indexB != 0) && (indexA > indexB))) { if (!node3Rect.Contains(quad1.B)) { abPoints.Add(quad1.B); } if (!node3Rect.Contains(quad2.A)) { abPoints.Add(quad2.A); } } else { Point intersection = GetIntersection(quad1.A, quad1.B, quad2.A, quad2.B); Rect node12 = Rect.Union(_operations.GetNodeBounds(previous._lastNode), _operations.GetNodeBounds(_lastNode)); node12.Inflate(1.0, 1.0); //make sure we're not off in space if (node12.Contains(intersection)) { abPoints.Add(intersection); #if DEBUG_RENDERING_FEEDBACK if (showFeedback) { debugDC.DrawEllipse(Brushes.Orange, null, intersection, feedbackSize, feedbackSize * 1.5); } #endif } else { //if we missed the intersection we'll need to close the stroke segment //this work is done in StrokeRenderer. missingIntersection = true; return; //we're done. } } // now take care of c-d. if (indexC == indexD) { //quad connection is the same, just add it if (!node3Rect.Contains(quad1.C)) { dcPoints.Add(quad1.C); } } else if ((indexC == 0 && indexD == 3) || ((indexC != 3 || indexD != 0) && (indexC > indexD))) { if (!node3Rect.Contains(quad1.C)) { dcPoints.Add(quad1.C); } if (!node3Rect.Contains(quad2.D)) { dcPoints.Add(quad2.D); } } else { Point intersection = GetIntersection(quad1.D, quad1.C, quad2.D, quad2.C); Rect node12 = Rect.Union(_operations.GetNodeBounds(previous._lastNode), _operations.GetNodeBounds(_lastNode)); node12.Inflate(1.0, 1.0); //make sure we're not off in space if (node12.Contains(intersection)) { dcPoints.Add(intersection); #if DEBUG_RENDERING_FEEDBACK if (showFeedback) { debugDC.DrawEllipse(Brushes.Orange, null, intersection, feedbackSize, feedbackSize * 1.5); } #endif } else { //if we missed the intersection we'll need to close the stroke segment //this work is done in StrokeRenderer. missingIntersection = true; return; //we're done. } } } } } } }
/// <summary> /// Hit-testing for point erase. /// </summary> /// <param name="iterator"></param> /// <param name="intersections"></param> /// <returns></returns> internal bool EraseTest(StrokeNodeIterator iterator, List <StrokeIntersection> intersections) { System.Diagnostics.Debug.Assert(iterator != null); System.Diagnostics.Debug.Assert(intersections != null); intersections.Clear(); List <StrokeFIndices> eraseAt = new List <StrokeFIndices>(); if ((_erasingStrokeNodes == null) || (_erasingStrokeNodes.Count == 0)) { return(false); } Rect inkSegmentBounds = Rect.Empty; for (int x = 0; x < iterator.Count; x++) { StrokeNode inkStrokeNode = iterator[x]; Rect inkNodeBounds = inkStrokeNode.GetBounds(); inkSegmentBounds.Union(inkNodeBounds); if (inkSegmentBounds.IntersectsWith(_bounds)) { // int index = eraseAt.Count; foreach (StrokeNode erasingStrokeNode in _erasingStrokeNodes) { if (false == inkSegmentBounds.IntersectsWith(erasingStrokeNode.GetBoundsConnected())) { continue; } StrokeFIndices fragment = inkStrokeNode.CutTest(erasingStrokeNode); if (fragment.IsEmpty) { continue; } // Merge it with the other results for this ink segment bool inserted = false; for (int i = index; i < eraseAt.Count; i++) { StrokeFIndices lastFragment = eraseAt[i]; if (fragment.BeginFIndex < lastFragment.EndFIndex) { // If the fragments overlap, merge them if (fragment.EndFIndex > lastFragment.BeginFIndex) { fragment = new StrokeFIndices( Math.Min(lastFragment.BeginFIndex, fragment.BeginFIndex), Math.Max(lastFragment.EndFIndex, fragment.EndFIndex)); // If the fragment doesn't go beyond lastFragment, break if ((fragment.EndFIndex <= lastFragment.EndFIndex) || ((i + 1) == eraseAt.Count)) { inserted = true; eraseAt[i] = fragment; break; } else { eraseAt.RemoveAt(i); i--; } } // insert otherwise else { eraseAt.Insert(i, fragment); inserted = true; break; } } } // If not merged nor inserted, add it to the end of the list if (false == inserted) { eraseAt.Add(fragment); } // Break out if the entire ink segment is hit - {BeforeFirst, AfterLast} if (eraseAt[eraseAt.Count - 1].IsFull) { break; } } // Merge inter-segment overlapping fragments if ((index > 0) && (index < eraseAt.Count)) { StrokeFIndices lastFragment = eraseAt[index - 1]; if (DoubleUtil.AreClose(lastFragment.EndFIndex, StrokeFIndices.AfterLast)) { if (DoubleUtil.AreClose(eraseAt[index].BeginFIndex, StrokeFIndices.BeforeFirst)) { lastFragment.EndFIndex = eraseAt[index].EndFIndex; eraseAt[index - 1] = lastFragment; eraseAt.RemoveAt(index); } else { lastFragment.EndFIndex = inkStrokeNode.Index; eraseAt[index - 1] = lastFragment; } } } } // Start next ink segment inkSegmentBounds = inkNodeBounds; } if (eraseAt.Count != 0) { foreach (StrokeFIndices segment in eraseAt) { intersections.Add(new StrokeIntersection(segment.BeginFIndex, StrokeFIndices.AfterLast, StrokeFIndices.BeforeFirst, segment.EndFIndex)); } } return(eraseAt.Count != 0); }
internal static void CalcGeometryAndBounds(StrokeNodeIterator iterator, DrawingAttributes drawingAttributes, #if DEBUG_RENDERING_FEEDBACK DrawingContext debugDC, double feedbackSize, bool showFeedback, #endif bool calculateBounds, out Geometry geometry, out Rect bounds) { Debug.Assert(iterator != null && drawingAttributes != null); //we can use our new algorithm for identity only. Matrix stylusTipTransform = drawingAttributes.StylusTipTransform; if (stylusTipTransform != Matrix.Identity && stylusTipTransform._type != MatrixTypes.TRANSFORM_IS_SCALING) { //second best optimization CalcGeometryAndBoundsWithTransform(iterator, drawingAttributes, stylusTipTransform._type, calculateBounds, out geometry, out bounds); } else { StreamGeometry streamGeometry = new StreamGeometry(); streamGeometry.FillRule = FillRule.Nonzero; StreamGeometryContext context = streamGeometry.Open(); geometry = streamGeometry; Rect empty = Rect.Empty; bounds = empty; try { // // We keep track of three StrokeNodes as we iterate across // the Stroke. Since these are structs, the default ctor will // be called and .IsValid will be false until we initialize them // StrokeNode emptyStrokeNode = new StrokeNode(); StrokeNode prevPrevStrokeNode = new StrokeNode(); StrokeNode prevStrokeNode = new StrokeNode(); StrokeNode strokeNode = new StrokeNode(); Rect prevPrevStrokeNodeBounds = empty; Rect prevStrokeNodeBounds = empty; Rect strokeNodeBounds = empty; //percentIntersect is a function of drawingAttributes height / width double percentIntersect = 95d; double maxExtent = Math.Max(drawingAttributes.Height, drawingAttributes.Width); percentIntersect += Math.Min(4.99999d, ((maxExtent / 20d) * 5d)); double prevAngle = double.MinValue; bool isStartOfSegment = true; bool isEllipse = drawingAttributes.StylusTip == StylusTip.Ellipse; bool ignorePressure = drawingAttributes.IgnorePressure; // // Two List<Point>'s that get reused for adding figures // to the streamgeometry. // List<Point> pathFigureABSide = new List<Point>();//don't prealloc. It causes Gen2 collections to rise and doesn't help execution time List<Point> pathFigureDCSide = new List<Point>(); List<Point> polyLinePoints = new List<Point>(4); int iteratorCount = iterator.Count; for (int index = 0, previousIndex = -1; index < iteratorCount; ) { if (!prevPrevStrokeNode.IsValid) { if (prevStrokeNode.IsValid) { //we're sliding our pointers forward prevPrevStrokeNode = prevStrokeNode; prevPrevStrokeNodeBounds = prevStrokeNodeBounds; prevStrokeNode = emptyStrokeNode; } else { prevPrevStrokeNode = iterator[index++, previousIndex++]; prevPrevStrokeNodeBounds = prevPrevStrokeNode.GetBounds(); continue; //so we always check if index < iterator.Count } } //we know prevPrevStrokeNode is valid if (!prevStrokeNode.IsValid) { if (strokeNode.IsValid) { //we're sliding our pointers forward prevStrokeNode = strokeNode; prevStrokeNodeBounds = strokeNodeBounds; strokeNode = emptyStrokeNode; } else { //get the next strokeNode, but don't automatically update previousIndex prevStrokeNode = iterator[index++, previousIndex]; prevStrokeNodeBounds = prevStrokeNode.GetBounds(); RectCompareResult result = FuzzyContains( prevStrokeNodeBounds, prevPrevStrokeNodeBounds, isStartOfSegment ? 99.99999d : percentIntersect); if (result == RectCompareResult.Rect1ContainsRect2) { // this node already contains the prevPrevStrokeNodeBounds (PP): // // |------------| // | |----| | // | | PP | P | // | |----| | // |------------| // prevPrevStrokeNode = iterator[index - 1, prevPrevStrokeNode.Index - 1]; ; prevPrevStrokeNodeBounds = Rect.Union(prevStrokeNodeBounds, prevPrevStrokeNodeBounds); // at this point prevPrevStrokeNodeBounds already contains this node // we can just ignore this node prevStrokeNode = emptyStrokeNode; // update previousIndex to point to this node previousIndex = index - 1; // go back to our main loop continue; } else if (result == RectCompareResult.Rect2ContainsRect1) { // this prevPrevStrokeNodeBounds (PP) already contains this node: // // |------------| // | |----|| // | PP | P || // | |----|| // |------------| // //prevPrevStrokeNodeBounds already contains this node //we can just ignore this node prevStrokeNode = emptyStrokeNode; // go back to our main loop, but do not update previousIndex // because it should continue to point to previousPrevious continue; } Debug.Assert(!prevStrokeNode.GetConnectingQuad().IsEmpty, "prevStrokeNode.GetConnectingQuad() is Empty!"); // if neither was true, we now have two of our three nodes required to // start our computation, we need to update previousIndex to point // to our current, valid prevStrokeNode previousIndex = index - 1; continue; //so we always check if index < iterator.Count } } //we know prevPrevStrokeNode and prevStrokeNode are both valid if (!strokeNode.IsValid) { strokeNode = iterator[index++, previousIndex]; strokeNodeBounds = strokeNode.GetBounds(); RectCompareResult result = FuzzyContains( strokeNodeBounds, prevStrokeNodeBounds, isStartOfSegment ? 99.99999 : percentIntersect); RectCompareResult result2 = FuzzyContains( strokeNodeBounds, prevPrevStrokeNodeBounds, isStartOfSegment ? 99.99999 : percentIntersect); if ( isStartOfSegment && result == RectCompareResult.Rect1ContainsRect2 && result2 == RectCompareResult.Rect1ContainsRect2) { if (pathFigureABSide.Count > 0) { //we've started a stroke, we need to end it before resetting //prevPrev #if DEBUG_RENDERING_FEEDBACK prevStrokeNode.GetPointsAtEndOfSegment(pathFigureABSide, pathFigureDCSide, debugDC, feedbackSize, showFeedback); #else prevStrokeNode.GetPointsAtEndOfSegment(pathFigureABSide, pathFigureDCSide); #endif //render ReverseDCPointsRenderAndClear(context, pathFigureABSide, pathFigureDCSide, polyLinePoints, isEllipse, true/*clear the point collections*/); } //we're resetting //prevPrevStrokeNode. We need to gen one //without a connecting quad prevPrevStrokeNode = iterator[index - 1, prevPrevStrokeNode.Index - 1]; prevPrevStrokeNodeBounds = prevPrevStrokeNode.GetBounds(); prevStrokeNode = emptyStrokeNode; strokeNode = emptyStrokeNode; // increment previousIndex to to point to this node previousIndex = index - 1; continue; } else if (result == RectCompareResult.Rect1ContainsRect2) { // this node (C) already contains the prevStrokeNodeBounds (P): // // |------------| // |----| | |----| | // | PP | | | P | C | // |----| | |----| | // |------------| // //we have to generate a new stroke node that points //to pp since the connecting quad from C to P could be empty //if they have the same point strokeNode = iterator[index - 1, prevStrokeNode.Index - 1]; if (!strokeNode.GetConnectingQuad().IsEmpty) { //only update prevStrokeNode if we have a valid connecting quad prevStrokeNode = strokeNode; prevStrokeNodeBounds = Rect.Union(strokeNodeBounds, prevStrokeNodeBounds); // update previousIndex, since it should point to this node now previousIndex = index - 1; } // at this point we can just ignore this node strokeNode = emptyStrokeNode; //strokeNodeBounds = empty; prevAngle = double.MinValue; //invalidate // go back to our main loop continue; } else if (result == RectCompareResult.Rect2ContainsRect1) { // this prevStrokeNodeBounds (P) already contains this node (C): // // |------------| // |----| | |----|| // | PP | | P | C || // |----| | |----|| // |------------| // //prevStrokeNodeBounds already contains this node //we can just ignore this node strokeNode = emptyStrokeNode; // go back to our main loop, but do not update previousIndex // because it should continue to point to previous continue; } Debug.Assert(!strokeNode.GetConnectingQuad().IsEmpty, "strokeNode.GetConnectingQuad was empty, this is unexpected"); // // NOTE: we do not check if C contains PP, or PP contains C because // that indicates a change in direction, which we handle below // // if neither was true P and C are separate, // we now have all three nodes required to // start our computation, we need to update previousIndex to point // to our current, valid prevStrokeNode previousIndex = index - 1; } // see if we have an overlap between the first and third node bool overlap = prevPrevStrokeNodeBounds.IntersectsWith(strokeNodeBounds); // prevPrevStrokeNode, prevStrokeNode and strokeNode are all // valid nodes now. Now we need to figure out what do add to our // PathFigure. First calc bounds on the strokeNode we know we need to render if (calculateBounds) { bounds.Union(prevStrokeNodeBounds); } // determine what points to add to pathFigureABSide and pathFigureDCSide // from prevPrevStrokeNode if (pathFigureABSide.Count == 0) { Debug.Assert(pathFigureDCSide.Count == 0); if (calculateBounds) { bounds.Union(prevPrevStrokeNodeBounds); } if (isStartOfSegment && overlap) { //render a complete first stroke node or we can get artifacts prevPrevStrokeNode.GetContourPoints(polyLinePoints); AddFigureToStreamGeometryContext(context, polyLinePoints, prevPrevStrokeNode.IsEllipse/*isBezierFigure*/); polyLinePoints.Clear(); } // we're starting a new pathfigure // we need to add parts of the prevPrevStrokeNode contour // to pathFigureABSide and pathFigureDCSide #if DEBUG_RENDERING_FEEDBACK prevStrokeNode.GetPointsAtStartOfSegment(pathFigureABSide, pathFigureDCSide, debugDC, feedbackSize, showFeedback); #else prevStrokeNode.GetPointsAtStartOfSegment(pathFigureABSide, pathFigureDCSide); #endif //set our marker, we're no longer at the start of the stroke isStartOfSegment = false; } if (prevAngle == double.MinValue) { //prevAngle is no longer valid prevAngle = GetAngleBetween(prevPrevStrokeNode.Position, prevStrokeNode.Position); } double delta = GetAngleDeltaFromLast(prevStrokeNode.Position, strokeNode.Position, ref prevAngle); bool directionChangedOverAbsoluteThreshold = Math.Abs(delta) > 90d && Math.Abs(delta) < (360d - 90d); bool directionChangedOverOverlapThreshold = overlap && !(ignorePressure || strokeNode.PressureFactor == 1f) && Math.Abs(delta) > 30d && Math.Abs(delta) < (360d - 30d); double prevArea = prevStrokeNodeBounds.Height * prevStrokeNodeBounds.Width; double currArea = strokeNodeBounds.Height * strokeNodeBounds.Width; bool areaChanged = !(prevArea == currArea && prevArea == (prevPrevStrokeNodeBounds.Height * prevPrevStrokeNodeBounds.Width)); bool areaChangeOverThreshold = false; if (overlap && areaChanged) { if ((Math.Min(prevArea, currArea) / Math.Max(prevArea, currArea)) <= 0.90d) { //the min area is < 70% of the max area areaChangeOverThreshold = true; } } if (areaChanged || delta != 0.0d || index >= iteratorCount) { //the area changed between the three nodes OR there was an angle delta OR we're at the end //of the stroke... either way, this is a significant node. If not, we're going to drop it. if ((overlap && (directionChangedOverOverlapThreshold || areaChangeOverThreshold)) || directionChangedOverAbsoluteThreshold) { // // we need to stop the pathfigure at P // and render the pathfigure // // |--| |--| |--||--| |------| // |PP|------|P | |PP||P | |PP P C| // |--| |--| |--||--| |------| // / |C | // |--| |--| // |C | // |--| #if DEBUG_RENDERING_FEEDBACK prevStrokeNode.GetPointsAtEndOfSegment(pathFigureABSide, pathFigureDCSide, debugDC, feedbackSize, showFeedback); #else //end the figure prevStrokeNode.GetPointsAtEndOfSegment(pathFigureABSide, pathFigureDCSide); #endif //render ReverseDCPointsRenderAndClear(context, pathFigureABSide, pathFigureDCSide, polyLinePoints, isEllipse, true/*clear the point collections*/); if (areaChangeOverThreshold) { //render a complete stroke node or we can get artifacts prevStrokeNode.GetContourPoints(polyLinePoints); AddFigureToStreamGeometryContext(context, polyLinePoints, prevStrokeNode.IsEllipse/*isBezierFigure*/); polyLinePoints.Clear(); } } else { // // direction didn't change over the threshold, add the midpoint data // |--| |--| // |PP|------|P | // |--| |--| // \ // |--| // |C | // |--| bool endSegment; //flag that tell us if we missed an intersection #if DEBUG_RENDERING_FEEDBACK strokeNode.GetPointsAtMiddleSegment(prevStrokeNode, delta, pathFigureABSide, pathFigureDCSide, out endSegment, debugDC, feedbackSize, showFeedback); #else strokeNode.GetPointsAtMiddleSegment(prevStrokeNode, delta, pathFigureABSide, pathFigureDCSide, out endSegment); #endif if (endSegment) { //we have a missing intersection, we need to end the //segment at P #if DEBUG_RENDERING_FEEDBACK prevStrokeNode.GetPointsAtEndOfSegment(pathFigureABSide, pathFigureDCSide, debugDC, feedbackSize, showFeedback); #else //end the figure prevStrokeNode.GetPointsAtEndOfSegment(pathFigureABSide, pathFigureDCSide); #endif //render ReverseDCPointsRenderAndClear(context, pathFigureABSide, pathFigureDCSide, polyLinePoints, isEllipse, true/*clear the point collections*/); } } } // // either way... slide our pointers forward, to do this, we simply mark // our first pointer as 'empty' // prevPrevStrokeNode = emptyStrokeNode; prevPrevStrokeNodeBounds = empty; } // // anything left to render? // if (prevPrevStrokeNode.IsValid) { if (prevStrokeNode.IsValid) { if (calculateBounds) { bounds.Union(prevPrevStrokeNodeBounds); bounds.Union(prevStrokeNodeBounds); } Debug.Assert(!strokeNode.IsValid); // // we never made it to strokeNode, render two points, OR // strokeNode was a dupe // if (pathFigureABSide.Count > 0) { #if DEBUG_RENDERING_FEEDBACK prevStrokeNode.GetPointsAtEndOfSegment(pathFigureABSide, pathFigureDCSide, debugDC, feedbackSize, showFeedback); #else // // strokeNode was a dupe, we just need to render the end of the stroke // which is at prevStrokeNode // prevStrokeNode.GetPointsAtEndOfSegment(pathFigureABSide, pathFigureDCSide); #endif //render ReverseDCPointsRenderAndClear(context, pathFigureABSide, pathFigureDCSide, polyLinePoints, isEllipse, false/*clear the point collections*/); } else { // we've only seen two points to render Debug.Assert(pathFigureDCSide.Count == 0); //contains all the logic to render two stroke nodes RenderTwoStrokeNodes( context, prevPrevStrokeNode, prevPrevStrokeNodeBounds, prevStrokeNode, prevStrokeNodeBounds, pathFigureABSide, pathFigureDCSide, polyLinePoints #if DEBUG_RENDERING_FEEDBACK ,debugDC, feedbackSize, showFeedback #endif ); } } else { if (calculateBounds) { bounds.Union(prevPrevStrokeNodeBounds); } // we only have a single point to render Debug.Assert(pathFigureABSide.Count == 0); prevPrevStrokeNode.GetContourPoints(pathFigureABSide); AddFigureToStreamGeometryContext(context, pathFigureABSide, prevPrevStrokeNode.IsEllipse/*isBezierFigure*/); } } else if (prevStrokeNode.IsValid && strokeNode.IsValid) { if (calculateBounds) { bounds.Union(prevStrokeNodeBounds); bounds.Union(strokeNodeBounds); } // typical case, we hit the end of the stroke // see if we need to start a stroke, or just end one if (pathFigureABSide.Count > 0) { #if DEBUG_RENDERING_FEEDBACK strokeNode.GetPointsAtEndOfSegment(pathFigureABSide, pathFigureDCSide, debugDC, feedbackSize, showFeedback); #else strokeNode.GetPointsAtEndOfSegment(pathFigureABSide, pathFigureDCSide); #endif //render ReverseDCPointsRenderAndClear(context, pathFigureABSide, pathFigureDCSide, polyLinePoints, isEllipse, false/*clear the point collections*/); if (FuzzyContains(strokeNodeBounds, prevStrokeNodeBounds, 70d) != RectCompareResult.NoItersection) { //render a complete stroke node or we can get artifacts strokeNode.GetContourPoints(polyLinePoints); AddFigureToStreamGeometryContext(context, polyLinePoints, strokeNode.IsEllipse/*isBezierFigure*/); } } else { Debug.Assert(pathFigureDCSide.Count == 0); //contains all the logic to render two stroke nodes RenderTwoStrokeNodes( context, prevStrokeNode, prevStrokeNodeBounds, strokeNode, strokeNodeBounds, pathFigureABSide, pathFigureDCSide, polyLinePoints #if DEBUG_RENDERING_FEEDBACK ,debugDC, feedbackSize, showFeedback #endif ); } } } finally { context.Close(); geometry.Freeze(); } } }
/// <summary> /// Helper function to find out whether a point is inside the lasso /// </summary> private bool SegmentWithinLasso(StrokeNode strokeNode, double fIndex) { bool currentSegmentWithinLasso; if (DoubleUtil.AreClose(fIndex, StrokeFIndices.BeforeFirst)) { // This should check against the very first stroke node currentSegmentWithinLasso = this.Contains(strokeNode.GetPointAt(0f)); } else if (DoubleUtil.AreClose(fIndex, StrokeFIndices.AfterLast)) { // This should check against the last stroke node currentSegmentWithinLasso = this.Contains(strokeNode.Position); } else { currentSegmentWithinLasso = this.Contains(strokeNode.GetPointAt(fIndex)); } return currentSegmentWithinLasso; }
/// <summary> /// GetPointsAtMiddleSegment /// </summary> internal void GetPointsAtMiddleSegment(StrokeNode previous, double angleBetweenNodes, List <Point> abPoints, List <Point> dcPoints, out bool missingIntersection #if DEBUG_RENDERING_FEEDBACK , DrawingContext debugDC, double feedbackSize, bool showFeedback #endif ) { missingIntersection = false; if (IsValid && previous.IsValid) { Quad quad1 = previous.ConnectingQuad; if (!quad1.IsEmpty) { Quad quad2 = ConnectingQuad; if (!quad2.IsEmpty) { if (IsEllipse) { Rect node1Bounds = _operations.GetNodeBounds(previous._lastNode); Rect node2Bounds = _operations.GetNodeBounds(_lastNode); Rect node3Bounds = _operations.GetNodeBounds(_thisNode); #if DEBUG_RENDERING_FEEDBACK if (showFeedback) { debugDC.DrawEllipse(null, new Pen(Brushes.Pink, feedbackSize / 2), _lastNode.Position, node2Bounds.Width / 2, node2Bounds.Height / 2); } #endif if (angleBetweenNodes == 0.0d || ((quad1.B == quad2.A) && (quad1.C == quad2.D))) { //quads connections are the same, just add them abPoints.Add(quad1.B); dcPoints.Add(quad1.C); #if DEBUG_RENDERING_FEEDBACK if (showFeedback) { debugDC.DrawEllipse(Brushes.Green, null, quad1.B, feedbackSize, feedbackSize); debugDC.DrawEllipse(Brushes.Yellow, null, quad1.C, feedbackSize, feedbackSize); } #endif } else if (angleBetweenNodes > 0.0) { //the stroke angled towards the AB side //this part is easy if (quad1.B == quad2.A) { abPoints.Add(quad1.B); #if DEBUG_RENDERING_FEEDBACK if (showFeedback) { debugDC.DrawEllipse(Brushes.Green, null, quad1.B, feedbackSize, feedbackSize); } #endif } else { Point intersection = GetIntersection(quad1.A, quad1.B, quad2.A, quad2.B); Rect union = Rect.Union(node1Bounds, node2Bounds); union.Inflate(1.0, 1.0); //make sure we're not off in space #if DEBUG_RENDERING_FEEDBACK if (showFeedback) { debugDC.DrawEllipse(Brushes.Green, null, quad1.B, feedbackSize * 1.5, feedbackSize * 1.5); debugDC.DrawEllipse(Brushes.Red, null, quad2.A, feedbackSize, feedbackSize); } #endif if (union.Contains(intersection)) { abPoints.Add(intersection); #if DEBUG_RENDERING_FEEDBACK if (showFeedback) { debugDC.DrawEllipse(Brushes.Orange, null, intersection, feedbackSize, feedbackSize); } #endif } else { //if we missed the intersection we'll need to close the stroke segment //this work is done in StrokeRenderer missingIntersection = true; return; //we're done. } } if (quad1.C == quad2.D) { dcPoints.Add(quad1.C); #if DEBUG_RENDERING_FEEDBACK if (showFeedback) { debugDC.DrawEllipse(Brushes.Yellow, null, quad1.C, feedbackSize, feedbackSize); } #endif } else { //add instructions to arc from quad1.C to quad2.D in reverse order (since we walk this array backwards to render) dcPoints.Add(quad1.C); dcPoints.Add(new Point(node2Bounds.Width, node2Bounds.Height)); dcPoints.Add(StrokeRenderer.ArcToMarker); dcPoints.Add(quad2.D); #if DEBUG_RENDERING_FEEDBACK if (showFeedback) { debugDC.DrawEllipse(Brushes.Yellow, null, quad1.C, feedbackSize, feedbackSize); debugDC.DrawEllipse(Brushes.Blue, null, quad2.D, feedbackSize, feedbackSize); } #endif } } else { //the stroke angled towards the CD side //this part is easy if (quad1.C == quad2.D) { dcPoints.Add(quad1.C); #if DEBUG_RENDERING_FEEDBACK if (showFeedback) { debugDC.DrawEllipse(Brushes.Yellow, null, quad1.C, feedbackSize, feedbackSize); } #endif } else { Point intersection = GetIntersection(quad1.D, quad1.C, quad2.D, quad2.C); Rect union = Rect.Union(node1Bounds, node2Bounds); union.Inflate(1.0, 1.0); //make sure we're not off in space #if DEBUG_RENDERING_FEEDBACK if (showFeedback) { debugDC.DrawEllipse(Brushes.Yellow, null, quad1.C, feedbackSize * 1.5, feedbackSize * 1.5); debugDC.DrawEllipse(Brushes.Blue, null, quad2.D, feedbackSize, feedbackSize); } #endif if (union.Contains(intersection)) { dcPoints.Add(intersection); #if DEBUG_RENDERING_FEEDBACK if (showFeedback) { debugDC.DrawEllipse(Brushes.Orange, null, intersection, feedbackSize, feedbackSize); } #endif } else { //if we missed the intersection we'll need to close the stroke segment //this work is done in StrokeRenderer missingIntersection = true; return; //we're done. } } if (quad1.B == quad2.A) { abPoints.Add(quad1.B); #if DEBUG_RENDERING_FEEDBACK if (showFeedback) { debugDC.DrawEllipse(Brushes.Green, null, quad1.B, feedbackSize, feedbackSize); } #endif } else { //we need to arc between quad1.B and quad2.A along node2 abPoints.Add(quad1.B); abPoints.Add(StrokeRenderer.ArcToMarker); abPoints.Add(new Point(node2Bounds.Width, node2Bounds.Height)); abPoints.Add(quad2.A); #if DEBUG_RENDERING_FEEDBACK if (showFeedback) { debugDC.DrawEllipse(Brushes.Green, null, quad1.B, feedbackSize, feedbackSize); debugDC.DrawEllipse(Brushes.Red, null, quad2.A, feedbackSize, feedbackSize); } #endif } } } else { //rectangle int indexA = -1; int indexB = -1; int indexC = -1; int indexD = -1; Vector[] vertices = _operations.GetVertices(); double pressureFactor = _lastNode.PressureFactor; for (int i = 0; i < vertices.Length; i++) { Point point = _lastNode.Position + (vertices[i % vertices.Length] * pressureFactor); if (point == quad2.A) { indexA = i; } if (point == quad1.B) { indexB = i; } if (point == quad1.C) { indexC = i; } if (point == quad2.D) { indexD = i; } } if (indexA == -1 || indexB == -1 || indexC == -1 || indexD == -1) { Debug.Assert(false, "Couldn't find all 4 indexes in StrokeNodeOperations.GetPointsAtMiddleSegment"); return; } #if DEBUG_RENDERING_FEEDBACK if (showFeedback) { debugDC.DrawRectangle(null, new Pen(Brushes.Pink, feedbackSize / 2), _operations.GetNodeBounds(_lastNode)); debugDC.DrawEllipse(Brushes.Red, null, quad2.A, feedbackSize, feedbackSize); debugDC.DrawEllipse(Brushes.Green, null, quad1.B, feedbackSize, feedbackSize); debugDC.DrawEllipse(Brushes.Yellow, null, quad1.C, feedbackSize, feedbackSize); debugDC.DrawEllipse(Brushes.Blue, null, quad2.D, feedbackSize, feedbackSize); } #endif Rect node3Rect = _operations.GetNodeBounds(_thisNode); //take care of a-b first if (indexA == indexB) { //quad connection is the same, just add it if (!node3Rect.Contains(quad1.B)) { abPoints.Add(quad1.B); } } else if ((indexA == 0 && indexB == 3) || ((indexA != 3 || indexB != 0) && (indexA > indexB))) { if (!node3Rect.Contains(quad1.B)) { abPoints.Add(quad1.B); } if (!node3Rect.Contains(quad2.A)) { abPoints.Add(quad2.A); } } else { Point intersection = GetIntersection(quad1.A, quad1.B, quad2.A, quad2.B); Rect node12 = Rect.Union(_operations.GetNodeBounds(previous._lastNode), _operations.GetNodeBounds(_lastNode)); node12.Inflate(1.0, 1.0); //make sure we're not off in space if (node12.Contains(intersection)) { abPoints.Add(intersection); #if DEBUG_RENDERING_FEEDBACK if (showFeedback) { debugDC.DrawEllipse(Brushes.Orange, null, intersection, feedbackSize, feedbackSize * 1.5); } #endif } else { //if we missed the intersection we'll need to close the stroke segment //this work is done in StrokeRenderer. missingIntersection = true; return; //we're done. } } // now take care of c-d. if (indexC == indexD) { //quad connection is the same, just add it if (!node3Rect.Contains(quad1.C)) { dcPoints.Add(quad1.C); } } else if ((indexC == 0 && indexD == 3) || ((indexC != 3 || indexD != 0) && (indexC > indexD))) { if (!node3Rect.Contains(quad1.C)) { dcPoints.Add(quad1.C); } if (!node3Rect.Contains(quad2.D)) { dcPoints.Add(quad2.D); } } else { Point intersection = GetIntersection(quad1.D, quad1.C, quad2.D, quad2.C); Rect node12 = Rect.Union(_operations.GetNodeBounds(previous._lastNode), _operations.GetNodeBounds(_lastNode)); node12.Inflate(1.0, 1.0); //make sure we're not off in space if (node12.Contains(intersection)) { dcPoints.Add(intersection); #if DEBUG_RENDERING_FEEDBACK if (showFeedback) { debugDC.DrawEllipse(Brushes.Orange, null, intersection, feedbackSize, feedbackSize * 1.5); } #endif } else { //if we missed the intersection we'll need to close the stroke segment //this work is done in StrokeRenderer. missingIntersection = true; return; //we're done. } } } } } } }
/// <summary> /// This method tells whether the contour of a given stroke node /// intersects with the contour of this node. The contours of both nodes /// include their connecting quadrangles. /// </summary> /// <param name="hitNode"></param> /// <returns></returns> internal bool HitTest(StrokeNode hitNode) { if (!IsValid || !hitNode.IsValid) { return false; } IEnumerable<ContourSegment> hittingContour = hitNode.GetContourSegments(); return _operations.HitTest(_lastNode, _thisNode, ConnectingQuad, hittingContour); }
/// <summary> /// Finds out if a given node intersects with this one, /// and returns findices of the intersection. /// </summary> /// <param name="hitNode"></param> /// <returns></returns> internal StrokeFIndices CutTest(StrokeNode hitNode) { if ((IsValid == false) || (hitNode.IsValid == false)) { return StrokeFIndices.Empty; } IEnumerable<ContourSegment> hittingContour = hitNode.GetContourSegments(); // If the node contours intersect, the result is a pair of findices // this segment should be cut at to let the hitNode's contour through it. StrokeFIndices cutAt = _operations.CutTest(_lastNode, _thisNode, ConnectingQuad, hittingContour); return (_index == 0) ? cutAt : BindFIndices(cutAt); }
/// <summary> /// Helper routine to render two distinct stroke nodes /// </summary> private static void RenderTwoStrokeNodes( StreamGeometryContext context, StrokeNode strokeNodePrevious, Rect strokeNodePreviousBounds, StrokeNode strokeNodeCurrent, Rect strokeNodeCurrentBounds, List<Point> pointBuffer1, List<Point> pointBuffer2, List<Point> pointBuffer3 #if DEBUG_RENDERING_FEEDBACK ,DrawingContext debugDC, double feedbackSize, bool showFeedback #endif ) { Debug.Assert(pointBuffer1 != null); Debug.Assert(pointBuffer2 != null); Debug.Assert(pointBuffer3 != null); Debug.Assert(context != null); //see if we need to render a quad - if there is not at least a 70% overlap if (FuzzyContains(strokeNodePreviousBounds, strokeNodeCurrentBounds, 70d) != RectCompareResult.NoItersection) { //we're between 100% and 70% overlapped //just render two distinct figures with a connecting quad (if needed) strokeNodePrevious.GetContourPoints(pointBuffer1); AddFigureToStreamGeometryContext(context, pointBuffer1, strokeNodePrevious.IsEllipse/*isBezierFigure*/); Quad quad = strokeNodeCurrent.GetConnectingQuad(); if (!quad.IsEmpty) { pointBuffer3.Add(quad.A); pointBuffer3.Add(quad.B); pointBuffer3.Add(quad.C); pointBuffer3.Add(quad.D); AddFigureToStreamGeometryContext(context, pointBuffer3, false/*isBezierFigure*/); } strokeNodeCurrent.GetContourPoints(pointBuffer2); AddFigureToStreamGeometryContext(context, pointBuffer2, strokeNodeCurrent.IsEllipse/*isBezierFigure*/); } else { //we're less than 70% overlapped, it's safe to run our optimization #if DEBUG_RENDERING_FEEDBACK strokeNodeCurrent.GetPointsAtStartOfSegment(pointBuffer1, pointBuffer2, debugDC, feedbackSize, showFeedback); strokeNodeCurrent.GetPointsAtEndOfSegment(pointBuffer1, pointBuffer2, debugDC, feedbackSize, showFeedback); #else strokeNodeCurrent.GetPointsAtStartOfSegment(pointBuffer1, pointBuffer2); strokeNodeCurrent.GetPointsAtEndOfSegment(pointBuffer1, pointBuffer2); #endif //render ReverseDCPointsRenderAndClear(context, pointBuffer1, pointBuffer2, pointBuffer3, strokeNodeCurrent.IsEllipse, false/*clear the point collections*/); } }
internal StrokeIntersection[] HitTest(StrokeNodeIterator iterator) { System.Diagnostics.Debug.Assert(_points != null); System.Diagnostics.Debug.Assert(iterator != null); if (_points.Count < 3) { // // it takes at least 3 points to create a lasso // return(Array.Empty <StrokeIntersection>()); } // // We're about to perform hit testing with a lasso. // To do so we need to iterate through each StrokeNode. // As we do, we calculate the bounding rect between it // and the previous StrokeNode and store this in 'currentStrokeSegmentBounds' // // Next, we check to see if that StrokeNode pair's bounding box intersects // with the bounding box of the Lasso points. If not, we continue iterating through // StrokeNode pairs. // // If it does, we do a more granular hit test by pairing points in the Lasso, getting // their bounding box and seeing if that bounding box intersects our current StrokeNode // pair // Point lastNodePosition = new Point(); Point lassoLastPoint = _points[_points.Count - 1]; Rect currentStrokeSegmentBounds = Rect.Empty; // Initilize the current crossing to be an empty one LassoCrossing currentCrossing = LassoCrossing.EmptyCrossing; // Creat a list to hold all the crossings List <LassoCrossing> crossingList = new List <LassoCrossing>(); for (int i = 0; i < iterator.Count; i++) { StrokeNode strokeNode = iterator[i]; Rect nodeBounds = strokeNode.GetBounds(); currentStrokeSegmentBounds.Union(nodeBounds); // Skip the node if it's outside of the lasso's bounds if (currentStrokeSegmentBounds.IntersectsWith(_bounds) == true) { // currentStrokeSegmentBounds, made up of the bounding box of // this StrokeNode unioned with the last StrokeNode, // intersects the lasso bounding box. // // Now we need to iterate through the lasso points and find out where they cross // Point lastPoint = lassoLastPoint; foreach (Point point in _points) { // // calculate a segment of the lasso from the last point // to the current point // Rect lassoSegmentBounds = new Rect(lastPoint, point); // // see if this lasso segment intersects with the current stroke segment // if (!currentStrokeSegmentBounds.IntersectsWith(lassoSegmentBounds)) { lastPoint = point; continue; } // // the lasso segment DOES intersect with the current stroke segment // find out precisely where // StrokeFIndices strokeFIndices = strokeNode.CutTest(lastPoint, point); lastPoint = point; if (strokeFIndices.IsEmpty) { // current lasso segment does not hit the stroke segment, continue with the next lasso point continue; } // Create a potentially new crossing for the current hit testing result. LassoCrossing potentialNewCrossing = new LassoCrossing(strokeFIndices, strokeNode); // Try to merge with the current crossing. If the merge is succussful (return true), the new crossing is actually // continueing the current crossing, so do not start a new crossing. Otherwise, start a new one and add the existing // one to the list. if (!currentCrossing.Merge(potentialNewCrossing)) { // start a new crossing and add the existing on to the list crossingList.Add(currentCrossing); currentCrossing = potentialNewCrossing; } } } // Continue with the next node currentStrokeSegmentBounds = nodeBounds; lastNodePosition = strokeNode.Position; } // Adding the last crossing to the list, if valid if (!currentCrossing.IsEmpty) { crossingList.Add(currentCrossing); } // Handle the special case of no intersection at all if (crossingList.Count == 0) { // the stroke was either completely inside the lasso // or outside the lasso if (this.Contains(lastNodePosition)) { StrokeIntersection[] strokeIntersections = new StrokeIntersection[1]; strokeIntersections[0] = StrokeIntersection.Full; return(strokeIntersections); } else { return(Array.Empty <StrokeIntersection>()); } } // It is still possible that the current crossing list is not sorted or overlapping. // Sort the list and merge the overlapping ones. SortAndMerge(ref crossingList); // Produce the hit test results and store them in a list List <StrokeIntersection> strokeIntersectionList = new List <StrokeIntersection>(); ProduceHitTestResults(crossingList, strokeIntersectionList); return(strokeIntersectionList.ToArray()); }
/// <summary> /// Merge two crossings into one. /// </summary> /// <param name="crossing"></param> /// <returns>Return true if these two crossings are actually overlapping and merged; false otherwise</returns> public bool Merge(LassoCrossing crossing) { if (crossing.IsEmpty) { return false; } if (FIndices.IsEmpty && !crossing.IsEmpty) { FIndices = crossing.FIndices; StartNode = crossing.StartNode; EndNode = crossing.EndNode; return true; } if(DoubleUtil.GreaterThanOrClose(crossing.FIndices.EndFIndex, FIndices.BeginFIndex) && DoubleUtil.GreaterThanOrClose(FIndices.EndFIndex, crossing.FIndices.BeginFIndex)) { if (DoubleUtil.LessThan(crossing.FIndices.BeginFIndex, FIndices.BeginFIndex)) { FIndices.BeginFIndex = crossing.FIndices.BeginFIndex; StartNode = crossing.StartNode; } if (DoubleUtil.GreaterThan(crossing.FIndices.EndFIndex, FIndices.EndFIndex)) { FIndices.EndFIndex = crossing.FIndices.EndFIndex; EndNode = crossing.EndNode; } return true; } return false; }