/// <summary> /// Get the "hit-segments" /// </summary> internal static StrokeFIndices[] GetHitSegments(StrokeIntersection[] intersections) { System.Diagnostics.Debug.Assert(intersections != null); System.Diagnostics.Debug.Assert(intersections.Length > 0); List <StrokeFIndices> hitFIndices = new List <StrokeFIndices>(intersections.Length); for (int j = 0; j < intersections.Length; j++) { System.Diagnostics.Debug.Assert(!intersections[j].IsEmpty); if (!intersections[j].HitSegment.IsEmpty) { if (hitFIndices.Count > 0 && hitFIndices[hitFIndices.Count - 1].EndFIndex >= intersections[j].HitSegment.BeginFIndex) { //merge StrokeFIndices sfiPrevious = hitFIndices[hitFIndices.Count - 1]; sfiPrevious.EndFIndex = intersections[j].HitSegment.EndFIndex; hitFIndices[hitFIndices.Count - 1] = sfiPrevious; } else { hitFIndices.Add(intersections[j].HitSegment); } } } return(hitFIndices.ToArray()); }
/// <summary> /// ToString /// </summary> public override string ToString() { return("{" + StrokeFIndices.GetStringRepresentation(_hitSegment.BeginFIndex) + "," + StrokeFIndices.GetStringRepresentation(_inSegment.BeginFIndex) + "," + StrokeFIndices.GetStringRepresentation(_inSegment.EndFIndex) + "," + StrokeFIndices.GetStringRepresentation(_hitSegment.EndFIndex) + "}"); }
/// <summary> /// Clip /// </summary> /// <param name="cutAt">Fragment markers for clipping</param> private StrokeCollection Clip(StrokeFIndices[] cutAt) { System.Diagnostics.Debug.Assert(cutAt != null); System.Diagnostics.Debug.Assert(cutAt.Length != 0); #if DEBUG // // Assert there are no overlaps between multiple StrokeFIndices // AssertSortedNoOverlap(cutAt); #endif StrokeCollection leftovers = new StrokeCollection(); if (cutAt.Length == 0) { return(leftovers); } if ((cutAt.Length == 1) && cutAt[0].IsFull) { leftovers.Add(this.Clone()); //clip and erase always return clones return(leftovers); } StylusPointCollection sourceStylusPoints = this.StylusPoints; if (this.DrawingAttributes.FitToCurve) { sourceStylusPoints = this.GetBezierStylusPoints(); } // // Assert the findices are NOT out of range with the packets // System.Diagnostics.Debug.Assert(false == ((!DoubleUtil.AreClose(cutAt[cutAt.Length - 1].EndFIndex, StrokeFIndices.AfterLast)) && Math.Ceiling(cutAt[cutAt.Length - 1].EndFIndex) > sourceStylusPoints.Count - 1)); for (int i = 0; i < cutAt.Length; i++) { StrokeFIndices fragment = cutAt[i]; if (DoubleUtil.GreaterThanOrClose(fragment.BeginFIndex, fragment.EndFIndex)) { // ISSUE-2004/06/26-vsmirnov - temporary workaround for bugs // in point erasing: drop invalid fragments System.Diagnostics.Debug.Assert(DoubleUtil.LessThan(fragment.BeginFIndex, fragment.EndFIndex)); continue; } Stroke stroke = Copy(sourceStylusPoints, fragment.BeginFIndex, fragment.EndFIndex); // Add the stroke to the output collection leftovers.Add(stroke); } return(leftovers); }
/// <summary> /// Helper method to calculate the exact location to cut /// </summary> /// <param name="spineVector">Vector the relative location of the two inking nodes</param> /// <param name="hitBegin">the begin point of the hitting segment</param> /// <param name="hitEnd">the end point of the hitting segment</param> /// <param name="endRadius">endNode radius</param> /// <param name="beginRadius">beginNode radius</param> /// <param name="result">StrokeFIndices representing the location for cutting</param> private void CalculateCutLocations( Vector spineVector, Vector hitBegin, Vector hitEnd, double endRadius, double beginRadius, ref StrokeFIndices result) { // Find out whether the {hitBegin, hitEnd} segment intersects with the contour // of the stroke segment, and find the lower index of the fragment to cut out. if (!DoubleUtil.AreClose(result.EndFIndex, StrokeFIndices.AfterLast)) { if (WhereIsNodeAboutSegment(spineVector, hitBegin, hitEnd) == HitResult.Left) { double findex = 1 - ClipTest(spineVector, endRadius, beginRadius, hitBegin, hitEnd); if (findex > result.EndFIndex) { result.EndFIndex = findex; } } } // Find out whether the {hitBegin, hitEnd} segment intersects with the contour // of the stroke segment, and find the higher index of the fragment to cut out. if (!DoubleUtil.AreClose(result.BeginFIndex, StrokeFIndices.BeforeFirst)) { hitBegin -= spineVector; hitEnd -= spineVector; if (WhereIsNodeAboutSegment(-spineVector, hitBegin, hitEnd) == HitResult.Left) { double findex = ClipTest(-spineVector, beginRadius, endRadius, hitBegin, hitEnd); if (findex < result.BeginFIndex) { result.BeginFIndex = findex; } } } }
/// <summary> /// Helper method used to determine if we came up with a bogus result during hit testing /// </summary> protected bool IsInvalidCutTestResult(StrokeFIndices result) { // // check for three invalid states // 1) BeforeFirst == AfterLast // 2) BeforeFirst, < 0 // 3) > 1, AfterLast // if (DoubleUtil.AreClose(result.BeginFIndex, result.EndFIndex) || DoubleUtil.AreClose(result.BeginFIndex, StrokeFIndices.BeforeFirst) && result.EndFIndex < 0.0f || result.BeginFIndex > 1.0f && DoubleUtil.AreClose(result.EndFIndex, StrokeFIndices.AfterLast)) { return true; } return false; }
/// <summary> /// Helper function to Hit-test against the two stroke nodes only (excluding the connecting quad). /// </summary> /// <param name="hitSegment"></param> /// <param name="beginNode"></param> /// <param name="endNode"></param> /// <param name="result"></param> /// <returns></returns> private bool HitTestStrokeNodes( ContourSegment hitSegment, StrokeNodeData beginNode, StrokeNodeData endNode, ref StrokeFIndices result) { // First, find out if hitSegment intersects with either of the ink nodes bool isHit = false; for (int node = 0; node < 2; node++) { Point position; double pressureFactor; if (node == 0) { if (isHit && DoubleUtil.AreClose(result.BeginFIndex, StrokeFIndices.BeforeFirst)) { continue; } position = beginNode.Position; pressureFactor = beginNode.PressureFactor; } else { if (isHit && DoubleUtil.AreClose(result.EndFIndex, StrokeFIndices.AfterLast)) { continue; } position = endNode.Position; pressureFactor = endNode.PressureFactor; } Vector hitBegin, hitEnd; // Adjust the segment for the node's pressure factor if (hitSegment.IsArc) { hitBegin = hitSegment.Begin - position + hitSegment.Radius; hitEnd = hitSegment.Radius; } else { hitBegin = hitSegment.Begin - position; hitEnd = hitBegin + hitSegment.Vector; } if (pressureFactor != 1) { System.Diagnostics.Debug.Assert(DoubleUtil.IsZero(pressureFactor) == false); hitBegin /= pressureFactor; hitEnd /= pressureFactor; } // Hit-test the node against the segment if (hitSegment.IsArc ? HitTestPolygonCircle(_vertices, hitBegin, hitEnd) : HitTestPolygonSegment(_vertices, hitBegin, hitEnd)) { isHit = true; if (node == 0) { result.BeginFIndex = StrokeFIndices.BeforeFirst; if (DoubleUtil.AreClose(result.EndFIndex, StrokeFIndices.AfterLast)) { break; } } else { result.EndFIndex = StrokeFIndices.AfterLast; if (beginNode.IsEmpty) { result.BeginFIndex = StrokeFIndices.BeforeFirst; break; } if (DoubleUtil.AreClose(result.BeginFIndex, StrokeFIndices.BeforeFirst)) { break; } } } } return isHit; }
/// <summary> /// Bind the StrokeFIndices for lasso hit test results. /// </summary> /// <param name="fragment"></param> /// <returns></returns> private StrokeFIndices BindFIndicesForLassoHitTest(StrokeFIndices fragment) { System.Diagnostics.Debug.Assert(IsValid); if (!fragment.IsEmpty) { // Adjust BeginFIndex if (DoubleUtil.AreClose(fragment.BeginFIndex, StrokeFIndices.BeforeFirst)) { // set it to be the index of the previous node, indicating intersection start from previous node fragment.BeginFIndex = (_index == 0 ? StrokeFIndices.BeforeFirst:_index - 1); } else { // Adjust findices which are on this segment of the spine (i.e. between 0 and 1) System.Diagnostics.Debug.Assert(DoubleUtil.GreaterThanOrClose(fragment.BeginFIndex, 0f)); System.Diagnostics.Debug.Assert(DoubleUtil.LessThanOrClose(fragment.BeginFIndex, 1f)); // Adjust the value to consider index, say from 0.75 to 3.75 (for _index = 4) fragment.BeginFIndex += _index - 1; } //Adjust EndFIndex if (DoubleUtil.AreClose(fragment.EndFIndex, StrokeFIndices.AfterLast)) { // set it to be the index of the current node, indicating the intersection cover the end of the node fragment.EndFIndex = (_isLastNode ? StrokeFIndices.AfterLast:_index); } else { System.Diagnostics.Debug.Assert(DoubleUtil.GreaterThanOrClose(fragment.EndFIndex, 0f)); System.Diagnostics.Debug.Assert(DoubleUtil.LessThanOrClose(fragment.EndFIndex, 1f)); // Ajust the value to consider the index fragment.EndFIndex += _index - 1; } } return fragment; }
/// <summary> /// /// </summary> /// <param name="cutAt">Fragment markers for clipping</param> /// <returns>Survived fragments of current Stroke as a StrokeCollection</returns> private StrokeCollection Erase(StrokeFIndices[] cutAt) { System.Diagnostics.Debug.Assert(cutAt != null); System.Diagnostics.Debug.Assert(cutAt.Length != 0); #if DEBUG // // Assert there are no overlaps between multiple StrokeFIndices // AssertSortedNoOverlap(cutAt); #endif StrokeCollection leftovers = new StrokeCollection(); // Return an empty collection if the entire stroke it to erase if ((cutAt.Length == 0) || ((cutAt.Length == 1) && cutAt[0].IsFull)) { return leftovers; } StylusPointCollection sourceStylusPoints = this.StylusPoints; if (this.DrawingAttributes.FitToCurve) { sourceStylusPoints = this.GetBezierStylusPoints(); } // // Assert the findices are NOT out of range with the packets // System.Diagnostics.Debug.Assert(false == ((!DoubleUtil.AreClose(cutAt[cutAt.Length - 1].EndFIndex, StrokeFIndices.AfterLast)) && Math.Ceiling(cutAt[cutAt.Length - 1].EndFIndex) > sourceStylusPoints.Count - 1)); int i = 0; double beginFIndex = StrokeFIndices.BeforeFirst; if (cutAt[0].BeginFIndex == StrokeFIndices.BeforeFirst) { beginFIndex = cutAt[0].EndFIndex; i++; } for (; i < cutAt.Length; i++) { StrokeFIndices fragment = cutAt[i]; if(DoubleUtil.GreaterThanOrClose(beginFIndex, fragment.BeginFIndex)) { // ISSUE-2004/06/26-vsmirnov - temporary workaround for bugs // in point erasing: drop invalid fragments System.Diagnostics.Debug.Assert(DoubleUtil.LessThan(beginFIndex, fragment.BeginFIndex)); continue; } Stroke stroke = Copy(sourceStylusPoints, beginFIndex, fragment.BeginFIndex); // Add the stroke to the output collection leftovers.Add(stroke); beginFIndex = fragment.EndFIndex; } if (beginFIndex != StrokeFIndices.AfterLast) { Stroke stroke = Copy(sourceStylusPoints, beginFIndex, StrokeFIndices.AfterLast); // Add the stroke to the output collection leftovers.Add(stroke); } return leftovers; }
private bool IsValidStrokeFIndices(StrokeFIndices findex) { return (!double.IsNaN(findex.BeginFIndex) && !double.IsNaN(findex.EndFIndex) && findex.BeginFIndex < findex.EndFIndex); }
/// <summary> /// Helper method used to validate that the strokefindices in the array /// are sorted and there are no overlaps /// </summary> /// <param name="fragments">fragments</param> private void AssertSortedNoOverlap(StrokeFIndices[] fragments) { if (fragments.Length == 0) { return; } if (fragments.Length == 1) { System.Diagnostics.Debug.Assert(IsValidStrokeFIndices(fragments[0])); return; } double current = StrokeFIndices.BeforeFirst; for (int x = 0; x < fragments.Length; x++) { if (fragments[x].BeginFIndex <= current) { // // when x == 0, we're just starting, any value is valid // System.Diagnostics.Debug.Assert(x == 0); } current = fragments[x].BeginFIndex; System.Diagnostics.Debug.Assert(IsValidStrokeFIndices(fragments[x]) && fragments[x].EndFIndex > current); current = fragments[x].EndFIndex; } }
/// <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); }
/// <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> /// Binds a local fragment to this node by setting the integer part of the /// fragment findices equal to the index of the previous node /// </summary> /// <param name="fragment"></param> /// <returns></returns> private StrokeFIndices BindFIndices(StrokeFIndices fragment) { System.Diagnostics.Debug.Assert(IsValid && (_index >= 0)); if (fragment.IsEmpty == false) { // Adjust only findices which are on this segment of thew spine (i.e. between 0 and 1) if (!DoubleUtil.AreClose(fragment.BeginFIndex, StrokeFIndices.BeforeFirst)) { System.Diagnostics.Debug.Assert(fragment.BeginFIndex >= 0 && fragment.BeginFIndex <= 1); fragment.BeginFIndex += _index - 1; } if (!DoubleUtil.AreClose(fragment.EndFIndex, StrokeFIndices.AfterLast)) { System.Diagnostics.Debug.Assert(fragment.EndFIndex >= 0 && fragment.EndFIndex <= 1); fragment.EndFIndex += _index - 1; } } return fragment; }
/// <summary> /// Constructor /// </summary> /// <param name="hitBegin"></param> /// <param name="inBegin"></param> /// <param name="inEnd"></param> /// <param name="hitEnd"></param> internal StrokeIntersection(double hitBegin, double inBegin, double inEnd, double hitEnd) { //ISSUE-2004/12/06-XiaoTu: should we validate the input? _hitSegment = new StrokeFIndices(hitBegin, hitEnd); _inSegment = new StrokeFIndices(inBegin, inEnd); }
private bool IsValidStrokeFIndices(StrokeFIndices findex) { return(!double.IsNaN(findex.BeginFIndex) && !double.IsNaN(findex.EndFIndex) && findex.BeginFIndex < findex.EndFIndex); }
/// <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; }