Code:
/ Net / Net / 3.5.50727.3053 / DEVDIV / depot / DevDiv / releases / Orcas / SP / wpf / src / Core / CSharp / MS / Internal / Ink / EllipticalNodeOperations.cs / 1 / EllipticalNodeOperations.cs
//------------------------------------------------------------------------ //// Copyright (c) Microsoft Corporation. All rights reserved. // //----------------------------------------------------------------------- using System; using System.Collections.Generic; using System.Windows; using System.Windows.Media; using System.Windows.Ink; using System.Windows.Input; using System.Diagnostics; namespace MS.Internal.Ink { ////// StrokeNodeOperations implementation for elliptical nodes /// internal class EllipticalNodeOperations : StrokeNodeOperations { ////// Constructor /// /// internal EllipticalNodeOperations(StylusShape nodeShape) : base(nodeShape) { System.Diagnostics.Debug.Assert((nodeShape != null) && nodeShape.IsEllipse); _radii = new Size(nodeShape.Width * 0.5, nodeShape.Height * 0.5); // All operations with ellipses become simple(r) if transfrom ellipses into circles. // Use the max of the radii for the radius of the circle _radius = Math.Max(_radii.Width, _radii.Height); // Compute ellipse-to-circle and circle-to-elliipse transforms. The former is used // in hit-testing operations while the latter is used when computing vertices of // a quadrangle connecting two ellipses _transform = nodeShape.Transform; _nodeShapeToCircle = _transform; Debug.Assert(_nodeShapeToCircle.HasInverse, "About to invert a non-invertible transform"); _nodeShapeToCircle.Invert(); if (DoubleUtil.AreClose(_radii.Width, _radii.Height)) { _circleToNodeShape = _transform; } else { // Reverse the rotation if (false == DoubleUtil.IsZero(nodeShape.Rotation)) { _nodeShapeToCircle.Rotate(-nodeShape.Rotation); Debug.Assert(_nodeShapeToCircle.HasInverse, "Just rotated an invertible transform and produced a non-invertible one"); } // Scale to enlarge double sx, sy; if (_radii.Width > _radii.Height) { sx = 1; sy = _radii.Width / _radii.Height; } else { sx = _radii.Height / _radii.Width; sy = 1; } _nodeShapeToCircle.Scale(sx, sy); Debug.Assert(_nodeShapeToCircle.HasInverse, "Just scaled an invertible transform and produced a non-invertible one"); _circleToNodeShape = _nodeShapeToCircle; _circleToNodeShape.Invert(); } } ////// This is probably not the best (design-wise) but the cheapest way to tell /// EllipticalNodeOperations from all other implementations of node operations. /// internal override bool IsNodeShapeEllipse { get { return true; } } ////// Finds connecting points for a pair of stroke nodes /// /// a node to connect /// another node, next to beginNode ///connecting quadrangle internal override Quad GetConnectingQuad(StrokeNodeData beginNode, StrokeNodeData endNode) { if (beginNode.IsEmpty || endNode.IsEmpty || DoubleUtil.AreClose(beginNode.Position, endNode.Position)) { return Quad.Empty; } // Get the vector between the node positions Vector spine = endNode.Position - beginNode.Position; if (_nodeShapeToCircle.IsIdentity == false) { spine = _nodeShapeToCircle.Transform(spine); } double beginRadius = _radius * beginNode.PressureFactor; double endRadius = _radius * endNode.PressureFactor; // Get the vector and the distance between the node positions double distanceSquared = spine.LengthSquared; double delta = endRadius - beginRadius; double deltaSquared = DoubleUtil.IsZero(delta) ? 0 : (delta * delta); if (DoubleUtil.LessThanOrClose(distanceSquared, deltaSquared)) { // One circle is contained within the other return Quad.Empty; } // Thus, at this point, distance > 0, which avoids the DivideByZero error // Also, here, distanceSquared > deltaSquared // Thus, 0 <= rSin < 1 // Get the components of the radius vectors double distance = Math.Sqrt(distanceSquared); spine /= distance; Vector rad = spine; // Turn left double temp = rad.Y; rad.Y = -rad.X; rad.X = temp; Vector vectorToLeftTangent, vectorToRightTangent; double rSinSquared = deltaSquared / distanceSquared; if (DoubleUtil.IsZero(rSinSquared)) { vectorToLeftTangent = rad; vectorToRightTangent = -rad; } else { rad *= Math.Sqrt(1 - rSinSquared); spine *= Math.Sqrt(rSinSquared); if (beginNode.PressureFactor < endNode.PressureFactor) { spine = -spine; } vectorToLeftTangent = spine + rad; vectorToRightTangent = spine - rad; } // Get the common tangent points if (_circleToNodeShape.IsIdentity == false) { vectorToLeftTangent = _circleToNodeShape.Transform(vectorToLeftTangent); vectorToRightTangent = _circleToNodeShape.Transform(vectorToRightTangent); } return new Quad(beginNode.Position + (vectorToLeftTangent * beginRadius), endNode.Position + (vectorToLeftTangent * endRadius), endNode.Position + (vectorToRightTangent * endRadius), beginNode.Position + (vectorToRightTangent * beginRadius)); } ////// /// /// /// ///internal override IEnumerable GetContourSegments(StrokeNodeData node, Quad quad) { System.Diagnostics.Debug.Assert(node.IsEmpty == false); if (quad.IsEmpty) { Point point = node.Position; point.X += _radius; yield return new ContourSegment(point, point, node.Position); } else if (_nodeShapeToCircle.IsIdentity) { yield return new ContourSegment(quad.A, quad.B); yield return new ContourSegment(quad.B, quad.C, node.Position); yield return new ContourSegment(quad.C, quad.D); yield return new ContourSegment(quad.D, quad.A); } } /// /// ISSUE-2004/06/15- temporary workaround to avoid hit-testing ellipses with ellipses /// /// /// ///internal override IEnumerable GetNonBezierContourSegments(StrokeNodeData beginNode, StrokeNodeData endNode) { Quad quad = beginNode.IsEmpty ? Quad.Empty : base.GetConnectingQuad(beginNode, endNode); return base.GetContourSegments(endNode, quad); } /// /// Hit-tests a stroke segment defined by two nodes against a linear segment. /// /// Begin node of the stroke segment to hit-test. Can be empty (none) /// End node of the stroke segment /// Pre-computed quadrangle connecting the two nodes. /// Can be empty if the begion node is empty or when one node is entirely inside the other /// an end point of the hitting linear segment /// an end point of the hitting linear segment ///true if the hitting segment intersect the contour comprised of the two stroke nodes internal override bool HitTest( StrokeNodeData beginNode, StrokeNodeData endNode, Quad quad, Point hitBeginPoint, Point hitEndPoint) { StrokeNodeData bigNode, smallNode; if (beginNode.IsEmpty || (quad.IsEmpty && (endNode.PressureFactor > beginNode.PressureFactor))) { // Need to test one node only bigNode = endNode; smallNode = StrokeNodeData.Empty; } else { // In this case the size doesn't matter. bigNode = beginNode; smallNode = endNode; } // Compute the positions of the involved points relative to bigNode. Vector hitBegin = hitBeginPoint - bigNode.Position; Vector hitEnd = hitEndPoint - bigNode.Position; // If the node shape is an ellipse, transform the scene to turn the shape to a circle if (_nodeShapeToCircle.IsIdentity == false) { hitBegin = _nodeShapeToCircle.Transform(hitBegin); hitEnd = _nodeShapeToCircle.Transform(hitEnd); } bool isHit = false; // Hit-test the big node double bigRadius = _radius * bigNode.PressureFactor; Vector nearest = GetNearest(hitBegin, hitEnd); if (nearest.LengthSquared <= (bigRadius * bigRadius)) { isHit = true; } else if (quad.IsEmpty == false) { // Hit-test the other node Vector spineVector = smallNode.Position - bigNode.Position; if (_nodeShapeToCircle.IsIdentity == false) { spineVector = _nodeShapeToCircle.Transform(spineVector); } double smallRadius = _radius * smallNode.PressureFactor; nearest = GetNearest(hitBegin - spineVector, hitEnd - spineVector); if ((nearest.LengthSquared <= (smallRadius * smallRadius)) || HitTestQuadSegment(quad, hitBeginPoint, hitEndPoint)) { isHit = true; } } return isHit; } ////// Hit-tests a stroke segment defined by two nodes against another stroke segment. /// /// Begin node of the stroke segment to hit-test. Can be empty (none) /// End node of the stroke segment /// Pre-computed quadrangle connecting the two nodes. /// Can be empty if the begion node is empty or when one node is entirely inside the other /// a collection of basic segments outlining the hitting contour ///true if the contours intersect or overlap internal override bool HitTest( StrokeNodeData beginNode, StrokeNodeData endNode, Quad quad, IEnumerablehitContour) { StrokeNodeData bigNode, smallNode; double bigRadiusSquared, smallRadiusSquared = 0; Vector spineVector; if (beginNode.IsEmpty || (quad.IsEmpty && (endNode.PressureFactor > beginNode.PressureFactor))) { // Need to test one node only bigNode = endNode; smallNode = StrokeNodeData.Empty; spineVector = new Vector(); } else { // In this case the size doesn't matter. bigNode = beginNode; smallNode = endNode; smallRadiusSquared = _radius * smallNode.PressureFactor; smallRadiusSquared *= smallRadiusSquared; // Find position of smallNode relative to the bigNode. spineVector = smallNode.Position - bigNode.Position; // If the node shape is an ellipse, transform the scene to turn the shape to a circle if (_nodeShapeToCircle.IsIdentity == false) { spineVector = _nodeShapeToCircle.Transform(spineVector); } } bigRadiusSquared = _radius * bigNode.PressureFactor; bigRadiusSquared *= bigRadiusSquared; bool isHit = false; // When hit-testing a contour against another contour, like in this case, // the default implementation checks whether any edge (segment) of the hitting // contour intersects with the contour of the ink segment. But this doesn't cover // the case when the ink segment is entirely inside of the hitting segment. // The bool variable isInside is used here to track that case. It answers the question // 'Is ink contour inside if the hitting contour?'. It's initialized to 'true" // and then verified for each edge of the hitting contour until there's a hit or // until it's false. bool isInside = true; foreach (ContourSegment hitSegment in hitContour) { if (hitSegment.IsArc) { // ISSUE-2004/06/15-vsmirnov - ellipse vs arc hit-testing is not implemented // and currently disabled in ErasingStroke } else { // Find position of the hitting segment relative to bigNode transformed to circle. Vector hitBegin = hitSegment.Begin - bigNode.Position; Vector hitEnd = hitBegin + hitSegment.Vector; if (_nodeShapeToCircle.IsIdentity == false) { hitBegin = _nodeShapeToCircle.Transform(hitBegin); hitEnd = _nodeShapeToCircle.Transform(hitEnd); } // Hit-test the big node Vector nearest = GetNearest(hitBegin, hitEnd); if (nearest.LengthSquared <= bigRadiusSquared) { isHit = true; break; } // Hit-test the other node if (quad.IsEmpty == false) { nearest = GetNearest(hitBegin - spineVector, hitEnd - spineVector); if ((nearest.LengthSquared <= smallRadiusSquared) || HitTestQuadSegment(quad, hitSegment.Begin, hitSegment.End)) { isHit = true; break; } } // While there's still a chance to find the both nodes inside the hitting contour, // continue checking on position of the endNode relative to the edges of the hitting contour. if (isInside && (WhereIsVectorAboutVector(endNode.Position - hitSegment.Begin, hitSegment.Vector) != HitResult.Right)) { isInside = false; } } } return (isHit || isInside); } /// /// Cut-test ink segment defined by two nodes and a connecting quad against a linear segment /// /// Begin node of the ink segment /// End node of the ink segment /// Pre-computed quadrangle connecting the two ink nodes /// egin point of the hitting segment /// End point of the hitting segment ///Exact location to cut at represented by StrokeFIndices internal override StrokeFIndices CutTest( StrokeNodeData beginNode, StrokeNodeData endNode, Quad quad, Point hitBeginPoint, Point hitEndPoint) { // Compute the positions of the involved points relative to the endNode. Vector spineVector = beginNode.IsEmpty ? new Vector(0, 0) : (beginNode.Position - endNode.Position); Vector hitBegin = hitBeginPoint - endNode.Position; Vector hitEnd = hitEndPoint - endNode.Position; // If the node shape is an ellipse, transform the scene to turn the shape to a circle if (_nodeShapeToCircle.IsIdentity == false) { spineVector = _nodeShapeToCircle.Transform(spineVector); hitBegin = _nodeShapeToCircle.Transform(hitBegin); hitEnd = _nodeShapeToCircle.Transform(hitEnd); } StrokeFIndices result = StrokeFIndices.Empty; // Hit-test the end node double beginRadius = 0, endRadius = _radius * endNode.PressureFactor; Vector nearest = GetNearest(hitBegin, hitEnd); if (nearest.LengthSquared <= (endRadius * endRadius)) { result.EndFIndex = StrokeFIndices.AfterLast; result.BeginFIndex = beginNode.IsEmpty ? StrokeFIndices.BeforeFirst : 1; } if (beginNode.IsEmpty == false) { // Hit-test the first node beginRadius = _radius * beginNode.PressureFactor; nearest = GetNearest(hitBegin - spineVector, hitEnd - spineVector); if (nearest.LengthSquared <= (beginRadius * beginRadius)) { result.BeginFIndex = StrokeFIndices.BeforeFirst; if (!DoubleUtil.AreClose(result.EndFIndex, StrokeFIndices.AfterLast)) { result.EndFIndex = 0; } } } // If both nodes are hit or nothing is hit at all, return. if (result.IsFull || quad.IsEmpty || (result.IsEmpty && (HitTestQuadSegment(quad, hitBeginPoint, hitEndPoint) == false))) { return result; } // Find out whether the {begin, end} segment intersects with the contour // of the stroke segment {_lastNode, _thisNode}, and find the lower index // of the fragment to cut out. if (!DoubleUtil.AreClose(result.BeginFIndex, StrokeFIndices.BeforeFirst)) { result.BeginFIndex = ClipTest(-spineVector, beginRadius, endRadius, hitBegin - spineVector, hitEnd - spineVector); } if (!DoubleUtil.AreClose(result.EndFIndex, StrokeFIndices.AfterLast)) { result.EndFIndex = 1 - ClipTest(spineVector, endRadius, beginRadius, hitBegin, hitEnd); } if (IsInvalidCutTestResult(result)) { return StrokeFIndices.Empty; } return result; } ////// CutTest an inking StrokeNode segment (two nodes and a connecting quadrangle) against a hitting contour /// (represented by an enumerator of Contoursegments). /// /// The begin StrokeNodeData /// The end StrokeNodeData /// Connecing quadrangle between the begin and end inking node /// The hitting ContourSegments ///StrokeFIndices representing the location for cutting internal override StrokeFIndices CutTest( StrokeNodeData beginNode, StrokeNodeData endNode, Quad quad, IEnumerablehitContour) { // Compute the positions of the beginNode relative to the endNode. Vector spineVector = beginNode.IsEmpty ? new Vector(0, 0) : (beginNode.Position - endNode.Position); // If the node shape is an ellipse, transform the scene to turn the shape to a circle if (_nodeShapeToCircle.IsIdentity == false) { spineVector = _nodeShapeToCircle.Transform(spineVector); } double beginRadius = 0, endRadius; double beginRadiusSquared = 0, endRadiusSquared; endRadius = _radius * endNode.PressureFactor; endRadiusSquared = endRadius * endRadius; if (beginNode.IsEmpty == false) { beginRadius = _radius * beginNode.PressureFactor; beginRadiusSquared = beginRadius * beginRadius; } bool isInside = true; StrokeFIndices result = StrokeFIndices.Empty; foreach (ContourSegment hitSegment in hitContour) { if (hitSegment.IsArc) { // ISSUE-2004/06/15-vsmirnov - ellipse vs arc hit-testing is not implemented // and currently disabled in ErasingStroke } else { Vector hitBegin = hitSegment.Begin - endNode.Position; Vector hitEnd = hitBegin + hitSegment.Vector; // If the node shape is an ellipse, transform the scene to turn // the shape into circle. if (_nodeShapeToCircle.IsIdentity == false) { hitBegin = _nodeShapeToCircle.Transform(hitBegin); hitEnd = _nodeShapeToCircle.Transform(hitEnd); } bool isHit = false; // Hit-test the end node Vector nearest = GetNearest(hitBegin, hitEnd); if (nearest.LengthSquared < endRadiusSquared) { isHit = true; if (!DoubleUtil.AreClose(result.EndFIndex, StrokeFIndices.AfterLast)) { result.EndFIndex = StrokeFIndices.AfterLast; if (beginNode.IsEmpty) { result.BeginFIndex = StrokeFIndices.BeforeFirst; break; } if (DoubleUtil.AreClose(result.BeginFIndex, StrokeFIndices.BeforeFirst)) { break; } } } if ((beginNode.IsEmpty == false) && (!isHit || !DoubleUtil.AreClose(result.BeginFIndex, StrokeFIndices.BeforeFirst))) { // Hit-test the first node nearest = GetNearest(hitBegin - spineVector, hitEnd - spineVector); if (nearest.LengthSquared < beginRadiusSquared) { isHit = true; if (!DoubleUtil.AreClose(result.BeginFIndex, StrokeFIndices.BeforeFirst)) { result.BeginFIndex = StrokeFIndices.BeforeFirst; if (DoubleUtil.AreClose(result.EndFIndex, StrokeFIndices.AfterLast)) { break; } } } } // If both nodes are hit or nothing is hit at all, return. if (beginNode.IsEmpty || (!isHit && (quad.IsEmpty || (HitTestQuadSegment(quad, hitSegment.Begin, hitSegment.End) == false)))) { if (isInside && (WhereIsVectorAboutVector( endNode.Position - hitSegment.Begin, hitSegment.Vector) != HitResult.Right)) { isInside = false; } continue; } isInside = false; // NTRAID#Window OS bug-1029694-2004/10/18-xiaotu, refactor the code to make it a method // to increase the maintainability of the program. FxCop bug. // Calculate the exact locations to cut. CalculateCutLocations(spineVector, hitBegin, hitEnd, endRadius, beginRadius, ref result); if (result.IsFull) { break; } } } // if (!result.IsFull) { if (isInside == true) { System.Diagnostics.Debug.Assert(result.IsEmpty); result = StrokeFIndices.Full; } else if ((DoubleUtil.AreClose(result.EndFIndex, StrokeFIndices.BeforeFirst)) && (!DoubleUtil.AreClose(result.BeginFIndex, StrokeFIndices.AfterLast))) { result.EndFIndex = StrokeFIndices.AfterLast; } else if ((DoubleUtil.AreClose(result.BeginFIndex,StrokeFIndices.AfterLast)) && (!DoubleUtil.AreClose(result.EndFIndex, StrokeFIndices.BeforeFirst))) { result.BeginFIndex = StrokeFIndices.BeforeFirst; } } if (IsInvalidCutTestResult(result)) { return StrokeFIndices.Empty; } return result; } /// /// Clip-Testing a circluar inking segment against a linear segment. /// See http://tabletpc/longhorn/Specs/Rendering%20and%20Hit-Testing%20Ink%20in%20Avalon%20M11.doc section /// 5.4.4.14 Clip-Testing a Circular Inking Segment against a Linear Segment for details of the algorithm /// /// Represent the spine of the inking segment pointing from the beginNode to endNode /// Radius of the beginNode /// Radius of the endNode /// Hitting segment start point /// Hitting segment end point ///A double which represents the location for cutting private static double ClipTest(Vector spineVector, double beginRadius, double endRadius, Vector hitBegin, Vector hitEnd) { // First handle the special case when the spineVector is (0,0). In other words, this is the case // when the stylus stays at the the location but pressure changes. if (DoubleUtil.IsZero(spineVector.X) && DoubleUtil.IsZero(spineVector.Y)) { System.Diagnostics.Debug.Assert(DoubleUtil.AreClose(beginRadius, endRadius) == false); Vector nearest = GetNearest(hitBegin, hitEnd); double radius; if (nearest.X == 0) { radius = Math.Abs(nearest.Y); } else if (nearest.Y == 0) { radius = Math.Abs(nearest.X); } else { radius = nearest.Length; } return AdjustFIndex((radius - beginRadius) / (endRadius - beginRadius)); } // This change to ClipTest with a point if the two hitting segment are close enough. if (DoubleUtil.AreClose(hitBegin, hitEnd)) { return ClipTest(spineVector, beginRadius, endRadius, hitBegin); } double findex; Vector hitVector = hitEnd - hitBegin; if (DoubleUtil.IsZero(Vector.Determinant(spineVector, hitVector))) { // hitVector and spineVector are parallel findex = ClipTest(spineVector, beginRadius, endRadius, GetNearest(hitBegin, hitEnd)); System.Diagnostics.Debug.Assert(!double.IsNaN(findex)); } else { // On the line defined by the segment find point P1Xp, the nearest to the beginNode.Position double x = GetProjectionFIndex(hitBegin, hitEnd); Vector P1Xp = hitBegin + (hitVector * x); if (P1Xp.LengthSquared < (beginRadius * beginRadius)) { System.Diagnostics.Debug.Assert(DoubleUtil.IsBetweenZeroAndOne(x) == false); findex = ClipTest(spineVector, beginRadius, endRadius, (0 > x) ? hitBegin : hitEnd); System.Diagnostics.Debug.Assert(!double.IsNaN(findex)); } else { // Find the projection point P of endNode.Position to the line (beginNode.Position, B). Vector P1P2p = spineVector + GetProjection(-spineVector, P1Xp - spineVector); //System.Diagnostics.Debug.Assert(false == DoubleUtil.IsZero(P1P2p.LengthSquared)); //System.Diagnostics.Debug.Assert(false == DoubleUtil.IsZero(endRadius - beginRadius + P1P2p.Length)); // There checks are here since if either fail no real solution can be caculated and we may // as well bail out now and save the caculations that are below. if (DoubleUtil.IsZero(P1P2p.LengthSquared) || DoubleUtil.IsZero(endRadius - beginRadius + P1P2p.Length)) return 1d; // Calculate the findex of the point to split the ink segment at. findex = (P1Xp.Length - beginRadius) / (endRadius - beginRadius + P1P2p.Length); System.Diagnostics.Debug.Assert(!double.IsNaN(findex)); // Find the projection of the split point to the line of this segment. Vector S = spineVector * findex; double r = GetProjectionFIndex(hitBegin - S, hitEnd - S); // If the nearest point misses the segment, then find the findex // of the node nearest to the segment. if (false == DoubleUtil.IsBetweenZeroAndOne(r)) { findex = ClipTest(spineVector, beginRadius, endRadius, (0 > r) ? hitBegin : hitEnd); System.Diagnostics.Debug.Assert(!double.IsNaN(findex)); } } } return AdjustFIndex(findex); } ////// Clip-Testing a circular inking segment again a hitting point. /// /// What need to find out a doulbe value s, which is between 0 and 1, such that /// DistanceOf(hit - s*spine) = beginRadius + s * (endRadius - beginRadius) /// That is /// (hit.X-s*spine.X)^2 + (hit.Y-s*spine.Y)^2 = [beginRadius + s*(endRadius-beginRadius)]^2 /// Rearrange /// A*s^2 + B*s + C = 0 /// where the value of A, B and C are described in the source code. /// Solving for s: /// s = (-B + sqrt(B^2-4A*C))/(2A) or s = (-B - sqrt(B^2-4A*C))/(2A) /// The smaller value between 0 and 1 is the one we want and discard the other one. /// /// Represent the spine of the inking segment pointing from the beginNode to endNode /// Radius of the beginNode /// Radius of the endNode /// The hitting point ///A double which represents the location for cutting private static double ClipTest(Vector spine, double beginRadius, double endRadius, Vector hit) { double radDiff = endRadius - beginRadius; double A = spine.X*spine.X + spine.Y*spine.Y - radDiff*radDiff; double B = -2.0f*(hit.X*spine.X + hit.Y * spine.Y + beginRadius*radDiff); double C = hit.X * hit.X + hit.Y * hit.Y - beginRadius * beginRadius; // There checks are here since if either fail no real solution can be caculated and we may // as well bail out now and save the caculations that are below. if (DoubleUtil.IsZero(A) || !DoubleUtil.GreaterThanOrClose(B*B, 4.0f*A*C)) return 1d; double tmp = Math.Sqrt(B*B-4.0f * A * C); double s1 = (-B + tmp)/(2.0f * A); double s2 = (-B - tmp)/(2.0f * A); double findex; if (DoubleUtil.IsBetweenZeroAndOne(s1) && DoubleUtil.IsBetweenZeroAndOne(s1)) { findex = Math.Min(s1, s2); } else if (DoubleUtil.IsBetweenZeroAndOne(s1)) { findex = s1; } else if (DoubleUtil.IsBetweenZeroAndOne(s2)) { findex = s2; } else { // There is still possiblity that value like 1.0000000000000402 is not considered // as "IsOne" by DoubleUtil class. We should be at either one of the following two cases: // 1. s1/s2 around 0 but not close enough (say -0.0000000000001) // 2. s1/s2 around 1 but not close enought (say 1.0000000000000402) if (s1 > 1d && s2 > 1d) { findex = 1d; } else if (s1 < 0d && s2 < 0d) { findex = 0d; } else { findex = Math.Abs(Math.Min(s1, s2) - 0d) < Math.Abs(Math.Max(s1, s2) - 1d) ? 0d : 1d; } } return AdjustFIndex(findex); } ////// Helper function to find out the relative location of a segment {segBegin, segEnd} against /// a strokeNode (spine). /// /// the spineVector of the StrokeNode /// Start position of the line segment /// End position of the line segment ///HitResult private static HitResult WhereIsNodeAboutSegment(Vector spine, Vector segBegin, Vector segEnd) { HitResult whereabout = HitResult.Right; Vector segVector = segEnd - segBegin; if ((WhereIsVectorAboutVector(-segBegin, segVector) == HitResult.Left) && !DoubleUtil.IsZero(Vector.Determinant(spine, segVector))) { whereabout = HitResult.Left; } return whereabout; } ////// Helper method to calculate the exact location to cut /// /// Vector the relative location of the two inking nodes /// the begin point of the hitting segment /// the end point of the hitting segment /// endNode radius /// beginNode radius /// StrokeFIndices representing the location for cutting 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; } } } } private double _radius = 0; private Size _radii; private Matrix _transform; private Matrix _nodeShapeToCircle; private Matrix _circleToNodeShape; } } // File provided for Reference Use Only by Microsoft Corporation (c) 2007. //------------------------------------------------------------------------ //// Copyright (c) Microsoft Corporation. All rights reserved. // //----------------------------------------------------------------------- using System; using System.Collections.Generic; using System.Windows; using System.Windows.Media; using System.Windows.Ink; using System.Windows.Input; using System.Diagnostics; namespace MS.Internal.Ink { ////// StrokeNodeOperations implementation for elliptical nodes /// internal class EllipticalNodeOperations : StrokeNodeOperations { ////// Constructor /// /// internal EllipticalNodeOperations(StylusShape nodeShape) : base(nodeShape) { System.Diagnostics.Debug.Assert((nodeShape != null) && nodeShape.IsEllipse); _radii = new Size(nodeShape.Width * 0.5, nodeShape.Height * 0.5); // All operations with ellipses become simple(r) if transfrom ellipses into circles. // Use the max of the radii for the radius of the circle _radius = Math.Max(_radii.Width, _radii.Height); // Compute ellipse-to-circle and circle-to-elliipse transforms. The former is used // in hit-testing operations while the latter is used when computing vertices of // a quadrangle connecting two ellipses _transform = nodeShape.Transform; _nodeShapeToCircle = _transform; Debug.Assert(_nodeShapeToCircle.HasInverse, "About to invert a non-invertible transform"); _nodeShapeToCircle.Invert(); if (DoubleUtil.AreClose(_radii.Width, _radii.Height)) { _circleToNodeShape = _transform; } else { // Reverse the rotation if (false == DoubleUtil.IsZero(nodeShape.Rotation)) { _nodeShapeToCircle.Rotate(-nodeShape.Rotation); Debug.Assert(_nodeShapeToCircle.HasInverse, "Just rotated an invertible transform and produced a non-invertible one"); } // Scale to enlarge double sx, sy; if (_radii.Width > _radii.Height) { sx = 1; sy = _radii.Width / _radii.Height; } else { sx = _radii.Height / _radii.Width; sy = 1; } _nodeShapeToCircle.Scale(sx, sy); Debug.Assert(_nodeShapeToCircle.HasInverse, "Just scaled an invertible transform and produced a non-invertible one"); _circleToNodeShape = _nodeShapeToCircle; _circleToNodeShape.Invert(); } } ////// This is probably not the best (design-wise) but the cheapest way to tell /// EllipticalNodeOperations from all other implementations of node operations. /// internal override bool IsNodeShapeEllipse { get { return true; } } ////// Finds connecting points for a pair of stroke nodes /// /// a node to connect /// another node, next to beginNode ///connecting quadrangle internal override Quad GetConnectingQuad(StrokeNodeData beginNode, StrokeNodeData endNode) { if (beginNode.IsEmpty || endNode.IsEmpty || DoubleUtil.AreClose(beginNode.Position, endNode.Position)) { return Quad.Empty; } // Get the vector between the node positions Vector spine = endNode.Position - beginNode.Position; if (_nodeShapeToCircle.IsIdentity == false) { spine = _nodeShapeToCircle.Transform(spine); } double beginRadius = _radius * beginNode.PressureFactor; double endRadius = _radius * endNode.PressureFactor; // Get the vector and the distance between the node positions double distanceSquared = spine.LengthSquared; double delta = endRadius - beginRadius; double deltaSquared = DoubleUtil.IsZero(delta) ? 0 : (delta * delta); if (DoubleUtil.LessThanOrClose(distanceSquared, deltaSquared)) { // One circle is contained within the other return Quad.Empty; } // Thus, at this point, distance > 0, which avoids the DivideByZero error // Also, here, distanceSquared > deltaSquared // Thus, 0 <= rSin < 1 // Get the components of the radius vectors double distance = Math.Sqrt(distanceSquared); spine /= distance; Vector rad = spine; // Turn left double temp = rad.Y; rad.Y = -rad.X; rad.X = temp; Vector vectorToLeftTangent, vectorToRightTangent; double rSinSquared = deltaSquared / distanceSquared; if (DoubleUtil.IsZero(rSinSquared)) { vectorToLeftTangent = rad; vectorToRightTangent = -rad; } else { rad *= Math.Sqrt(1 - rSinSquared); spine *= Math.Sqrt(rSinSquared); if (beginNode.PressureFactor < endNode.PressureFactor) { spine = -spine; } vectorToLeftTangent = spine + rad; vectorToRightTangent = spine - rad; } // Get the common tangent points if (_circleToNodeShape.IsIdentity == false) { vectorToLeftTangent = _circleToNodeShape.Transform(vectorToLeftTangent); vectorToRightTangent = _circleToNodeShape.Transform(vectorToRightTangent); } return new Quad(beginNode.Position + (vectorToLeftTangent * beginRadius), endNode.Position + (vectorToLeftTangent * endRadius), endNode.Position + (vectorToRightTangent * endRadius), beginNode.Position + (vectorToRightTangent * beginRadius)); } ////// /// /// /// ///internal override IEnumerable GetContourSegments(StrokeNodeData node, Quad quad) { System.Diagnostics.Debug.Assert(node.IsEmpty == false); if (quad.IsEmpty) { Point point = node.Position; point.X += _radius; yield return new ContourSegment(point, point, node.Position); } else if (_nodeShapeToCircle.IsIdentity) { yield return new ContourSegment(quad.A, quad.B); yield return new ContourSegment(quad.B, quad.C, node.Position); yield return new ContourSegment(quad.C, quad.D); yield return new ContourSegment(quad.D, quad.A); } } /// /// ISSUE-2004/06/15- temporary workaround to avoid hit-testing ellipses with ellipses /// /// /// ///internal override IEnumerable GetNonBezierContourSegments(StrokeNodeData beginNode, StrokeNodeData endNode) { Quad quad = beginNode.IsEmpty ? Quad.Empty : base.GetConnectingQuad(beginNode, endNode); return base.GetContourSegments(endNode, quad); } /// /// Hit-tests a stroke segment defined by two nodes against a linear segment. /// /// Begin node of the stroke segment to hit-test. Can be empty (none) /// End node of the stroke segment /// Pre-computed quadrangle connecting the two nodes. /// Can be empty if the begion node is empty or when one node is entirely inside the other /// an end point of the hitting linear segment /// an end point of the hitting linear segment ///true if the hitting segment intersect the contour comprised of the two stroke nodes internal override bool HitTest( StrokeNodeData beginNode, StrokeNodeData endNode, Quad quad, Point hitBeginPoint, Point hitEndPoint) { StrokeNodeData bigNode, smallNode; if (beginNode.IsEmpty || (quad.IsEmpty && (endNode.PressureFactor > beginNode.PressureFactor))) { // Need to test one node only bigNode = endNode; smallNode = StrokeNodeData.Empty; } else { // In this case the size doesn't matter. bigNode = beginNode; smallNode = endNode; } // Compute the positions of the involved points relative to bigNode. Vector hitBegin = hitBeginPoint - bigNode.Position; Vector hitEnd = hitEndPoint - bigNode.Position; // If the node shape is an ellipse, transform the scene to turn the shape to a circle if (_nodeShapeToCircle.IsIdentity == false) { hitBegin = _nodeShapeToCircle.Transform(hitBegin); hitEnd = _nodeShapeToCircle.Transform(hitEnd); } bool isHit = false; // Hit-test the big node double bigRadius = _radius * bigNode.PressureFactor; Vector nearest = GetNearest(hitBegin, hitEnd); if (nearest.LengthSquared <= (bigRadius * bigRadius)) { isHit = true; } else if (quad.IsEmpty == false) { // Hit-test the other node Vector spineVector = smallNode.Position - bigNode.Position; if (_nodeShapeToCircle.IsIdentity == false) { spineVector = _nodeShapeToCircle.Transform(spineVector); } double smallRadius = _radius * smallNode.PressureFactor; nearest = GetNearest(hitBegin - spineVector, hitEnd - spineVector); if ((nearest.LengthSquared <= (smallRadius * smallRadius)) || HitTestQuadSegment(quad, hitBeginPoint, hitEndPoint)) { isHit = true; } } return isHit; } ////// Hit-tests a stroke segment defined by two nodes against another stroke segment. /// /// Begin node of the stroke segment to hit-test. Can be empty (none) /// End node of the stroke segment /// Pre-computed quadrangle connecting the two nodes. /// Can be empty if the begion node is empty or when one node is entirely inside the other /// a collection of basic segments outlining the hitting contour ///true if the contours intersect or overlap internal override bool HitTest( StrokeNodeData beginNode, StrokeNodeData endNode, Quad quad, IEnumerablehitContour) { StrokeNodeData bigNode, smallNode; double bigRadiusSquared, smallRadiusSquared = 0; Vector spineVector; if (beginNode.IsEmpty || (quad.IsEmpty && (endNode.PressureFactor > beginNode.PressureFactor))) { // Need to test one node only bigNode = endNode; smallNode = StrokeNodeData.Empty; spineVector = new Vector(); } else { // In this case the size doesn't matter. bigNode = beginNode; smallNode = endNode; smallRadiusSquared = _radius * smallNode.PressureFactor; smallRadiusSquared *= smallRadiusSquared; // Find position of smallNode relative to the bigNode. spineVector = smallNode.Position - bigNode.Position; // If the node shape is an ellipse, transform the scene to turn the shape to a circle if (_nodeShapeToCircle.IsIdentity == false) { spineVector = _nodeShapeToCircle.Transform(spineVector); } } bigRadiusSquared = _radius * bigNode.PressureFactor; bigRadiusSquared *= bigRadiusSquared; bool isHit = false; // When hit-testing a contour against another contour, like in this case, // the default implementation checks whether any edge (segment) of the hitting // contour intersects with the contour of the ink segment. But this doesn't cover // the case when the ink segment is entirely inside of the hitting segment. // The bool variable isInside is used here to track that case. It answers the question // 'Is ink contour inside if the hitting contour?'. It's initialized to 'true" // and then verified for each edge of the hitting contour until there's a hit or // until it's false. bool isInside = true; foreach (ContourSegment hitSegment in hitContour) { if (hitSegment.IsArc) { // ISSUE-2004/06/15-vsmirnov - ellipse vs arc hit-testing is not implemented // and currently disabled in ErasingStroke } else { // Find position of the hitting segment relative to bigNode transformed to circle. Vector hitBegin = hitSegment.Begin - bigNode.Position; Vector hitEnd = hitBegin + hitSegment.Vector; if (_nodeShapeToCircle.IsIdentity == false) { hitBegin = _nodeShapeToCircle.Transform(hitBegin); hitEnd = _nodeShapeToCircle.Transform(hitEnd); } // Hit-test the big node Vector nearest = GetNearest(hitBegin, hitEnd); if (nearest.LengthSquared <= bigRadiusSquared) { isHit = true; break; } // Hit-test the other node if (quad.IsEmpty == false) { nearest = GetNearest(hitBegin - spineVector, hitEnd - spineVector); if ((nearest.LengthSquared <= smallRadiusSquared) || HitTestQuadSegment(quad, hitSegment.Begin, hitSegment.End)) { isHit = true; break; } } // While there's still a chance to find the both nodes inside the hitting contour, // continue checking on position of the endNode relative to the edges of the hitting contour. if (isInside && (WhereIsVectorAboutVector(endNode.Position - hitSegment.Begin, hitSegment.Vector) != HitResult.Right)) { isInside = false; } } } return (isHit || isInside); } /// /// Cut-test ink segment defined by two nodes and a connecting quad against a linear segment /// /// Begin node of the ink segment /// End node of the ink segment /// Pre-computed quadrangle connecting the two ink nodes /// egin point of the hitting segment /// End point of the hitting segment ///Exact location to cut at represented by StrokeFIndices internal override StrokeFIndices CutTest( StrokeNodeData beginNode, StrokeNodeData endNode, Quad quad, Point hitBeginPoint, Point hitEndPoint) { // Compute the positions of the involved points relative to the endNode. Vector spineVector = beginNode.IsEmpty ? new Vector(0, 0) : (beginNode.Position - endNode.Position); Vector hitBegin = hitBeginPoint - endNode.Position; Vector hitEnd = hitEndPoint - endNode.Position; // If the node shape is an ellipse, transform the scene to turn the shape to a circle if (_nodeShapeToCircle.IsIdentity == false) { spineVector = _nodeShapeToCircle.Transform(spineVector); hitBegin = _nodeShapeToCircle.Transform(hitBegin); hitEnd = _nodeShapeToCircle.Transform(hitEnd); } StrokeFIndices result = StrokeFIndices.Empty; // Hit-test the end node double beginRadius = 0, endRadius = _radius * endNode.PressureFactor; Vector nearest = GetNearest(hitBegin, hitEnd); if (nearest.LengthSquared <= (endRadius * endRadius)) { result.EndFIndex = StrokeFIndices.AfterLast; result.BeginFIndex = beginNode.IsEmpty ? StrokeFIndices.BeforeFirst : 1; } if (beginNode.IsEmpty == false) { // Hit-test the first node beginRadius = _radius * beginNode.PressureFactor; nearest = GetNearest(hitBegin - spineVector, hitEnd - spineVector); if (nearest.LengthSquared <= (beginRadius * beginRadius)) { result.BeginFIndex = StrokeFIndices.BeforeFirst; if (!DoubleUtil.AreClose(result.EndFIndex, StrokeFIndices.AfterLast)) { result.EndFIndex = 0; } } } // If both nodes are hit or nothing is hit at all, return. if (result.IsFull || quad.IsEmpty || (result.IsEmpty && (HitTestQuadSegment(quad, hitBeginPoint, hitEndPoint) == false))) { return result; } // Find out whether the {begin, end} segment intersects with the contour // of the stroke segment {_lastNode, _thisNode}, and find the lower index // of the fragment to cut out. if (!DoubleUtil.AreClose(result.BeginFIndex, StrokeFIndices.BeforeFirst)) { result.BeginFIndex = ClipTest(-spineVector, beginRadius, endRadius, hitBegin - spineVector, hitEnd - spineVector); } if (!DoubleUtil.AreClose(result.EndFIndex, StrokeFIndices.AfterLast)) { result.EndFIndex = 1 - ClipTest(spineVector, endRadius, beginRadius, hitBegin, hitEnd); } if (IsInvalidCutTestResult(result)) { return StrokeFIndices.Empty; } return result; } ////// CutTest an inking StrokeNode segment (two nodes and a connecting quadrangle) against a hitting contour /// (represented by an enumerator of Contoursegments). /// /// The begin StrokeNodeData /// The end StrokeNodeData /// Connecing quadrangle between the begin and end inking node /// The hitting ContourSegments ///StrokeFIndices representing the location for cutting internal override StrokeFIndices CutTest( StrokeNodeData beginNode, StrokeNodeData endNode, Quad quad, IEnumerablehitContour) { // Compute the positions of the beginNode relative to the endNode. Vector spineVector = beginNode.IsEmpty ? new Vector(0, 0) : (beginNode.Position - endNode.Position); // If the node shape is an ellipse, transform the scene to turn the shape to a circle if (_nodeShapeToCircle.IsIdentity == false) { spineVector = _nodeShapeToCircle.Transform(spineVector); } double beginRadius = 0, endRadius; double beginRadiusSquared = 0, endRadiusSquared; endRadius = _radius * endNode.PressureFactor; endRadiusSquared = endRadius * endRadius; if (beginNode.IsEmpty == false) { beginRadius = _radius * beginNode.PressureFactor; beginRadiusSquared = beginRadius * beginRadius; } bool isInside = true; StrokeFIndices result = StrokeFIndices.Empty; foreach (ContourSegment hitSegment in hitContour) { if (hitSegment.IsArc) { // ISSUE-2004/06/15-vsmirnov - ellipse vs arc hit-testing is not implemented // and currently disabled in ErasingStroke } else { Vector hitBegin = hitSegment.Begin - endNode.Position; Vector hitEnd = hitBegin + hitSegment.Vector; // If the node shape is an ellipse, transform the scene to turn // the shape into circle. if (_nodeShapeToCircle.IsIdentity == false) { hitBegin = _nodeShapeToCircle.Transform(hitBegin); hitEnd = _nodeShapeToCircle.Transform(hitEnd); } bool isHit = false; // Hit-test the end node Vector nearest = GetNearest(hitBegin, hitEnd); if (nearest.LengthSquared < endRadiusSquared) { isHit = true; if (!DoubleUtil.AreClose(result.EndFIndex, StrokeFIndices.AfterLast)) { result.EndFIndex = StrokeFIndices.AfterLast; if (beginNode.IsEmpty) { result.BeginFIndex = StrokeFIndices.BeforeFirst; break; } if (DoubleUtil.AreClose(result.BeginFIndex, StrokeFIndices.BeforeFirst)) { break; } } } if ((beginNode.IsEmpty == false) && (!isHit || !DoubleUtil.AreClose(result.BeginFIndex, StrokeFIndices.BeforeFirst))) { // Hit-test the first node nearest = GetNearest(hitBegin - spineVector, hitEnd - spineVector); if (nearest.LengthSquared < beginRadiusSquared) { isHit = true; if (!DoubleUtil.AreClose(result.BeginFIndex, StrokeFIndices.BeforeFirst)) { result.BeginFIndex = StrokeFIndices.BeforeFirst; if (DoubleUtil.AreClose(result.EndFIndex, StrokeFIndices.AfterLast)) { break; } } } } // If both nodes are hit or nothing is hit at all, return. if (beginNode.IsEmpty || (!isHit && (quad.IsEmpty || (HitTestQuadSegment(quad, hitSegment.Begin, hitSegment.End) == false)))) { if (isInside && (WhereIsVectorAboutVector( endNode.Position - hitSegment.Begin, hitSegment.Vector) != HitResult.Right)) { isInside = false; } continue; } isInside = false; // NTRAID#Window OS bug-1029694-2004/10/18-xiaotu, refactor the code to make it a method // to increase the maintainability of the program. FxCop bug. // Calculate the exact locations to cut. CalculateCutLocations(spineVector, hitBegin, hitEnd, endRadius, beginRadius, ref result); if (result.IsFull) { break; } } } // if (!result.IsFull) { if (isInside == true) { System.Diagnostics.Debug.Assert(result.IsEmpty); result = StrokeFIndices.Full; } else if ((DoubleUtil.AreClose(result.EndFIndex, StrokeFIndices.BeforeFirst)) && (!DoubleUtil.AreClose(result.BeginFIndex, StrokeFIndices.AfterLast))) { result.EndFIndex = StrokeFIndices.AfterLast; } else if ((DoubleUtil.AreClose(result.BeginFIndex,StrokeFIndices.AfterLast)) && (!DoubleUtil.AreClose(result.EndFIndex, StrokeFIndices.BeforeFirst))) { result.BeginFIndex = StrokeFIndices.BeforeFirst; } } if (IsInvalidCutTestResult(result)) { return StrokeFIndices.Empty; } return result; } /// /// Clip-Testing a circluar inking segment against a linear segment. /// See http://tabletpc/longhorn/Specs/Rendering%20and%20Hit-Testing%20Ink%20in%20Avalon%20M11.doc section /// 5.4.4.14 Clip-Testing a Circular Inking Segment against a Linear Segment for details of the algorithm /// /// Represent the spine of the inking segment pointing from the beginNode to endNode /// Radius of the beginNode /// Radius of the endNode /// Hitting segment start point /// Hitting segment end point ///A double which represents the location for cutting private static double ClipTest(Vector spineVector, double beginRadius, double endRadius, Vector hitBegin, Vector hitEnd) { // First handle the special case when the spineVector is (0,0). In other words, this is the case // when the stylus stays at the the location but pressure changes. if (DoubleUtil.IsZero(spineVector.X) && DoubleUtil.IsZero(spineVector.Y)) { System.Diagnostics.Debug.Assert(DoubleUtil.AreClose(beginRadius, endRadius) == false); Vector nearest = GetNearest(hitBegin, hitEnd); double radius; if (nearest.X == 0) { radius = Math.Abs(nearest.Y); } else if (nearest.Y == 0) { radius = Math.Abs(nearest.X); } else { radius = nearest.Length; } return AdjustFIndex((radius - beginRadius) / (endRadius - beginRadius)); } // This change to ClipTest with a point if the two hitting segment are close enough. if (DoubleUtil.AreClose(hitBegin, hitEnd)) { return ClipTest(spineVector, beginRadius, endRadius, hitBegin); } double findex; Vector hitVector = hitEnd - hitBegin; if (DoubleUtil.IsZero(Vector.Determinant(spineVector, hitVector))) { // hitVector and spineVector are parallel findex = ClipTest(spineVector, beginRadius, endRadius, GetNearest(hitBegin, hitEnd)); System.Diagnostics.Debug.Assert(!double.IsNaN(findex)); } else { // On the line defined by the segment find point P1Xp, the nearest to the beginNode.Position double x = GetProjectionFIndex(hitBegin, hitEnd); Vector P1Xp = hitBegin + (hitVector * x); if (P1Xp.LengthSquared < (beginRadius * beginRadius)) { System.Diagnostics.Debug.Assert(DoubleUtil.IsBetweenZeroAndOne(x) == false); findex = ClipTest(spineVector, beginRadius, endRadius, (0 > x) ? hitBegin : hitEnd); System.Diagnostics.Debug.Assert(!double.IsNaN(findex)); } else { // Find the projection point P of endNode.Position to the line (beginNode.Position, B). Vector P1P2p = spineVector + GetProjection(-spineVector, P1Xp - spineVector); //System.Diagnostics.Debug.Assert(false == DoubleUtil.IsZero(P1P2p.LengthSquared)); //System.Diagnostics.Debug.Assert(false == DoubleUtil.IsZero(endRadius - beginRadius + P1P2p.Length)); // There checks are here since if either fail no real solution can be caculated and we may // as well bail out now and save the caculations that are below. if (DoubleUtil.IsZero(P1P2p.LengthSquared) || DoubleUtil.IsZero(endRadius - beginRadius + P1P2p.Length)) return 1d; // Calculate the findex of the point to split the ink segment at. findex = (P1Xp.Length - beginRadius) / (endRadius - beginRadius + P1P2p.Length); System.Diagnostics.Debug.Assert(!double.IsNaN(findex)); // Find the projection of the split point to the line of this segment. Vector S = spineVector * findex; double r = GetProjectionFIndex(hitBegin - S, hitEnd - S); // If the nearest point misses the segment, then find the findex // of the node nearest to the segment. if (false == DoubleUtil.IsBetweenZeroAndOne(r)) { findex = ClipTest(spineVector, beginRadius, endRadius, (0 > r) ? hitBegin : hitEnd); System.Diagnostics.Debug.Assert(!double.IsNaN(findex)); } } } return AdjustFIndex(findex); } ////// Clip-Testing a circular inking segment again a hitting point. /// /// What need to find out a doulbe value s, which is between 0 and 1, such that /// DistanceOf(hit - s*spine) = beginRadius + s * (endRadius - beginRadius) /// That is /// (hit.X-s*spine.X)^2 + (hit.Y-s*spine.Y)^2 = [beginRadius + s*(endRadius-beginRadius)]^2 /// Rearrange /// A*s^2 + B*s + C = 0 /// where the value of A, B and C are described in the source code. /// Solving for s: /// s = (-B + sqrt(B^2-4A*C))/(2A) or s = (-B - sqrt(B^2-4A*C))/(2A) /// The smaller value between 0 and 1 is the one we want and discard the other one. /// /// Represent the spine of the inking segment pointing from the beginNode to endNode /// Radius of the beginNode /// Radius of the endNode /// The hitting point ///A double which represents the location for cutting private static double ClipTest(Vector spine, double beginRadius, double endRadius, Vector hit) { double radDiff = endRadius - beginRadius; double A = spine.X*spine.X + spine.Y*spine.Y - radDiff*radDiff; double B = -2.0f*(hit.X*spine.X + hit.Y * spine.Y + beginRadius*radDiff); double C = hit.X * hit.X + hit.Y * hit.Y - beginRadius * beginRadius; // There checks are here since if either fail no real solution can be caculated and we may // as well bail out now and save the caculations that are below. if (DoubleUtil.IsZero(A) || !DoubleUtil.GreaterThanOrClose(B*B, 4.0f*A*C)) return 1d; double tmp = Math.Sqrt(B*B-4.0f * A * C); double s1 = (-B + tmp)/(2.0f * A); double s2 = (-B - tmp)/(2.0f * A); double findex; if (DoubleUtil.IsBetweenZeroAndOne(s1) && DoubleUtil.IsBetweenZeroAndOne(s1)) { findex = Math.Min(s1, s2); } else if (DoubleUtil.IsBetweenZeroAndOne(s1)) { findex = s1; } else if (DoubleUtil.IsBetweenZeroAndOne(s2)) { findex = s2; } else { // There is still possiblity that value like 1.0000000000000402 is not considered // as "IsOne" by DoubleUtil class. We should be at either one of the following two cases: // 1. s1/s2 around 0 but not close enough (say -0.0000000000001) // 2. s1/s2 around 1 but not close enought (say 1.0000000000000402) if (s1 > 1d && s2 > 1d) { findex = 1d; } else if (s1 < 0d && s2 < 0d) { findex = 0d; } else { findex = Math.Abs(Math.Min(s1, s2) - 0d) < Math.Abs(Math.Max(s1, s2) - 1d) ? 0d : 1d; } } return AdjustFIndex(findex); } ////// Helper function to find out the relative location of a segment {segBegin, segEnd} against /// a strokeNode (spine). /// /// the spineVector of the StrokeNode /// Start position of the line segment /// End position of the line segment ///HitResult private static HitResult WhereIsNodeAboutSegment(Vector spine, Vector segBegin, Vector segEnd) { HitResult whereabout = HitResult.Right; Vector segVector = segEnd - segBegin; if ((WhereIsVectorAboutVector(-segBegin, segVector) == HitResult.Left) && !DoubleUtil.IsZero(Vector.Determinant(spine, segVector))) { whereabout = HitResult.Left; } return whereabout; } ////// Helper method to calculate the exact location to cut /// /// Vector the relative location of the two inking nodes /// the begin point of the hitting segment /// the end point of the hitting segment /// endNode radius /// beginNode radius /// StrokeFIndices representing the location for cutting 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; } } } } private double _radius = 0; private Size _radii; private Matrix _transform; private Matrix _nodeShapeToCircle; private Matrix _circleToNodeShape; } } // File provided for Reference Use Only by Microsoft Corporation (c) 2007.
Link Menu
This book is available now!
Buy at Amazon US or
Buy at Amazon UK
- ProtectedConfigurationSection.cs
- EdmComplexTypeAttribute.cs
- LinqDataView.cs
- Primitive.cs
- ByeOperationCD1AsyncResult.cs
- GraphicsPath.cs
- CodePropertyReferenceExpression.cs
- OperationResponse.cs
- ToolboxItemCollection.cs
- CrossSiteScriptingValidation.cs
- OleDbSchemaGuid.cs
- CapacityStreamGeometryContext.cs
- DbDeleteCommandTree.cs
- FixedPosition.cs
- ObjectListSelectEventArgs.cs
- SqlConnectionPoolProviderInfo.cs
- DrawingState.cs
- EdmRelationshipRoleAttribute.cs
- ArithmeticLiteral.cs
- ApplyTemplatesAction.cs
- AuthenticatingEventArgs.cs
- PageCodeDomTreeGenerator.cs
- EncodingNLS.cs
- DataGridViewColumn.cs
- CodeTypeMemberCollection.cs
- DataTrigger.cs
- TcpStreams.cs
- ReaderWriterLockWrapper.cs
- SecurityUtils.cs
- ErrorHandler.cs
- XmlUtil.cs
- PassportIdentity.cs
- DataGridRow.cs
- FileDialogPermission.cs
- CqlParser.cs
- ReliableRequestSessionChannel.cs
- XmlDataSourceDesigner.cs
- CopyNodeSetAction.cs
- DashStyles.cs
- BaseUriHelper.cs
- WindowsListViewItemCheckBox.cs
- Vector.cs
- PolyBezierSegment.cs
- DynamicField.cs
- SolidColorBrush.cs
- ChildDocumentBlock.cs
- IPGlobalProperties.cs
- DiscoveryInnerClientAdhocCD1.cs
- DataContractFormatAttribute.cs
- RegistryKey.cs
- XPathItem.cs
- ClientBuildManager.cs
- OracleLob.cs
- DbDataSourceEnumerator.cs
- SubordinateTransaction.cs
- MaskedTextBox.cs
- ObjectManager.cs
- Handle.cs
- Utils.cs
- EndpointAddressMessageFilter.cs
- WindowsTokenRoleProvider.cs
- GcSettings.cs
- DataControlHelper.cs
- DataServiceRequestException.cs
- CodeConditionStatement.cs
- InstanceDescriptor.cs
- Assembly.cs
- ScriptHandlerFactory.cs
- OrderingExpression.cs
- Facet.cs
- BamlResourceContent.cs
- Models.cs
- WinCategoryAttribute.cs
- PageHandlerFactory.cs
- WebEventTraceProvider.cs
- SafeHandles.cs
- SourceFileInfo.cs
- DataGridViewElement.cs
- ToolboxComponentsCreatedEventArgs.cs
- Rijndael.cs
- EventDescriptor.cs
- SBCSCodePageEncoding.cs
- TextEndOfParagraph.cs
- CodeNamespaceImportCollection.cs
- DynamicILGenerator.cs
- Char.cs
- IdentityNotMappedException.cs
- MergeExecutor.cs
- IBuiltInEvidence.cs
- ServicePointManager.cs
- ConfigurationConverterBase.cs
- AppSettingsSection.cs
- TextTreeExtractElementUndoUnit.cs
- ConstraintManager.cs
- WSDualHttpSecurityMode.cs
- CodeTypeConstructor.cs
- LocalsItemDescription.cs
- SqlDataReader.cs
- DataServices.cs
- TemplateDefinition.cs