EllipticalNodeOperations.cs source code in C# .NET

Source code for the .NET framework in C#

                        

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, IEnumerable hitContour)
        {
            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, IEnumerable hitContour) 
        { 
            // 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, IEnumerable hitContour)
        {
            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, IEnumerable hitContour) 
        { 
            // 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

Network programming in C#, Network Programming in VB.NET, Network Programming in .NET
This book is available now!
Buy at Amazon US or
Buy at Amazon UK