Пример #1
0
        /// <summary>
        /// Gets a StrokeNode at the specified index that connects to a stroke at the previousIndex
        /// previousIndex can be -1 to signify it should be empty (first strokeNode)
        /// </summary>
        /// <returns></returns>
        internal StrokeNode this[int index, int previousIndex]
        {
            get
            {
                if (_stylusPoints == null || index < 0 || index >= _stylusPoints.Count || previousIndex < -1 || previousIndex >= index)
                {
                    throw new IndexOutOfRangeException();
                }

                StylusPoint stylusPoint            = _stylusPoints[index];
                StylusPoint previousStylusPoint    = (previousIndex == -1 ? new StylusPoint() : _stylusPoints[previousIndex]);
                float       pressureFactor         = 1.0f;
                float       previousPressureFactor = 1.0f;
                if (_usePressure)
                {
                    pressureFactor         = StrokeNodeIterator.GetNormalizedPressureFactor(stylusPoint.PressureFactor);
                    previousPressureFactor = StrokeNodeIterator.GetNormalizedPressureFactor(previousStylusPoint.PressureFactor);
                }

                StrokeNodeData nodeData     = new StrokeNodeData((Point)stylusPoint, pressureFactor);
                StrokeNodeData lastNodeData = StrokeNodeData.Empty;
                if (previousIndex != -1)
                {
                    lastNodeData = new StrokeNodeData((Point)previousStylusPoint, previousPressureFactor);
                }

                //we use previousIndex+1 because index can skip ahead
                return(new StrokeNode(_operations, previousIndex + 1, nodeData, lastNodeData, index == _stylusPoints.Count - 1 /*Is this the last node?*/));
            }
        }
Пример #2
0
        /// <summary>
        /// Computes the bounds of a node
        /// </summary>
        /// <param name="node">node to compute bounds of</param>
        /// <returns>bounds of the node</returns>
        internal Rect GetNodeBounds(StrokeNodeData node)
        {
            if (_shapeBounds.IsEmpty)
            {
                int i;
                for (i = 0; (i + 1) < _vertices.Length; i += 2)
                {
                    _shapeBounds.Union(new Rect((Point)_vertices[i], (Point)_vertices[i + 1]));
                }
                if (i < _vertices.Length)
                {
                    _shapeBounds.Union((Point)_vertices[i]);
                }
            }

            Rect boundingBox = _shapeBounds;
            System.Diagnostics.Debug.Assert((boundingBox.X <= 0) && (boundingBox.Y <= 0));

            double pressureFactor = node.PressureFactor;
            if (!DoubleUtil.AreClose(pressureFactor,1d))
            {
                boundingBox = new Rect(
                    _shapeBounds.X * pressureFactor,
                    _shapeBounds.Y * pressureFactor,
                    _shapeBounds.Width * pressureFactor,
                    _shapeBounds.Height * pressureFactor);
            }
            
            boundingBox.Location += (Vector)node.Position;

            return boundingBox;
        }
Пример #3
0
        /// <summary>
        /// Hit-tests a stroke segment defined by two nodes against a linear segment.
        /// </summary>
        /// <param name="beginNode">Begin node of the stroke segment to hit-test. Can be empty (none)</param>
        /// <param name="endNode">End node of the stroke segment</param>
        /// <param name="quad">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</param>
        /// <param name="hitBeginPoint">an end point of the hitting linear segment</param>
        /// <param name="hitEndPoint">an end point of the hitting linear segment</param>
        /// <returns>true if the hitting segment intersect the contour comprised of the two stroke nodes</returns>
        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);
        }
Пример #4
0
        /// <summary>
        /// Constructor.
        /// </summary>
        /// <param name="operations">StrokeNodeOperations object created for particular rendering</param>
        /// <param name="index">Index of the node on the stroke spine</param>
        /// <param name="nodeData">StrokeNodeData for this node</param>
        /// <param name="lastNodeData">StrokeNodeData for the precedeng node</param>
        /// <param name="isLastNode">Whether the current node is the last node</param>
        internal StrokeNode(
            StrokeNodeOperations operations,
            int index,
            StrokeNodeData nodeData,
            StrokeNodeData lastNodeData,
            bool isLastNode)
        {
            System.Diagnostics.Debug.Assert(operations != null);
            System.Diagnostics.Debug.Assert((nodeData.IsEmpty == false) && (index >= 0));
          

            _operations = operations;
            _index = index;
            _thisNode = nodeData;
            _lastNode = lastNodeData;
            _isQuadCached = lastNodeData.IsEmpty;
            _connectingQuad = Quad.Empty;
            _isLastNode = isLastNode;
        }
        /// <summary>
        /// Constructor.
        /// </summary>
        /// <param name="operations">StrokeNodeOperations object created for particular rendering</param>
        /// <param name="index">Index of the node on the stroke spine</param>
        /// <param name="nodeData">StrokeNodeData for this node</param>
        /// <param name="lastNodeData">StrokeNodeData for the precedeng node</param>
        /// <param name="isLastNode">Whether the current node is the last node</param>
        internal StrokeNode(
            StrokeNodeOperations operations,
            int index,
            StrokeNodeData nodeData,
            StrokeNodeData lastNodeData,
            bool isLastNode)
        {
            System.Diagnostics.Debug.Assert(operations != null);
            System.Diagnostics.Debug.Assert((nodeData.IsEmpty == false) && (index >= 0));


            _operations     = operations;
            _index          = index;
            _thisNode       = nodeData;
            _lastNode       = lastNodeData;
            _isQuadCached   = lastNodeData.IsEmpty;
            _connectingQuad = Quad.Empty;
            _isLastNode     = isLastNode;
        }
Пример #6
0
        /// <summary>
        ///
        /// </summary>
        /// <param name="node"></param>
        /// <param name="quad"></param>
        /// <returns></returns>
        internal override IEnumerable <ContourSegment> 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));
            }
        }
Пример #7
0
        /// <summary>
        /// Gets a StrokeNode at the specified index that connects to a stroke at the previousIndex
        /// previousIndex can be -1 to signify it should be empty (first strokeNode)
        /// </summary>
        /// <returns></returns>
        internal StrokeNode this[int index, int previousIndex]
        {
            get
            {
                if (_stylusPoints == null || index < 0 || index >= _stylusPoints.Count || previousIndex < -1 || previousIndex >= index)
                {
                    throw new IndexOutOfRangeException();
                }

                StylusPoint stylusPoint = _stylusPoints[index];
                StylusPoint previousStylusPoint = (previousIndex == -1 ? new StylusPoint() : _stylusPoints[previousIndex]);
                float pressureFactor = 1.0f;
                float previousPressureFactor = 1.0f;
                if (_usePressure)
                {
                    pressureFactor = StrokeNodeIterator.GetNormalizedPressureFactor(stylusPoint.PressureFactor);
                    previousPressureFactor = StrokeNodeIterator.GetNormalizedPressureFactor(previousStylusPoint.PressureFactor);
                }

                StrokeNodeData nodeData = new StrokeNodeData((Point)stylusPoint, pressureFactor);
                StrokeNodeData lastNodeData = StrokeNodeData.Empty;
                if (previousIndex != -1)
                {
                    lastNodeData = new StrokeNodeData((Point)previousStylusPoint, previousPressureFactor);
                }

                //we use previousIndex+1 because index can skip ahead
                return new StrokeNode(_operations, previousIndex + 1, nodeData, lastNodeData, index == _stylusPoints.Count - 1 /*Is this the last node?*/);
            }
        }
        /// <summary>
        /// CutTest an inking StrokeNode segment (two nodes and a connecting quadrangle) against a hitting contour
        /// (represented by an enumerator of Contoursegments).
        /// </summary>
        /// <param name="beginNode">The begin StrokeNodeData</param>
        /// <param name="endNode">The end StrokeNodeData</param>
        /// <param name="quad">Connecing quadrangle between the begin and end inking node</param>
        /// <param name="hitContour">The hitting ContourSegments</param>
        /// <returns>StrokeFIndices representing the location for cutting</returns>
        internal override StrokeFIndices CutTest(
            StrokeNodeData beginNode, StrokeNodeData endNode, Quad quad, IEnumerable<ContourSegment> 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;
        }
        /// <summary>
        /// Hit-tests a stroke segment defined by two nodes against another stroke segment.
        /// </summary>
        /// <param name="beginNode">Begin node of the stroke segment to hit-test. Can be empty (none)</param>
        /// <param name="endNode">End node of the stroke segment</param>
        /// <param name="quad">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</param>
        /// <param name="hitContour">a collection of basic segments outlining the hitting contour</param>
        /// <returns>true if the contours intersect or overlap</returns>
        internal override bool HitTest(
            StrokeNodeData beginNode, StrokeNodeData endNode, Quad quad, IEnumerable<ContourSegment> 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);
        }
 /// <summary>
 /// ISSUE-2004/06/15- temporary workaround to avoid hit-testing ellipses with ellipses
 /// </summary>
 /// <param name="beginNode"></param>
 /// <param name="endNode"></param>
 /// <returns></returns>
 internal override IEnumerable<ContourSegment> GetNonBezierContourSegments(StrokeNodeData beginNode, StrokeNodeData endNode)
 {
     Quad quad = beginNode.IsEmpty ? Quad.Empty : base.GetConnectingQuad(beginNode, endNode);
     return base.GetContourSegments(endNode, quad);
 }
Пример #11
0
 internal void GetNodeContourPoints(StrokeNodeData node, List<Point> pointBuffer)
 {
     double pressureFactor = node.PressureFactor;
     if (DoubleUtil.AreClose(pressureFactor, 1d))
     {
         for (int i = 0; i < _vertices.Length; i++)
         {
             pointBuffer.Add(node.Position + _vertices[i]);
         }
     }
     else
     {
         for (int i = 0; i < _vertices.Length; i++)
         {
             pointBuffer.Add(node.Position + (_vertices[i] * pressureFactor));
         }
     }
 }
Пример #12
0
        /// <summary>
        /// Hit-tests ink segment defined by two nodes against a linear segment.
        /// </summary>
        /// <param name="beginNode">Begin node of the ink segment</param>
        /// <param name="endNode">End node of the ink segment</param>
        /// <param name="quad">Pre-computed quadrangle connecting the two ink nodes</param>
        /// <param name="hitBeginPoint">Begin point of the hitting segment</param>
        /// <param name="hitEndPoint">End point of the hitting segment</param>
        /// <returns>Exact location to cut at represented by StrokeFIndices</returns>
        internal virtual StrokeFIndices CutTest(
            StrokeNodeData beginNode, StrokeNodeData endNode, Quad quad, Point hitBeginPoint, Point hitEndPoint)
        {
            StrokeFIndices result = StrokeFIndices.Empty;

            // First, find out if the hitting segment intersects with either of the ink nodes
            for (int node = (beginNode.IsEmpty ? 1 : 0); node < 2; node++)
            {
                Point position = (node == 0) ? beginNode.Position : endNode.Position;
                double pressureFactor = (node == 0) ? beginNode.PressureFactor : endNode.PressureFactor;

                // Adjust the segment for the node's pressure factor
                Vector hitBegin = hitBeginPoint - position;
                Vector hitEnd = hitEndPoint - position;
                if (pressureFactor != 1)
                {
                    System.Diagnostics.Debug.Assert(DoubleUtil.IsZero(pressureFactor) == false);
                    hitBegin /= pressureFactor;
                    hitEnd /= pressureFactor;
                }
                // Hit-test the node against the segment
                if (true == HitTestPolygonSegment(_vertices, hitBegin, hitEnd))
                {
                    if (node == 0)
                    {
                        result.BeginFIndex = StrokeFIndices.BeforeFirst;
                        result.EndFIndex = 0;
                    }
                    else
                    {
                        result.EndFIndex = StrokeFIndices.AfterLast;
                        if (beginNode.IsEmpty)
                        {
                            result.BeginFIndex = StrokeFIndices.BeforeFirst;
                        }
                        else if (result.BeginFIndex != StrokeFIndices.BeforeFirst)
                        {
                            result.BeginFIndex = 1;
                        }
                    }
                }
            }

            // If both nodes are hit, return.
            if (result.IsFull)
            {
                return result;
            }
            // If there's no hit at all, return.
            if (result.IsEmpty && (quad.IsEmpty || !HitTestQuadSegment(quad, hitBeginPoint, hitEndPoint)))
            {
                return result;
            }

            // The segments do intersect. Find findices on the ink segment to cut it at.
            if (result.BeginFIndex != StrokeFIndices.BeforeFirst)
            {
                // The begin node is not hit, i.e. the begin findex is on this spine segment, find it.
                result.BeginFIndex = ClipTest(
                    (endNode.Position - beginNode.Position) / beginNode.PressureFactor,
                    (endNode.PressureFactor / beginNode.PressureFactor) - 1,
                    (hitBeginPoint - beginNode.Position) / beginNode.PressureFactor,
                    (hitEndPoint - beginNode.Position) / beginNode.PressureFactor);
            }

            if (result.EndFIndex != StrokeFIndices.AfterLast)
            {
                // The end node is not hit, i.e. the end findex is on this spine segment, find it.
                result.EndFIndex = 1 - ClipTest(
                    (beginNode.Position - endNode.Position) / endNode.PressureFactor,
                    (beginNode.PressureFactor / endNode.PressureFactor) - 1,
                    (hitBeginPoint - endNode.Position) / endNode.PressureFactor,
                    (hitEndPoint - endNode.Position) / endNode.PressureFactor);
            }

            if (IsInvalidCutTestResult(result))
            {
                return StrokeFIndices.Empty;
            }

            return result;
        }
Пример #13
0
        /// <summary>
        /// Cut-test ink segment defined by two nodes and a connecting quad against a linear segment
        /// </summary>
        /// <param name="beginNode">Begin node of the ink segment</param>
        /// <param name="endNode">End node of the ink segment</param>
        /// <param name="quad">Pre-computed quadrangle connecting the two ink nodes</param>
        /// <param name="hitBeginPoint">egin point of the hitting segment</param>
        /// <param name="hitEndPoint">End point of the hitting segment</param>
        /// <returns>Exact location to cut at represented by StrokeFIndices</returns>
        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);
        }
Пример #14
0
        /// <summary>
        /// Hit-tests a stroke segment defined by two nodes against another stroke segment.
        /// </summary>
        /// <param name="beginNode">Begin node of the stroke segment to hit-test. Can be empty (none)</param>
        /// <param name="endNode">End node of the stroke segment</param>
        /// <param name="quad">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</param>
        /// <param name="hitContour">a collection of basic segments outlining the hitting contour</param>
        /// <returns>true if the contours intersect or overlap</returns>
        internal override bool HitTest(
            StrokeNodeData beginNode, StrokeNodeData endNode, Quad quad, IEnumerable <ContourSegment> 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);
        }
Пример #15
0
        /// <summary>
        /// ISSUE-2004/06/15- temporary workaround to avoid hit-testing ellipses with ellipses
        /// </summary>
        /// <param name="beginNode"></param>
        /// <param name="endNode"></param>
        /// <returns></returns>
        internal override IEnumerable <ContourSegment> GetNonBezierContourSegments(StrokeNodeData beginNode, StrokeNodeData endNode)
        {
            Quad quad = beginNode.IsEmpty ? Quad.Empty : base.GetConnectingQuad(beginNode, endNode);

            return(base.GetContourSegments(endNode, quad));
        }
Пример #16
0
        /// <summary>
        /// Hit-tests ink segment defined by two nodes against a linear segment.
        /// </summary>
        /// <param name="beginNode">Begin node of the ink segment</param>
        /// <param name="endNode">End node of the ink segment</param>
        /// <param name="quad">Pre-computed quadrangle connecting the two ink nodes</param>
        /// <param name="hitBeginPoint">Begin point of the hitting segment</param>
        /// <param name="hitEndPoint">End point of the hitting segment</param>
        /// <returns>true if there's intersection, false otherwise</returns>
        internal virtual bool HitTest(
            StrokeNodeData beginNode, StrokeNodeData endNode, Quad quad, Point hitBeginPoint, Point hitEndPoint)
        {
            // Check for special cases when the endNode is the very first one (beginNode.IsEmpty) 
            // or one node is completely inside the other. In either case the connecting quad 
            // would be Empty and we need to hit-test against the biggest node (the one with 
            // the greater PressureFactor)
            if (quad.IsEmpty)
            {
                Point position;
                double pressureFactor;
                if (beginNode.IsEmpty || (endNode.PressureFactor > beginNode.PressureFactor))
                {
                    position = endNode.Position;
                    pressureFactor = endNode.PressureFactor;
                }
                else
                {
                    position = beginNode.Position;
                    pressureFactor = beginNode.PressureFactor;
                }

                // Find the coordinates of the hitting segment relative to the ink node
                Vector hitBegin = hitBeginPoint - position, hitEnd = hitEndPoint - position;
                if (pressureFactor != 1)
                {
                    // Instead of applying pressure to the node, do reverse scaling on
                    // the hitting segment. This allows us use the original array of vertices 
                    // in hit-testing.
                    System.Diagnostics.Debug.Assert(DoubleUtil.IsZero(pressureFactor) == false);
                    hitBegin /= pressureFactor;
                    hitEnd /= pressureFactor;
                }
                return HitTestPolygonSegment(_vertices, hitBegin, hitEnd);
            }
            else
            {
                // Iterate through the vertices of the contour of the ink segment
                // check where the hitting segment is about them, return false if it's 
                // on the outer (left) side of the ink contour. This implementation might
                // look more complex than straightforward separated hit-testing of three 
                // polygons (beginNode, quad, endNode), but it's supposed to be more optimal
                // because the number of edges it hit-tests is approximately twice less
                // than with the straightforward implementation.

                // Start with the segment quad.C->quad.D
                Vector hitBegin = hitBeginPoint - beginNode.Position;
                Vector hitEnd = hitEndPoint - beginNode.Position;
                HitResult hitResult = WhereIsSegmentAboutSegment(
                    hitBegin, hitEnd, quad.C - beginNode.Position, quad.D - beginNode.Position);
                if (HitResult.Left == hitResult)
                {
                    return false;
                }

                // Continue clockwise from quad.D to quad.C

                HitResult firstResult = hitResult, lastResult = hitResult;
                double pressureFactor = beginNode.PressureFactor;

                // Find the index of the vertex that is quad.D 
                // Use count var to avoid infinite loop, normally it shouldn't 
                // happen but it doesn't hurt to check it just in case.
                int i = 0, count = _vertices.Length;
                Vector vertex = new Vector();
                for (i = 0; i < count; i++)
                {
                    vertex = _vertices[i] * pressureFactor;
                    // Here and in a few more places down the code, when comparing
                    // a quad's vertex vs a scaled shape vertex, it's important to 
                    // compute them the same way as in GetConnectingQuad, so that not
                    // hit that double's computation error. For instance, sometimes the
                    // expression (vertex == quad.D - beginNode.Position) gives 'false' 
                    // while the expression below gives 'true'. (Another workaround is to
                    // use DoubleUtil.AreClose but that;d be less performant)
                    if ((beginNode.Position + vertex) == quad.D)
                    {
                        break;
                    }
                }
                System.Diagnostics.Debug.Assert(count > 0);
                // This loop does the iteration thru the edges of the ink segment 
                // clockwise from quad.D to quad.C. 
                for (int node = 0; node < 2; node++)
                {
                    Point nodePosition = (node == 0) ? beginNode.Position : endNode.Position;
                    Point end = (node == 0) ? quad.A : quad.C;

                    count = _vertices.Length;
                    while (((nodePosition + vertex) != end) && (count != 0))
                    {
                        i = (i + 1) % _vertices.Length;
                        Vector nextVertex = (pressureFactor == 1) ? _vertices[i] : (_vertices[i] * pressureFactor);
                        hitResult = WhereIsSegmentAboutSegment(hitBegin, hitEnd, vertex, nextVertex);
                        if (HitResult.Hit == hitResult)
                        {
                            return true;
                        }
                        if (true == IsOutside(hitResult, lastResult))
                        {
                            return false;
                        }
                        lastResult = hitResult;
                        vertex = nextVertex;
                        count--;
                    }
                    System.Diagnostics.Debug.Assert(count > 0);

                    if (node == 0)
                    {
                        // The first iteration is done thru the outer segments of beginNode
                        // and ends at quad.A, for the second one make some adjustments 
                        // to continue iterating through quad.AB and the outer segments of 
                        // endNode up to quad.C
                        pressureFactor = endNode.PressureFactor;

                        Vector spineVector = endNode.Position - beginNode.Position;
                        vertex -= spineVector;
                        hitBegin -= spineVector;
                        hitEnd -= spineVector;

                        // Find the index of the vertex that is quad.B
                        count = _vertices.Length;
                        while (((endNode.Position + _vertices[i] * pressureFactor) != quad.B) && (count != 0))
                        {
                            i = (i + 1) % _vertices.Length;
                            count--;
                        }
                        System.Diagnostics.Debug.Assert(count > 0);
                        i--;
                    }
                }
                return (false == IsOutside(firstResult, hitResult));
            }
        }
Пример #17
0
 /// <summary>
 /// Hit-tests a stroke segment defined by two nodes against another stroke segment.
 /// </summary>
 /// <param name="beginNode">Begin node of the stroke segment to hit-test. Can be empty (none)</param>
 /// <param name="endNode">End node of the stroke segment</param>
 /// <param name="quad">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</param>
 /// <param name="hitContour">a collection of basic segments outlining the hitting contour</param>
 /// <returns>true if the contours intersect or overlap</returns>
 internal virtual bool HitTest(
     StrokeNodeData beginNode, StrokeNodeData endNode, Quad quad, IEnumerable<ContourSegment> hitContour)
 {           
     // Check for special cases when the endNode is the very first one (beginNode.IsEmpty) 
     // or one node is completely inside the other. In either case the connecting quad 
     // would be Empty and we need to hittest against the biggest node (the one with 
     // the greater PressureFactor)
     if (quad.IsEmpty)
     {
         // NTRAID#Window OS bug-1029694-2004/10/15-xiaotu, refactor the code to make it a method
         // to increase the maintainability of the program. FxCop bug.
         
         // Make a call to hit-test the biggest node the hitting contour.
         return HitTestPolygonContourSegments(hitContour, beginNode, endNode);
     }
     else
     {
         // NTRAID#Window OS bug-1029694-2004/10/15-xiaotu, refactor the code to make it a method
         // to increase the maintainability of the program. FxCop bug.
     
         // HitTest the the hitting contour against the inking contour
         return HitTestInkContour(hitContour, quad, beginNode, endNode);
     }
 }
Пример #18
0
        /// <summary>
        /// CutTest an inking StrokeNode segment (two nodes and a connecting quadrangle) against a hitting contour
        /// (represented by an enumerator of Contoursegments).
        /// </summary>
        /// <param name="beginNode">The begin StrokeNodeData</param>
        /// <param name="endNode">The end StrokeNodeData</param>
        /// <param name="quad">Connecing quadrangle between the begin and end inking node</param>
        /// <param name="hitContour">The hitting ContourSegments</param>
        /// <returns>StrokeFIndices representing the location for cutting</returns>
        internal override StrokeFIndices CutTest(
            StrokeNodeData beginNode, StrokeNodeData endNode, Quad quad, IEnumerable <ContourSegment> 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);
        }
Пример #19
0
        /// <summary>
        /// CutTest
        /// </summary>
        /// <param name="beginNode">Begin node of the stroke segment to hit-test. Can be empty (none)</param>
        /// <param name="endNode">End node of the stroke segment</param>
        /// <param name="quad">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</param>
        /// <param name="hitContour">a collection of basic segments outlining the hitting contour</param>
        /// <returns></returns>
        internal virtual StrokeFIndices CutTest(
            StrokeNodeData beginNode, StrokeNodeData endNode, Quad quad, IEnumerable<ContourSegment> hitContour)
        {
            if (beginNode.IsEmpty)
            {
                if (HitTest(beginNode, endNode, quad, hitContour) == true)
                {
                    return StrokeFIndices.Full;
                }
                return StrokeFIndices.Empty;
            }

            StrokeFIndices result = StrokeFIndices.Empty;
            bool isInside = true;
            Vector spineVector = (endNode.Position - beginNode.Position) / beginNode.PressureFactor;
            Vector spineVectorReversed = (beginNode.Position - endNode.Position) / endNode.PressureFactor;
            double pressureDelta = (endNode.PressureFactor / beginNode.PressureFactor) - 1;
            double pressureDeltaReversed = (beginNode.PressureFactor / endNode.PressureFactor) - 1;

            foreach (ContourSegment hitSegment in hitContour)
            {
                // NTRAID#Window OS bug-1029694-2004/10/19-xiaotu, refactor the code to make it a method
                // to increase the maintainability of the program. FxCop bug.

                // First, find out if hitSegment intersects with either of the ink nodes
                bool isHit = HitTestStrokeNodes(hitSegment,beginNode,endNode, ref result);

                // If both nodes are hit, return.
                if (result.IsFull)
                {
                    return result;
                }

                // If neither of the nodes is hit, hit-test the connecting quad
                if (isHit == false)
                {
                    // If neither of the nodes is hit and the contour of one node is entirely 
                    // inside the contour of the other node, then done with this hitting segment
                    if (!quad.IsEmpty)
                    {
                        isHit = hitSegment.IsArc
                             ? HitTestQuadCircle(quad, hitSegment.Begin + hitSegment.Radius, hitSegment.Radius)
                             : HitTestQuadSegment(quad, hitSegment.Begin, hitSegment.End);
                    }
                    
                    if (isHit == false)
                    {
                        if (isInside == true)
                        {
                            isInside = hitSegment.IsArc
                                ? (WhereIsVectorAboutArc(endNode.Position - hitSegment.Begin - hitSegment.Radius,
                                    -hitSegment.Radius, hitSegment.Vector - hitSegment.Radius) != HitResult.Hit)
                                : (WhereIsVectorAboutVector(
                                    endNode.Position - hitSegment.Begin, hitSegment.Vector) == HitResult.Right);
                        }
                        continue;
                    }
                }

                isInside = false;

                // NTRAID#Window OS bug-1029694-2004/10/15-xiaotu, refactor the code to make it a new method
                // CalculateClipLocation to increase the maintainability of the program. FxCop bug.

                // If the begin node is not hit, find the begin findex on the ink segment to cut it at
                if (!DoubleUtil.AreClose(result.BeginFIndex, StrokeFIndices.BeforeFirst))
                {
                    double findex = CalculateClipLocation(hitSegment, beginNode, spineVector, pressureDelta);
                    if (findex != StrokeFIndices.BeforeFirst)
                    {
                        System.Diagnostics.Debug.Assert(findex >= 0 && findex <= 1);
                        if (result.BeginFIndex > findex)
                        {
                            result.BeginFIndex = findex;
                        }
                    }
                }

                // If the end node is not hit, find the end findex on the ink segment to cut it at
                if (!DoubleUtil.AreClose(result.EndFIndex, StrokeFIndices.AfterLast))
                {
                    double findex = CalculateClipLocation(hitSegment, endNode, spineVectorReversed, pressureDeltaReversed);
                    if (findex != StrokeFIndices.BeforeFirst)
                    {
                        System.Diagnostics.Debug.Assert(findex >= 0 && findex <= 1);
                        findex = 1 - findex;
                        if (result.EndFIndex < findex)
                        {
                            result.EndFIndex = findex;
                        }
                    }
                }
            }

            if (DoubleUtil.AreClose(result.BeginFIndex, StrokeFIndices.AfterLast))
            {
                if (!DoubleUtil.AreClose(result.EndFIndex, StrokeFIndices.BeforeFirst))
                {
                    result.BeginFIndex = StrokeFIndices.BeforeFirst;
                }
            }
            else if (DoubleUtil.AreClose(result.EndFIndex, StrokeFIndices.BeforeFirst))
            {
                result.EndFIndex = StrokeFIndices.AfterLast;
            }

            if (IsInvalidCutTestResult(result))
            {
                return StrokeFIndices.Empty;
            }

            return (result.IsEmpty && isInside) ? StrokeFIndices.Full : result;
        }
Пример #20
0
        /// <summary>
        /// Finds connecting points for a pair of stroke nodes
        /// </summary>
        /// <param name="beginNode">a node to connect</param>
        /// <param name="endNode">another node, next to beginNode</param>
        /// <returns>connecting quadrangle</returns>
        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)));
        }
Пример #21
0
        /// <summary>
        /// Helper function to hit-test the biggest node against hitting contour segments
        /// </summary>
        /// <param name="hitContour">a collection of basic segments outlining the hitting contour</param>
        /// <param name="beginNode">Begin node of the stroke segment to hit-test. Can be empty (none)</param>
        /// <param name="endNode">End node of the stroke segment</param>
        /// <returns>true if hit; false otherwise</returns>
        private bool HitTestPolygonContourSegments(
            IEnumerable<ContourSegment> hitContour, StrokeNodeData beginNode, StrokeNodeData endNode)
        {
            bool isHit = false;

            // The bool variable isInside is used here to track that case. It answers to
            // '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;

            Point position;
            double pressureFactor;
            if (beginNode.IsEmpty || endNode.PressureFactor > beginNode.PressureFactor)
            {
                position = endNode.Position;
                pressureFactor = endNode.PressureFactor;
            }
            else
            {
                position = beginNode.Position;
                pressureFactor = beginNode.PressureFactor;
            }

            // Enumerate through the segments of the hitting contour and test them 
            // one by one against the contour of the ink node.
            foreach (ContourSegment hitSegment in hitContour)
            {
                if (hitSegment.IsArc)
                {
                    // Adjust the arc for the node' pressure factor.
                    Vector hitCenter = hitSegment.Begin + hitSegment.Radius - position;
                    Vector hitRadius = hitSegment.Radius;
                    if (!DoubleUtil.AreClose(pressureFactor, 1d))
                    {
                        System.Diagnostics.Debug.Assert(DoubleUtil.IsZero(pressureFactor) == false);
                        hitCenter /= pressureFactor;
                        hitRadius /= pressureFactor;
                    }
                    // If the segment is an arc, hit-test against the entire circle the arc is part of.
                    if (true == HitTestPolygonCircle(_vertices, hitCenter, hitRadius))
                    {
                        isHit = true;
                        break;
                    }
                    //
                    if (isInside && (WhereIsVectorAboutArc(
                        position - hitSegment.Begin - hitSegment.Radius,
                        -hitSegment.Radius, hitSegment.Vector - hitSegment.Radius) == HitResult.Hit))
                    {
                        isInside = false;
                    }
                }
                else
                {
                    // Adjust the segment for the node's pressure factor
                    Vector hitBegin = hitSegment.Begin - position;
                    Vector hitEnd = hitBegin + hitSegment.Vector;
                    if (!DoubleUtil.AreClose(pressureFactor, 1d))
                    {
                        System.Diagnostics.Debug.Assert(DoubleUtil.IsZero(pressureFactor) == false);
                        hitBegin /= pressureFactor;
                        hitEnd /= pressureFactor;
                    }
                    // Hit-test the node against the segment
                    if (true == HitTestPolygonSegment(_vertices, hitBegin, hitEnd))
                    {
                        isHit = true;
                        break;
                    }
                    //
                    if (isInside && WhereIsVectorAboutVector(
                        position - hitSegment.Begin, hitSegment.Vector) != HitResult.Right)
                    {
                        isInside = false;
                    }
                }
            }
            return (isInside || isHit);
        }   
Пример #22
0
        /// <summary>
        /// Helper function to HitTest the the hitting contour against the inking contour
        /// </summary>
        /// <param name="hitContour">a collection of basic segments outlining the hitting contour</param>
        /// <param name="quad">A connecting quad</param>
        /// <param name="beginNode">Begin node of the stroke segment to hit-test. Can be empty (none)</param>
        /// <param name="endNode">End node of the stroke segment</param>
        /// <returns>true if hit; false otherwise</returns>
        private bool HitTestInkContour(
            IEnumerable<ContourSegment> hitContour, Quad quad, StrokeNodeData beginNode, StrokeNodeData endNode)
        {
            System.Diagnostics.Debug.Assert(!quad.IsEmpty);
            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 to
            // '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;

            // The ink connecting quad is not empty, enumerate through the segments of the 
            // hitting contour and hit-test them one by one against the ink contour.
            foreach (ContourSegment hitSegment in hitContour)
            {
                // Iterate through the vertices of the contour of the ink segment
                // check where the hit segment is about them, return false if it's 
                // on the left side off either of the ink contour segments.

                Vector hitBegin, hitEnd;
                HitResult hitResult;

                // Start with the segment quad.C->quad.D
                if (hitSegment.IsArc)
                {
                    hitBegin = hitSegment.Begin + hitSegment.Radius - beginNode.Position;
                    hitEnd = hitSegment.Radius;
                    hitResult = WhereIsCircleAboutSegment(
                        hitBegin, hitEnd, quad.C - beginNode.Position, quad.D - beginNode.Position);
                }
                else
                {
                    hitBegin = hitSegment.Begin - beginNode.Position;
                    hitEnd = hitBegin + hitSegment.Vector;
                    hitResult = WhereIsSegmentAboutSegment(
                        hitBegin, hitEnd, quad.C - beginNode.Position, quad.D - beginNode.Position);
                }
                if (HitResult.Left == hitResult)
                {
                    if (isInside)
                    {
                        isInside = hitSegment.IsArc
                            ? (WhereIsVectorAboutArc(-hitBegin, -hitSegment.Radius, hitSegment.Vector - hitSegment.Radius) != HitResult.Hit)
                            : (WhereIsVectorAboutVector(-hitBegin, hitSegment.Vector) == HitResult.Right);
                    }
                    // This hitSegment is completely outside of the ink contour, 
                    // continue with the next one.
                    continue;
                }

                // Continue clockwise from quad.D to quad.A, then to quad.B, ..., quad.C

                HitResult firstResult = hitResult, lastResult = hitResult;
                double pressureFactor = beginNode.PressureFactor;

                // Find the index of the vertex that is quad.D 
                // Use count var to avoid infinite loop, normally this shouldn't 
                // happen but it doesn't hurt to check it just in case.
                int i = 0, count = _vertices.Length;
                Vector vertex = new Vector();
                for (i = 0; i < count; i++)
                {
                    vertex = _vertices[i] * pressureFactor;
                    if (DoubleUtil.AreClose((beginNode.Position + vertex), quad.D))
                    {
                        break;
                    }
                }
                System.Diagnostics.Debug.Assert(i < count);

                int k;
                for (k = 0; k < 2; k++)
                {
                    count = _vertices.Length;
                    Point nodePosition = (k == 0) ? beginNode.Position : endNode.Position;
                    Point end = (k == 0) ? quad.A : quad.C;

                    // Iterate over the vertices on 
                    //          beginNode(k=0)from quad.D to quad.A 
                    //    or 
                    //          endNode(k=1)from quad.A to quad.B ... to quad.C
                    while (((nodePosition + vertex) != end) && (count != 0))
                    {
                        // Find out the next vertex
                        i = (i + 1) % _vertices.Length;
                        Vector nextVertex = _vertices[i] * pressureFactor;

                        // Hit-test the hitting segment against the current edge
                        hitResult = hitSegment.IsArc
                            ? WhereIsCircleAboutSegment(hitBegin, hitEnd, vertex, nextVertex)
                            : WhereIsSegmentAboutSegment(hitBegin, hitEnd, vertex, nextVertex);

                        if (HitResult.Hit == hitResult)
                        {
                            return true;  //Got a hit
                        }
                        if (true == IsOutside(hitResult, lastResult))
                        {
                            // This hitSegment is definitely outside the ink contour, drop it.
                            // Change k to something > 2 to leave the for loop and skip 
                            // IsOutside at the bottom 
                            k = 3;
                            break;
                        }
                        lastResult = hitResult;
                        vertex = nextVertex;
                        count--;
                    }
                    System.Diagnostics.Debug.Assert(count > 0);

                    if (k == 0)
                    {
                        // Make some adjustments for the second one to continue iterating through 
                        // quad.AB and the outer segments of endNode up to quad.C
                        pressureFactor = endNode.PressureFactor;
                        Vector spineVector = endNode.Position - beginNode.Position;
                        vertex -= spineVector; // now vertex = quad.A - spineVector
                        hitBegin -= spineVector; // adjust hitBegin to the space of endNode
                        if (hitSegment.IsArc == false)
                        {
                            hitEnd -= spineVector;
                        }

                        // Find the index of the vertex that is quad.B
                        count = _vertices.Length;
                        while (!DoubleUtil.AreClose((endNode.Position + _vertices[i] * pressureFactor), quad.B) && (count != 0))
                        {
                            i = (i + 1) % _vertices.Length;
                            count--;
                        }
                        System.Diagnostics.Debug.Assert(count > 0);
                        i--;
                    }
                }
                if ((k == 2) && (false == IsOutside(firstResult, hitResult)))
                {
                    isHit = true;
                    break;
                }
                //
                if (isInside)
                {
                    isInside = hitSegment.IsArc
                        ? (WhereIsVectorAboutArc(-hitBegin, -hitSegment.Radius, hitSegment.Vector - hitSegment.Radius) != HitResult.Hit)
                        : (WhereIsVectorAboutVector(-hitBegin, hitSegment.Vector) == HitResult.Right);
                }
            }
            return (isHit||isInside);
        }
        /// <summary>
        ///
        /// </summary>
        /// <param name="node"></param>
        /// <param name="quad"></param>
        /// <returns></returns>
        internal override IEnumerable<ContourSegment> 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);
            }
        }
Пример #24
0
        /// <summary>
        /// Helper function to Hit-test against the two stroke nodes only (excluding the connecting quad). 
        /// </summary>
        /// <param name="hitSegment"></param>
        /// <param name="beginNode"></param>
        /// <param name="endNode"></param>
        /// <param name="result"></param>
        /// <returns></returns>
        private bool HitTestStrokeNodes(
            ContourSegment hitSegment, StrokeNodeData beginNode, StrokeNodeData endNode, ref StrokeFIndices result)
        {
            // First, find out if hitSegment intersects with either of the ink nodes
            bool isHit = false;
            for (int node = 0; node < 2; node++)
            {
                Point position;
                double pressureFactor;
                if (node == 0)
                {
                    if (isHit && DoubleUtil.AreClose(result.BeginFIndex, StrokeFIndices.BeforeFirst))
                    {
                        continue;
                    }
                    position = beginNode.Position;
                    pressureFactor = beginNode.PressureFactor;
                }
                else
                {
                    if (isHit && DoubleUtil.AreClose(result.EndFIndex, StrokeFIndices.AfterLast))
                    {
                        continue;
                    }
                    position = endNode.Position;
                    pressureFactor = endNode.PressureFactor;
                }

                Vector hitBegin, hitEnd;

                // Adjust the segment for the node's pressure factor
                if (hitSegment.IsArc)
                {
                    hitBegin = hitSegment.Begin - position + hitSegment.Radius;
                    hitEnd = hitSegment.Radius;
                }
                else
                {
                    hitBegin = hitSegment.Begin - position;
                    hitEnd = hitBegin + hitSegment.Vector;
                }

                if (pressureFactor != 1)
                {
                    System.Diagnostics.Debug.Assert(DoubleUtil.IsZero(pressureFactor) == false);
                    hitBegin /= pressureFactor;
                    hitEnd /= pressureFactor;
                }
                // Hit-test the node against the segment
                if (hitSegment.IsArc
                    ? HitTestPolygonCircle(_vertices, hitBegin, hitEnd)
                    : HitTestPolygonSegment(_vertices, hitBegin, hitEnd))
                {
                    isHit = true;
                    if (node == 0)
                    {
                        result.BeginFIndex = StrokeFIndices.BeforeFirst;
                        if (DoubleUtil.AreClose(result.EndFIndex, StrokeFIndices.AfterLast))
                        {
                            break;
                        }
                    }
                    else
                    {
                        result.EndFIndex = StrokeFIndices.AfterLast;
                        if (beginNode.IsEmpty)
                        {
                            result.BeginFIndex = StrokeFIndices.BeforeFirst;
                            break;
                        }
                        if (DoubleUtil.AreClose(result.BeginFIndex, StrokeFIndices.BeforeFirst))
                        {
                            break;
                        }
                    }
                }
            }
            return isHit;
        }
        /// <summary>
        /// Hit-tests a stroke segment defined by two nodes against a linear segment.
        /// </summary>
        /// <param name="beginNode">Begin node of the stroke segment to hit-test. Can be empty (none)</param>
        /// <param name="endNode">End node of the stroke segment</param>
        /// <param name="quad">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</param>
        /// <param name="hitBeginPoint">an end point of the hitting linear segment</param>
        /// <param name="hitEndPoint">an end point of the hitting linear segment</param>
        /// <returns>true if the hitting segment intersect the contour comprised of the two stroke nodes</returns>
        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;
        }
Пример #26
0
        /// <summary>
        /// Returns an enumerator for edges of the contour comprised by a given node 
        /// and its connecting quadrangle.
        /// Used for hit-testing a stroke against an other stroke (stroke and point erasing)
        /// </summary>
        /// <param name="node">node</param>
        /// <param name="quad">quadrangle connecting the node to the preceeding node</param>
        /// <returns>contour segments enumerator</returns>
        internal virtual IEnumerable<ContourSegment> GetContourSegments(StrokeNodeData node, Quad quad)
        {
            System.Diagnostics.Debug.Assert(node.IsEmpty == false);

            if (quad.IsEmpty)
            {
                Point vertex = node.Position + (_vertices[_vertices.Length - 1] * node.PressureFactor);
                for (int i = 0; i < _vertices.Length; i++)
                {
                    Point nextVertex = node.Position + (_vertices[i] * node.PressureFactor);
                    yield return new ContourSegment(vertex, nextVertex);
                    vertex = nextVertex;
                }
            }
            else
            {
                yield return new ContourSegment(quad.A, quad.B);

                for (int i = 0, count = _vertices.Length; i < count; i++)
                {
                    Point vertex = node.Position + (_vertices[i] * node.PressureFactor);
                    if (vertex == quad.B)
                    {
                        for (int j = 0; (j < count) && (vertex != quad.C); j++)
                        {
                            i = (i + 1) % count;
                            Point nextVertex = node.Position + (_vertices[i] * node.PressureFactor);
                            yield return new ContourSegment(vertex, nextVertex);
                            vertex = nextVertex;
                        }
                        break;
                    }
                }

                yield return new ContourSegment(quad.C, quad.D);
                yield return new ContourSegment(quad.D, quad.A);
            }
        }
        /// <summary>
        /// Cut-test ink segment defined by two nodes and a connecting quad against a linear segment
        /// </summary>
        /// <param name="beginNode">Begin node of the ink segment</param>
        /// <param name="endNode">End node of the ink segment</param>
        /// <param name="quad">Pre-computed quadrangle connecting the two ink nodes</param>
        /// <param name="hitBeginPoint">egin point of the hitting segment</param>
        /// <param name="hitEndPoint">End point of the hitting segment</param>
        /// <returns>Exact location to cut at represented by StrokeFIndices</returns>
        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;
        }
Пример #28
0
 /// <summary>
 ///  Calculate the clip location
 /// </summary>
 /// <param name="hitSegment">the hitting segment</param>
 /// <param name="beginNode">begin node</param>
 /// <param name="spineVector"></param>
 /// <param name="pressureDelta"></param>
 /// <returns>the clip location. not-clip if return StrokeFIndices.BeforeFirst</returns>
 private double CalculateClipLocation(
     ContourSegment hitSegment, StrokeNodeData beginNode, Vector spineVector, double pressureDelta)
 {
     double findex = StrokeFIndices.BeforeFirst;
     bool clipIt = hitSegment.IsArc ? true
         //? (WhereIsVectorAboutArc(beginNode.Position - hitSegment.Begin - hitSegment.Radius,
         //            -hitSegment.Radius, hitSegment.Vector - hitSegment.Radius) == HitResult.Hit)
         : (WhereIsVectorAboutVector(
                            beginNode.Position - hitSegment.Begin, hitSegment.Vector) == HitResult.Left);
     if (clipIt)
     {
         findex = hitSegment.IsArc
             ? ClipTestArc(spineVector, pressureDelta,
                 (hitSegment.Begin + hitSegment.Radius - beginNode.Position) / beginNode.PressureFactor,
                 hitSegment.Radius / beginNode.PressureFactor)
             : ClipTest(spineVector, pressureDelta,
                 (hitSegment.Begin - beginNode.Position) / beginNode.PressureFactor,
                 (hitSegment.End - beginNode.Position) / beginNode.PressureFactor);
         
         // NTRAID#WINDOWS-1384646-2005/11/17-WAYNEZEN,
         // ClipTest returns StrokeFIndices.AfterLast to indicate a false hit test.
         // But the caller CutTest expects StrokeFIndices.BeforeFirst when there is no hit.
         if ( findex == StrokeFIndices.AfterLast )
         {
             findex = StrokeFIndices.BeforeFirst;
         }
         else
         {
             System.Diagnostics.Debug.Assert(findex >= 0 && findex <= 1);
         }
     }
     return findex;
 }
        /// <summary>
        /// Finds connecting points for a pair of stroke nodes
        /// </summary>
        /// <param name="beginNode">a node to connect</param>
        /// <param name="endNode">another node, next to beginNode</param>
        /// <returns>connecting quadrangle</returns>
        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));
        }
Пример #30
0
        /// <summary>
        /// Finds connecting points for a pair of stroke nodes (of a polygonal shape)
        /// </summary>
        /// <param name="beginNode">a node to connect</param>
        /// <param name="endNode">another node, next to beginNode</param>
        /// <returns>connecting quadrangle, that can be empty if one node is inside the other</returns>
        internal virtual Quad GetConnectingQuad(StrokeNodeData beginNode, StrokeNodeData endNode)
        {            
            // Return an empty quad if either of the nodes is empty (not a node) 
            // or if both nodes are at the same position.
            if (beginNode.IsEmpty || endNode.IsEmpty || DoubleUtil.AreClose(beginNode.Position, endNode.Position))
            {
                return Quad.Empty;
            }

            // By definition, Quad's vertices (A,B,C,D) are ordered clockwise with points A and D located
            // on the beginNode and B and C on the endNode. Basically, we're looking for segments AB and CD. 
            // We iterate through the vertices of the beginNode, at each vertex we analyze location of the
            // connecting segment relative to the node's edges at the vertex, and enforce these rules: 
            //  - if the vector of the connecting segment at a vertex V[i] is on the left from vector V[i]V[i+1] 
            //    and not on the left from vector V[i-1]V[i], then it's the AB segment of the quad (V[i] == A).
            //  - if the vector of the connecting segment at a vertex V[i] is on the left from vector V[i-1]V[i] 
            //    and not on the left from vector V[i]V[i+1], then it's the CD segment of the quad (V[i] == D).
            // 

            Quad quad = Quad.Empty;
            bool foundAB = false, foundCD = false;

            // There's no need to build shapes of the two nodes in order to find their connecting quad.
            // It's the spine vector between the nodes and their scaling diff (pressure delta) is all 
            // that matters here. 
            Vector spine = endNode.Position - beginNode.Position;
            double pressureDelta = endNode.PressureFactor - beginNode.PressureFactor;

            // Iterate through the vertices of the default shape
            int count = _vertices.Length;
            for (int i = 0, j = count - 1; i < count; i++, j = ((j + 1) % count))
            {
                // Compute vector of the connecting segment at the vertex [i]
                Vector connection = spine + _vertices[i] * pressureDelta;
                if ((pressureDelta != 0) && (connection.X == 0) && (connection.Y == 0))
                {
                    // One of the nodes,                       |----|
                    // as well as the connecting quad,         |__  |
                    // is entirely inside the other node.      |  | |
                    //                                 [i] --> |__|_|
                    return Quad.Empty;
                }

                // Find out where this vector is about the node edge [i][i+1]
                // (The vars names "goingTo" and "comingFrom" refer direction of the line defined 
                // by the connecting vector applied at vertex [i], relative to the contour of the node shape.
                // Using these terms, (comingFrom != Right && goingTo == Left) corresponds to the segment AB,
                // and (comingFrom == Right && goingTo != Left) describes the DC.
                HitResult goingTo = WhereIsVectorAboutVector(connection, _vertices[(i + 1) % count] - _vertices[i]);

                if (goingTo == HitResult.Left)
                {
                    if (false == foundAB)
                    {
                        // Find out where the node edge [i-1][i] is about the connecting vector 
                        HitResult comingFrom = WhereIsVectorAboutVector(_vertices[i] - _vertices[j], connection);
                        if (HitResult.Right != comingFrom)
                        {
                            foundAB = true;
                            quad.A = beginNode.Position + _vertices[i] * beginNode.PressureFactor;
                            quad.B = endNode.Position + _vertices[i] * endNode.PressureFactor;
                            if (true == foundCD)
                            {
                                // Found all 4 points. Break out from the 'for' loop.
                                break;
                            }
                        }
                    }
                }
                else
                {
                    if (false == foundCD)
                    {
                        // Find out where the node edge [i-1][i] is about the connecting vector 
                        HitResult comingFrom = WhereIsVectorAboutVector(_vertices[i] - _vertices[j], connection);
                        if (HitResult.Right == comingFrom)
                        {
                            foundCD = true;
                            quad.C = endNode.Position + _vertices[i] * endNode.PressureFactor;
                            quad.D = beginNode.Position + _vertices[i] * beginNode.PressureFactor;
                            if (true == foundAB)
                            {
                                // Found all 4 points. Break out from the 'for' loop.
                                break;
                            }
                        }
                    }
                }
            }
            
            if (!foundAB || !foundCD ||   // (2)
                ((pressureDelta != 0) && Vector.Determinant(quad.B - quad.A, quad.D - quad.A) == 0)) // (1)
            {   
                //                                          _____        _______
                // One of the nodes,                    (1) |__  |   (2) | ___  |
                // as well as the connecting quad,          |  | |       | |  | |
                // is entirely inside the other node.       |__| |       | |__| |
                //                                          |____|       |___ __|
                return Quad.Empty;
            }

            return quad;
        }