/// <summary> /// Helper function to find out the hit test result /// </summary> private void ProduceHitTestResults( List <LassoCrossing> crossingList, List <StrokeIntersection> strokeIntersections) { bool previousSegmentInsideLasso = false; for (int x = 0; x <= crossingList.Count; x++) { bool currentSegmentWithinLasso = false; bool canMerge = true; StrokeIntersection si = new StrokeIntersection(); if (x == 0) { si.HitBegin = StrokeFIndices.BeforeFirst; si.InBegin = StrokeFIndices.BeforeFirst; } else { si.InBegin = crossingList[x - 1].FIndices.EndFIndex; si.HitBegin = crossingList[x - 1].FIndices.BeginFIndex; currentSegmentWithinLasso = SegmentWithinLasso(crossingList[x - 1].EndNode, si.InBegin); } if (x == crossingList.Count) { // For a special case when the last intersection is something like (1.2, AL). // As a result the last InSegment should be empty. if (DoubleUtil.AreClose(si.InBegin, StrokeFIndices.AfterLast)) { si.InEnd = StrokeFIndices.BeforeFirst; } else { si.InEnd = StrokeFIndices.AfterLast; } si.HitEnd = StrokeFIndices.AfterLast; } else { si.InEnd = crossingList[x].FIndices.BeginFIndex; // For a speical case when the first intersection is something like (BF, 0.67). // As a result the first InSegment should be empty if (DoubleUtil.AreClose(si.InEnd, StrokeFIndices.BeforeFirst)) { System.Diagnostics.Debug.Assert(DoubleUtil.AreClose(si.InBegin, StrokeFIndices.BeforeFirst)); si.InBegin = StrokeFIndices.AfterLast; } si.HitEnd = crossingList[x].FIndices.EndFIndex; currentSegmentWithinLasso = SegmentWithinLasso(crossingList[x].StartNode, si.InEnd); // If both the start and end position of the current crossing is // outside the lasso, the crossing is a hit-only intersection, i.e., the in-segment is empty. if (!currentSegmentWithinLasso && !SegmentWithinLasso(crossingList[x].EndNode, si.HitEnd)) { currentSegmentWithinLasso = true; si.HitBegin = crossingList[x].FIndices.BeginFIndex; si.InBegin = StrokeFIndices.AfterLast; si.InEnd = StrokeFIndices.BeforeFirst; canMerge = false; } } if (currentSegmentWithinLasso) { if (x > 0 && previousSegmentInsideLasso && canMerge) { // we need to consolidate with the previous segment StrokeIntersection previousIntersection = strokeIntersections[strokeIntersections.Count - 1]; // For example: previousIntersection = [BF, AL, BF, 0.0027], si = [BF, 0.0027, 0.049, 0.063] if (previousIntersection.InSegment.IsEmpty) { previousIntersection.InBegin = si.InBegin; } previousIntersection.InEnd = si.InEnd; previousIntersection.HitEnd = si.HitEnd; strokeIntersections[strokeIntersections.Count - 1] = previousIntersection; } else { strokeIntersections.Add(si); } if (DoubleUtil.AreClose(si.HitEnd, StrokeFIndices.AfterLast)) { // The strokeIntersections already cover the end of the stroke. No need to continue. return; } } previousSegmentInsideLasso = currentSegmentWithinLasso; } }
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()); }