/// <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> /// 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); }
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> /// 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 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()); }