Code:
/ DotNET / DotNET / 8.0 / untmp / WIN_WINDOWS / lh_tools_devdiv_wpf / Windows / wcp / Core / MS / Internal / Ink / StrokeNodeOperations.cs / 1 / StrokeNodeOperations.cs
//------------------------------------------------------------------------ //// Copyright (c) Microsoft Corporation. All rights reserved. // //----------------------------------------------------------------------- using System; using System.Collections.Generic; using System.Windows; using System.Windows.Ink; using System.Windows.Media; using System.Windows.Input; using System.Diagnostics; using MS.Utility; using MS.Internal; namespace MS.Internal.Ink { ////// The base operations class that implements polygonal node operations by default. /// internal partial class StrokeNodeOperations { #region Static API ////// /// /// ///internal static StrokeNodeOperations CreateInstance(StylusShape nodeShape) { if (nodeShape == null) { throw new ArgumentNullException("nodeShape"); } if (nodeShape.IsEllipse) { return new EllipticalNodeOperations(nodeShape); } return new StrokeNodeOperations(nodeShape); } #endregion #region API /// /// Constructor /// /// shape of the nodes internal StrokeNodeOperations(StylusShape nodeShape) { System.Diagnostics.Debug.Assert(nodeShape != null); _vertices = nodeShape.GetVerticesAsVectors(); } ////// This is probably not the best (design-wise) but the cheapest way to tell /// EllipticalNodeOperations from all other implementations of node operations. /// internal virtual bool IsNodeShapeEllipse { get { return false; } } ////// Computes the bounds of a node /// /// node to compute bounds of ///bounds of the node 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; } internal void GetNodeContourPoints(StrokeNodeData node, ListpointBuffer) { 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)); } } } /// /// 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) /// /// node /// quadrangle connecting the node to the preceeding node ///contour segments enumerator internal virtual IEnumerableGetContourSegments(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); } } /// /// internal virtual IEnumerable GetNonBezierContourSegments(StrokeNodeData beginNode, StrokeNodeData endNode) { Quad quad = beginNode.IsEmpty ? Quad.Empty : GetConnectingQuad(beginNode, endNode); return GetContourSegments(endNode, quad); } /// /// Finds connecting points for a pair of stroke nodes (of a polygonal shape) /// /// a node to connect /// another node, next to beginNode ///connecting quadrangle, that can be empty if one node is inside the other 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; } ////// Hit-tests ink segment defined by two nodes against a linear segment. /// /// Begin node of the ink segment /// End node of the ink segment /// Pre-computed quadrangle connecting the two ink nodes /// Begin point of the hitting segment /// End point of the hitting segment ///true if there's intersection, false otherwise 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)); } } ////// Hit-tests a stroke segment defined by two nodes against another stroke segment. /// /// Begin node of the stroke segment to hit-test. Can be empty (none) /// End node of the stroke segment /// Pre-computed quadrangle connecting the two nodes. /// Can be empty if the begion node is empty or when one node is entirely inside the other /// a collection of basic segments outlining the hitting contour ///true if the contours intersect or overlap internal virtual bool HitTest( StrokeNodeData beginNode, StrokeNodeData endNode, Quad quad, IEnumerablehitContour) { // 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) { // // Make a call to hit-test the biggest node the hitting contour. return HitTestPolygonContourSegments(hitContour, beginNode, endNode); } else { // // HitTest the the hitting contour against the inking contour return HitTestInkContour(hitContour, quad, beginNode, endNode); } } /// /// Hit-tests ink segment defined by two nodes against a linear segment. /// /// Begin node of the ink segment /// End node of the ink segment /// Pre-computed quadrangle connecting the two ink nodes /// Begin point of the hitting segment /// End point of the hitting segment ///Exact location to cut at represented by StrokeFIndices 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; } ////// CutTest /// /// Begin node of the stroke segment to hit-test. Can be empty (none) /// End node of the stroke segment /// Pre-computed quadrangle connecting the two nodes. /// Can be empty if the begion node is empty or when one node is entirely inside the other /// a collection of basic segments outlining the hitting contour ///internal virtual StrokeFIndices CutTest( StrokeNodeData beginNode, StrokeNodeData endNode, Quad quad, IEnumerable 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) { // // 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; // // 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; } /// /// Cutting ink with polygonal tip shapes with a linear segment /// /// Vector representing the starting and ending point for the inking /// segment /// Represents the difference in the node size for startNode and endNode. /// pressureDelta = (endNode.PressureFactor / beginNode.PressureFactor) - 1 /// Start point of the hitting segment /// End point of the hitting segment ///a double representing the point of clipping private double ClipTest(Vector spineVector, double pressureDelta, Vector hitBegin, Vector hitEnd) { // Let's represent the vertices for the startNode are N1, N2, ..., Ni and for the endNode, M1, M2, // ..., Mi. // When ink tip shape is a convex polygon, one may iterate in a constant direction // (for instance, clockwise) through the edges of the polygon P1 and hit test the cutting segment // against quadrangles NIMIMI+1NI+1 with MI on the left side off the vector NINI+1. // If the cutting segment intersects the quadrangle, on the intersected part of the segment, // one may then find point Q (the nearest to the line NINI+1) and point QP // (the point of the intersection of the segment NIMI and vector NI+1NI started at Q). // Next, // QP = NI + s * LengthOf(MI - NI) (1) // s = LengthOf(QP - NI ) / LengthOf(MI - NI). (2) // If the cutting segment intersects more than one quadrant, one may then use the smallest s // to find the split point: // S = P1 + s * LengthOf(P2 - P1) (3) double findex = StrokeFIndices.AfterLast; Vector hitVector = hitEnd - hitBegin; Vector lastVertex = _vertices[_vertices.Length - 1]; // Note the definition of pressureDelta = (endNode.PressureFactor / beginNode.PressureFactor) - 1 // So the equation below gives // nextNode = spineVector + (endNode.PressureFactor / beginNode.PressureFactor)*lastVertex - lastVertex // As a result, nextNode is a Vector pointing from lastVertex of the beginNode to the correspoinding "lastVertex" // of the endNode. Vector nextNode = spineVector + lastVertex * pressureDelta; bool testNextEdge = false; for (int k = 0, count = _vertices.Length; k < count || (k == count && testNextEdge); k++) { Vector vertex = _vertices[k % count]; Vector nextVertex = vertex - lastVertex; // Point from vertex in beginNode to the corresponding "vertex" in endNode Vector nextVertexNextNode = spineVector + (vertex * pressureDelta); // Find out a "nextNode" on the endNode (nextNode) that is on the left side off the vector // (lastVertex, vertex). if ((DoubleUtil.IsZero(nextNode.X) && DoubleUtil.IsZero(nextNode.Y)) || (!testNextEdge && (HitResult.Left != WhereIsVectorAboutVector(nextNode, nextVertex)))) { lastVertex = vertex; nextNode = nextVertexNextNode; continue; } // Now we need to do hit testing of the hitting segment against quarangle (NI, MI, MI+1, NI+1), // that is, (lastVertex, nextNode, nextVertexNextNode, vertex) testNextEdge = false; HitResult hit = HitResult.Left; int side = 0; for (int i = 0; i < 2; i++) { Vector hitPoint = ((0 == i) ? hitBegin : hitEnd) - lastVertex; hit = WhereIsVectorAboutVector(hitPoint, nextNode); if (hit == HitResult.Hit) { double r = (Math.Abs(nextNode.X) < Math.Abs(nextNode.Y)) //DoubleUtil.IsZero(nextNode.X) ? (hitPoint.Y / nextNode.Y) : (hitPoint.X / nextNode.X); if ((findex > r) && DoubleUtil.IsBetweenZeroAndOne(r)) { findex = r; } } else if (hit == HitResult.Right) { side++; if (HitResult.Left == WhereIsVectorAboutVector( hitPoint - nextVertex, nextVertexNextNode)) { double r = GetPositionBetweenLines(nextVertex, nextNode, hitPoint); if ((findex > r) && DoubleUtil.IsBetweenZeroAndOne(r)) { findex = r; } } else { testNextEdge = true; } } else { side--; } } // if (0 == side) { if (hit == HitResult.Hit) { // This segment is collinear with the edge connecting the nodes, // no need to hit-test the other edges. System.Diagnostics.Debug.Assert(true == DoubleUtil.IsBetweenZeroAndOne(findex)); break; } // The hitting segment intersects the line of the edge connecting // the nodes. Find the findex of the intersection point. double det = -Vector.Determinant(nextNode, hitVector); if (DoubleUtil.IsZero(det) == false) { double s = Vector.Determinant(hitVector, hitBegin - lastVertex) / det; if ((findex > s) && DoubleUtil.IsBetweenZeroAndOne(s)) { findex = s; } } } // lastVertex = vertex; nextNode = nextVertexNextNode; } return AdjustFIndex(findex); } ////// Clip-Testing a polygonal inking segment against an arc (circle) /// /// Vector representing the starting and ending point for the inking /// segment /// Represents the difference in the node size for startNode and endNode. /// pressureDelta = (endNode.PressureFactor / beginNode.PressureFactor) - 1 /// The center of the hitting circle /// The radius of the hitting circle ///a double representing the point of clipping private double ClipTestArc(Vector spineVector, double pressureDelta, Vector hitCenter, Vector hitRadius) { // throw new NotImplementedException(); /* double findex = StrokeFIndices.AfterLast; double radiusSquared = hitRadius.LengthSquared; Vector vertex, lastVertex = _vertices[_vertices.Length - 1]; Vector nextVertexNextNode, nextNode = spineVector + lastVertex * pressureDelta; bool testNextEdge = false; for (int k = 0, count = _vertices.Length; k < count || (k == count && testNextEdge); k++, lastVertex = vertex, nextNode = nextVertexNextNode) { vertex = _vertices[k % count]; Vector nextVertex = vertex - lastVertex; nextVertexNextNode = spineVector + (vertex * pressureDelta); if (DoubleUtil.IsZero(nextNode.X) && DoubleUtil.IsZero(nextNode.Y)) { continue; } bool testConnectingEdge = false; if (HitResult.Left == WhereIsVectorAboutVector(nextNode, nextVertex)) { testNextEdge = false; Vector normal = GetProjection(lastVertex - hitCenter, vertex - hitCenter); if (radiusSquared <= normal.LengthSquared) { if (WhereIsVectorAboutVector(hitCenter - lastVertex, nextVertex) == HitResult.Left) { Vector hitPoint = hitCenter + (normal * Math.Sqrt(radiusSquared / normal.LengthSquared)); if (HitResult.Right == WhereIsVectorAboutVector(hitPoint - vertex, nextVertexNextNode)) { testNextEdge = true; } else if (HitResult.Left == WhereIsVectorAboutVector(hitPoint - lastVertex, nextNode)) { testConnectingEdge = true; } else { // this is it findex = GetPositionBetweenLines(nextVertex, nextNode, hitPoint - lastVertex); System.Diagnostics.Debug.Assert(DoubleUtil.IsBetweenZeroAndOne(findex)); break; } } } else if (HitResult.Right == WhereIsVectorAboutVector(hitCenter + normal - lastVertex, nextNode)) { testNextEdge = true; } else { testConnectingEdge = true; } } else if (testNextEdge == true) { testNextEdge = false; testConnectingEdge = true; } if (testConnectingEdge) { // Find out the projection of hitCenter on nextNode Vector v = lastVertex - hitCenter; double findexNearest = GetProjectionFIndex(v, v + nextNode); if (findexNearest > 0) { Vector nearest = nextNode * findexNearest; double squaredDistanceFromNearestToHitPoint = radiusSquared - (nearest + v).LengthSquared; if (DoubleUtil.IsZero(squaredDistanceFromNearestToHitPoint) && (findexNearest <= 1)) { if (findexNearest < findex) { findex = findexNearest; } } else if ((squaredDistanceFromNearestToHitPoint > 0) && (nearest.LengthSquared >= squaredDistanceFromNearestToHitPoint)) { double hitPointFIndex = findexNearest - Math.Sqrt( squaredDistanceFromNearestToHitPoint / nextNode.LengthSquared); System.Diagnostics.Debug.Assert(DoubleUtil.GreaterThanOrClose(hitPointFIndex, 0)); if (hitPointFIndex < findex) { findex = hitPointFIndex; } } } } } return AdjustFIndex(findex); */ } ////// Internal access to __vertices /// ///internal Vector[] GetVertices() { return _vertices; } /// /// Helper function to hit-test the biggest node against hitting contour segments /// /// a collection of basic segments outlining the hitting contour /// Begin node of the stroke segment to hit-test. Can be empty (none) /// End node of the stroke segment ///true if hit; false otherwise private bool HitTestPolygonContourSegments( IEnumerablehitContour, 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); } /// /// Helper function to HitTest the the hitting contour against the inking contour /// /// a collection of basic segments outlining the hitting contour /// A connecting quad /// Begin node of the stroke segment to hit-test. Can be empty (none) /// End node of the stroke segment ///true if hit; false otherwise private bool HitTestInkContour( IEnumerablehitContour, 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); } /// /// Helper function to Hit-test against the two stroke nodes only (excluding the connecting quad). /// /// /// /// /// ///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; } /// /// Calculate the clip location /// /// the hitting segment /// begin node /// /// ///the clip location. not-clip if return StrokeFIndices.BeforeFirst 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); // if ( findex == StrokeFIndices.AfterLast ) { findex = StrokeFIndices.BeforeFirst; } else { System.Diagnostics.Debug.Assert(findex >= 0 && findex <= 1); } } return findex; } ////// Helper method used to determine if we came up with a bogus result during hit testing /// protected bool IsInvalidCutTestResult(StrokeFIndices result) { // // check for three invalid states // 1) BeforeFirst == AfterLast // 2) BeforeFirst, < 0 // 3) > 1, AfterLast // if (DoubleUtil.AreClose(result.BeginFIndex, result.EndFIndex) || DoubleUtil.AreClose(result.BeginFIndex, StrokeFIndices.BeforeFirst) && result.EndFIndex < 0.0f || result.BeginFIndex > 1.0f && DoubleUtil.AreClose(result.EndFIndex, StrokeFIndices.AfterLast)) { return true; } return false; } #endregion #region Instance data // Shape parameters private Rect _shapeBounds = Rect.Empty; protected Vector[] _vertices; #endregion } } // File provided for Reference Use Only by Microsoft Corporation (c) 2007.
Link Menu

This book is available now!
Buy at Amazon US or
Buy at Amazon UK
- ChangePassword.cs
- NaturalLanguageHyphenator.cs
- Slider.cs
- SqlDataReader.cs
- KeyEvent.cs
- Typography.cs
- DbCommandDefinition.cs
- EdmMember.cs
- StickyNoteContentControl.cs
- ApplicationInfo.cs
- OperationFormatStyle.cs
- WindowsListViewItemCheckBox.cs
- NumberSubstitution.cs
- Win32MouseDevice.cs
- ContextBase.cs
- EntityDataSourceReferenceGroup.cs
- EventRoute.cs
- OpenTypeLayout.cs
- ExpandCollapseProviderWrapper.cs
- TableLayoutColumnStyleCollection.cs
- EventLog.cs
- WebPartZone.cs
- PackageDigitalSignature.cs
- AnnotationObservableCollection.cs
- LinqDataSourceInsertEventArgs.cs
- Container.cs
- TextOutput.cs
- DataTemplate.cs
- InvalidMessageContractException.cs
- UTF7Encoding.cs
- tooltip.cs
- DeleteCardRequest.cs
- HtmlShimManager.cs
- PlacementWorkspace.cs
- SqlCaseSimplifier.cs
- CollectionBase.cs
- EndGetFileNameFromUserRequest.cs
- SqlEnums.cs
- SendKeys.cs
- NavigationProgressEventArgs.cs
- Component.cs
- TypeDependencyAttribute.cs
- EnvelopedSignatureTransform.cs
- ConfigurationStrings.cs
- DesignTimeVisibleAttribute.cs
- PriorityQueue.cs
- Currency.cs
- ParserHooks.cs
- IndexerNameAttribute.cs
- HandoffBehavior.cs
- PrintDialog.cs
- DataServiceCollectionOfT.cs
- DbConnectionPoolIdentity.cs
- TakeQueryOptionExpression.cs
- RuleEngine.cs
- EditCommandColumn.cs
- InternalSendMessage.cs
- EdmRelationshipRoleAttribute.cs
- QueryOpcode.cs
- ConsumerConnectionPointCollection.cs
- HMACSHA384.cs
- Enumerable.cs
- Size.cs
- HttpClientCertificate.cs
- FamilyMapCollection.cs
- X509PeerCertificateAuthenticationElement.cs
- SqlDataSourceSummaryPanel.cs
- UnionCodeGroup.cs
- ProviderMetadataCachedInformation.cs
- AttributeEmitter.cs
- JoinElimination.cs
- ApplicationDirectory.cs
- ModelItemKeyValuePair.cs
- EdmComplexTypeAttribute.cs
- GeneralTransform3DGroup.cs
- TextServicesProperty.cs
- TdsParserStaticMethods.cs
- MessageSmuggler.cs
- SystemIPGlobalStatistics.cs
- ApplicationHost.cs
- HMACMD5.cs
- ComponentEditorForm.cs
- TextBox.cs
- CommandValueSerializer.cs
- PropertyGrid.cs
- UserPreferenceChangedEventArgs.cs
- _CommandStream.cs
- Merger.cs
- ApplicationSecurityManager.cs
- TypeDescriptionProviderAttribute.cs
- DataGridRelationshipRow.cs
- VisualStyleElement.cs
- JapaneseCalendar.cs
- SecureStringHasher.cs
- WebConfigurationHost.cs
- DetailsViewUpdateEventArgs.cs
- ResourceReferenceExpression.cs
- ProfileServiceManager.cs
- WebPartConnectionsDisconnectVerb.cs
- ProgressBarHighlightConverter.cs