Code:
/ Net / Net / 3.5.50727.3053 / DEVDIV / depot / DevDiv / releases / Orcas / SP / wpf / src / Core / CSharp / MS / Internal / Ink / StrokeNode.cs / 1 / StrokeNode.cs
//#define DEBUG_RENDERING_FEEDBACK //------------------------------------------------------------------------ //// 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; namespace MS.Internal.Ink { #region StrokeNode ////// StrokeNode represents a single segment on a stroke spine. /// It's used in enumerating through basic geometries making a stroke contour. /// internal struct StrokeNode { #region Constructors ////// Constructor. /// /// StrokeNodeOperations object created for particular rendering /// Index of the node on the stroke spine /// StrokeNodeData for this node /// StrokeNodeData for the precedeng node /// Whether the current node is the last node 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; } #endregion #region Public API ////// Position of the node on the stroke spine. /// ///internal Point Position { get { return _thisNode.Position; } } /// /// Position of the previous StrokeNode /// ///internal Point PreviousPosition { get { return _lastNode.Position; } } /// /// PressureFactor of the node on the stroke spine. /// ///internal float PressureFactor { get { return _thisNode.PressureFactor; } } /// /// PressureFactor of the previous StrokeNode /// ///internal float PreviousPressureFactor { get { return _lastNode.PressureFactor; } } /// /// Tells whether the node shape (the stylus shape used in the rendering) /// is elliptical or polygonal. If the shape is an ellipse, GetContourPoints /// returns the control points for the quadratic Bezier that defines the ellipse. /// ///true if the shape is ellipse, false otherwise internal bool IsEllipse { get { return IsValid && _operations.IsNodeShapeEllipse; } } ////// Returns true if this is the last node in the enumerator /// internal bool IsLastNode { get { return _isLastNode; } } ////// Returns the bounds of the node shape w/o connecting quadrangle /// ///internal Rect GetBounds() { return IsValid ? _operations.GetNodeBounds(_thisNode) : Rect.Empty; } /// /// Returns the bounds of the node shape and connecting quadrangle /// ///internal Rect GetBoundsConnected() { return IsValid ? Rect.Union(_operations.GetNodeBounds(_thisNode), ConnectingQuad.Bounds) : Rect.Empty; } /// /// Returns the points that make up the stroke node shape (minus the connecting quad) /// internal void GetContourPoints(ListpointBuffer) { if (IsValid) { _operations.GetNodeContourPoints(_thisNode, pointBuffer); } } /// /// Returns the points that make up the stroke node shape (minus the connecting quad) /// internal void GetPreviousContourPoints(ListpointBuffer) { if (IsValid) { _operations.GetNodeContourPoints(_lastNode, pointBuffer); } } /// /// Returns the connecting quad /// internal Quad GetConnectingQuad() { if (IsValid) { return ConnectingQuad; } return Quad.Empty; } ////////// IsPointWithinRectOrEllipse ///// //internal bool IsPointWithinRectOrEllipse(Point point, double xRadiusOrHalfWidth, double yRadiusOrHalfHeight, Point center, bool isEllipse) //{ // if (isEllipse) // { // //determine what delta is required to move the rect to be // //centered at 0,0 // double xDelta = center.X + xRadiusOrHalfWidth; // double yDelta = center.Y + yRadiusOrHalfHeight; // //offset the point by that delta // point.X -= xDelta; // point.Y -= yDelta; // //formula for ellipse is x^2/a^2 + y^2/b^2 = 1 // double a = xRadiusOrHalfWidth; // double b = yRadiusOrHalfHeight; // double res = (((point.X * point.X) / (a * a)) + // ((point.Y * point.Y) / (b * b))); // if (res <= 1) // { // return true; // } // return false; // } // else // { // if (point.X >= (center.X - xRadiusOrHalfWidth) && // point.X <= (center.X + xRadiusOrHalfWidth) && // point.Y >= (center.Y - yRadiusOrHalfHeight) && // point.Y <= (center.Y + yRadiusOrHalfHeight)) // { // return true; // } // return false; // } //} ////// GetPointsAtStartOfSegment /// internal void GetPointsAtStartOfSegment(ListabPoints, List dcPoints #if DEBUG_RENDERING_FEEDBACK , DrawingContext debugDC, double feedbackSize, bool showFeedback #endif ) { if (IsValid) { Quad quad = ConnectingQuad; if (IsEllipse) { Rect startNodeBounds = _operations.GetNodeBounds(_lastNode); //add instructions to arc from D to A abPoints.Add(quad.D); abPoints.Add(StrokeRenderer.ArcToMarker); abPoints.Add(new Point(startNodeBounds.Width, startNodeBounds.Height)); abPoints.Add(quad.A); //simply start at D dcPoints.Add(quad.D); #if DEBUG_RENDERING_FEEDBACK if (showFeedback) { debugDC.DrawEllipse(null, new Pen(Brushes.Pink, feedbackSize / 2), _lastNode.Position, startNodeBounds.Width / 2, startNodeBounds.Height / 2); debugDC.DrawEllipse(Brushes.Red, null, quad.A, feedbackSize, feedbackSize); debugDC.DrawEllipse(Brushes.Blue, null, quad.D, feedbackSize, feedbackSize); } #endif } else { //we're interested in the A, D points as well as the //nodecountour points between them Rect endNodeRect = _operations.GetNodeBounds(_thisNode); #if DEBUG_RENDERING_FEEDBACK if (showFeedback) { debugDC.DrawRectangle(null, new Pen(Brushes.Pink, feedbackSize / 2), _operations.GetNodeBounds(_lastNode)); } #endif Vector[] vertices = _operations.GetVertices(); double pressureFactor = _lastNode.PressureFactor; int maxCount = vertices.Length * 2; int i = 0; bool dIsInEndNode = true; for (; i < maxCount; i++) { //look for the d point first Point point = _lastNode.Position + (vertices[i % vertices.Length] * pressureFactor); if (point == quad.D) { //ab always starts with the D position (only add if it's not in endNode's bounds) if (!endNodeRect.Contains(quad.D)) { dIsInEndNode = false; abPoints.Add(quad.D); dcPoints.Add(quad.D); } #if DEBUG_RENDERING_FEEDBACK if (showFeedback) { debugDC.DrawEllipse(Brushes.Blue, null, quad.D, feedbackSize, feedbackSize); } #endif break; } } if (i == maxCount) { Debug.Assert(false, "StrokeNodeOperations.GetPointsAtStartOfSegment failed to find the D position"); //we didn't find the d point, return return; } //now look for the A position //advance i i++; for (int j = 0; i < maxCount && j < vertices.Length; i++, j++) { //look for the A point now Point point = _lastNode.Position + (vertices[i % vertices.Length] * pressureFactor); //add everything in between to ab as long as it's not already in endNode's bounds if (!endNodeRect.Contains(point)) { abPoints.Add(point); #if DEBUG_RENDERING_FEEDBACK if (showFeedback) { debugDC.DrawEllipse(Brushes.Wheat, null, point, feedbackSize, feedbackSize); } #endif } if (dIsInEndNode) { Debug.Assert(!endNodeRect.Contains(point)); //add the first point after d, clockwise dIsInEndNode = false; dcPoints.Add(point); } if (point == quad.A) { #if DEBUG_RENDERING_FEEDBACK if (showFeedback) { debugDC.DrawEllipse(Brushes.Red, null, point, feedbackSize, feedbackSize); } #endif break; } } } } } /// /// GetPointsAtEndOfSegment /// internal void GetPointsAtEndOfSegment( ListabPoints, List dcPoints #if DEBUG_RENDERING_FEEDBACK , DrawingContext debugDC, double feedbackSize, bool showFeedback #endif ) { if (IsValid) { Quad quad = ConnectingQuad; if (IsEllipse) { Rect bounds = GetBounds(); //add instructions to arc from D to A abPoints.Add(quad.B); abPoints.Add(StrokeRenderer.ArcToMarker); abPoints.Add(new Point(bounds.Width, bounds.Height)); abPoints.Add(quad.C); //don't add to the dc points #if DEBUG_RENDERING_FEEDBACK if (showFeedback) { debugDC.DrawEllipse(null, new Pen(Brushes.Pink, feedbackSize / 2), _thisNode.Position, bounds.Width / 2, bounds.Height / 2); debugDC.DrawEllipse(Brushes.Green, null, quad.B, feedbackSize, feedbackSize); debugDC.DrawEllipse(Brushes.Yellow, null, quad.C, feedbackSize, feedbackSize); } #endif } else { #if DEBUG_RENDERING_FEEDBACK if (showFeedback) { debugDC.DrawRectangle(null, new Pen(Brushes.Pink, feedbackSize / 2), GetBounds()); } #endif //we're interested in the B, C points as well as the //nodecountour points between them double pressureFactor = _thisNode.PressureFactor; Vector[] vertices = _operations.GetVertices(); int maxCount = vertices.Length * 2; int i = 0; for (; i < maxCount; i++) { //look for the d point first Point point = _thisNode.Position + (vertices[i % vertices.Length] * pressureFactor); if (point == quad.B) { abPoints.Add(quad.B); #if DEBUG_RENDERING_FEEDBACK if (showFeedback) { debugDC.DrawEllipse(Brushes.Green, null, point, feedbackSize, feedbackSize); } #endif break; } } if (i == maxCount) { Debug.Assert(false, "StrokeNodeOperations.GetPointsAtEndOfSegment failed to find the B position"); //we didn't find the d point, return return; } //now look for the C position //advance i i++; for (int j = 0; i < maxCount && j < vertices.Length; i++, j++) { //look for the c point last Point point = _thisNode.Position + (vertices[i % vertices.Length] * pressureFactor); if (point == quad.C) { break; } //only add to ab if we didn't find C abPoints.Add(point); #if DEBUG_RENDERING_FEEDBACK if (showFeedback) { debugDC.DrawEllipse(Brushes.Wheat, null, quad.C, feedbackSize, feedbackSize); } #endif } //finally, add the D point dcPoints.Add(quad.C); #if DEBUG_RENDERING_FEEDBACK if (showFeedback) { debugDC.DrawEllipse(Brushes.Yellow, null, quad.C, feedbackSize, feedbackSize); } #endif } } } /// /// GetPointsAtMiddleSegment /// internal void GetPointsAtMiddleSegment( StrokeNode previous, double angleBetweenNodes, ListabPoints, List dcPoints, out bool missingIntersection #if DEBUG_RENDERING_FEEDBACK , DrawingContext debugDC, double feedbackSize, bool showFeedback #endif ) { missingIntersection = false; if (IsValid && previous.IsValid) { Quad quad1 = previous.ConnectingQuad; if (!quad1.IsEmpty) { Quad quad2 = ConnectingQuad; if (!quad2.IsEmpty) { if (IsEllipse) { Rect node1Bounds = _operations.GetNodeBounds(previous._lastNode); Rect node2Bounds = _operations.GetNodeBounds(_lastNode); Rect node3Bounds = _operations.GetNodeBounds(_thisNode); #if DEBUG_RENDERING_FEEDBACK if (showFeedback) { debugDC.DrawEllipse(null, new Pen(Brushes.Pink, feedbackSize / 2), _lastNode.Position, node2Bounds.Width / 2, node2Bounds.Height / 2); } #endif if (angleBetweenNodes == 0.0d || ((quad1.B == quad2.A) && (quad1.C == quad2.D))) { //quads connections are the same, just add them abPoints.Add(quad1.B); dcPoints.Add(quad1.C); #if DEBUG_RENDERING_FEEDBACK if (showFeedback) { debugDC.DrawEllipse(Brushes.Green, null, quad1.B, feedbackSize, feedbackSize); debugDC.DrawEllipse(Brushes.Yellow, null, quad1.C, feedbackSize, feedbackSize); } #endif } else if (angleBetweenNodes > 0.0) { //the stroke angled towards the AB side //this part is easy if (quad1.B == quad2.A) { abPoints.Add(quad1.B); #if DEBUG_RENDERING_FEEDBACK if (showFeedback) { debugDC.DrawEllipse(Brushes.Green, null, quad1.B, feedbackSize, feedbackSize); } #endif } else { Point intersection = GetIntersection(quad1.A, quad1.B, quad2.A, quad2.B); Rect union = Rect.Union(node1Bounds, node2Bounds); union.Inflate(1.0, 1.0); //make sure we're not off in space #if DEBUG_RENDERING_FEEDBACK if (showFeedback) { debugDC.DrawEllipse(Brushes.Green, null, quad1.B, feedbackSize * 1.5, feedbackSize * 1.5); debugDC.DrawEllipse(Brushes.Red, null, quad2.A, feedbackSize, feedbackSize); } #endif if (union.Contains(intersection)) { abPoints.Add(intersection); #if DEBUG_RENDERING_FEEDBACK if (showFeedback) { debugDC.DrawEllipse(Brushes.Orange, null, intersection, feedbackSize, feedbackSize); } #endif } else { //if we missed the intersection we'll need to close the stroke segment //this work is done in StrokeRenderer missingIntersection = true; return; //we're done. } } if (quad1.C == quad2.D) { dcPoints.Add(quad1.C); #if DEBUG_RENDERING_FEEDBACK if (showFeedback) { debugDC.DrawEllipse(Brushes.Yellow, null, quad1.C, feedbackSize, feedbackSize); } #endif } else { //add instructions to arc from quad1.C to quad2.D in reverse order (since we walk this array backwards to render) dcPoints.Add(quad1.C); dcPoints.Add(new Point(node2Bounds.Width, node2Bounds.Height)); dcPoints.Add(StrokeRenderer.ArcToMarker); dcPoints.Add(quad2.D); #if DEBUG_RENDERING_FEEDBACK if (showFeedback) { debugDC.DrawEllipse(Brushes.Yellow, null, quad1.C, feedbackSize, feedbackSize); debugDC.DrawEllipse(Brushes.Blue, null, quad2.D, feedbackSize, feedbackSize); } #endif } } else { //the stroke angled towards the CD side //this part is easy if (quad1.C == quad2.D) { dcPoints.Add(quad1.C); #if DEBUG_RENDERING_FEEDBACK if (showFeedback) { debugDC.DrawEllipse(Brushes.Yellow, null, quad1.C, feedbackSize, feedbackSize); } #endif } else { Point intersection = GetIntersection(quad1.D, quad1.C, quad2.D, quad2.C); Rect union = Rect.Union(node1Bounds, node2Bounds); union.Inflate(1.0, 1.0); //make sure we're not off in space #if DEBUG_RENDERING_FEEDBACK if (showFeedback) { debugDC.DrawEllipse(Brushes.Yellow, null, quad1.C, feedbackSize * 1.5, feedbackSize * 1.5); debugDC.DrawEllipse(Brushes.Blue, null, quad2.D, feedbackSize, feedbackSize); } #endif if (union.Contains(intersection)) { dcPoints.Add(intersection); #if DEBUG_RENDERING_FEEDBACK if (showFeedback) { debugDC.DrawEllipse(Brushes.Orange, null, intersection, feedbackSize, feedbackSize); } #endif } else { //if we missed the intersection we'll need to close the stroke segment //this work is done in StrokeRenderer missingIntersection = true; return; //we're done. } } if (quad1.B == quad2.A) { abPoints.Add(quad1.B); #if DEBUG_RENDERING_FEEDBACK if (showFeedback) { debugDC.DrawEllipse(Brushes.Green, null, quad1.B, feedbackSize, feedbackSize); } #endif } else { //we need to arc between quad1.B and quad2.A along node2 abPoints.Add(quad1.B); abPoints.Add(StrokeRenderer.ArcToMarker); abPoints.Add(new Point(node2Bounds.Width, node2Bounds.Height)); abPoints.Add(quad2.A); #if DEBUG_RENDERING_FEEDBACK if (showFeedback) { debugDC.DrawEllipse(Brushes.Green, null, quad1.B, feedbackSize, feedbackSize); debugDC.DrawEllipse(Brushes.Red, null, quad2.A, feedbackSize, feedbackSize); } #endif } } } else { //rectangle int indexA = -1; int indexB = -1; int indexC = -1; int indexD = -1; Vector[] vertices = _operations.GetVertices(); double pressureFactor = _lastNode.PressureFactor; for (int i = 0; i < vertices.Length; i++) { Point point = _lastNode.Position + (vertices[i % vertices.Length] * pressureFactor); if (point == quad2.A) { indexA = i; } if (point == quad1.B) { indexB = i; } if (point == quad1.C) { indexC = i; } if (point == quad2.D) { indexD = i; } } if (indexA == -1 || indexB == -1 || indexC == -1 || indexD == -1) { Debug.Assert(false, "Couldn't find all 4 indexes in StrokeNodeOperations.GetPointsAtMiddleSegment"); return; } #if DEBUG_RENDERING_FEEDBACK if (showFeedback) { debugDC.DrawRectangle(null, new Pen(Brushes.Pink, feedbackSize / 2), _operations.GetNodeBounds(_lastNode)); debugDC.DrawEllipse(Brushes.Red, null, quad2.A, feedbackSize, feedbackSize); debugDC.DrawEllipse(Brushes.Green, null, quad1.B, feedbackSize, feedbackSize); debugDC.DrawEllipse(Brushes.Yellow, null, quad1.C, feedbackSize, feedbackSize); debugDC.DrawEllipse(Brushes.Blue, null, quad2.D, feedbackSize, feedbackSize); } #endif Rect node3Rect = _operations.GetNodeBounds(_thisNode); //take care of a-b first if (indexA == indexB) { //quad connection is the same, just add it if (!node3Rect.Contains(quad1.B)) { abPoints.Add(quad1.B); } } else if ((indexA == 0 && indexB == 3) || ((indexA != 3 || indexB != 0) && (indexA > indexB))) { if (!node3Rect.Contains(quad1.B)) { abPoints.Add(quad1.B); } if (!node3Rect.Contains(quad2.A)) { abPoints.Add(quad2.A); } } else { Point intersection = GetIntersection(quad1.A, quad1.B, quad2.A, quad2.B); Rect node12 = Rect.Union(_operations.GetNodeBounds(previous._lastNode), _operations.GetNodeBounds(_lastNode)); node12.Inflate(1.0, 1.0); //make sure we're not off in space if (node12.Contains(intersection)) { abPoints.Add(intersection); #if DEBUG_RENDERING_FEEDBACK if (showFeedback) { debugDC.DrawEllipse(Brushes.Orange, null, intersection, feedbackSize, feedbackSize * 1.5); } #endif } else { //if we missed the intersection we'll need to close the stroke segment //this work is done in StrokeRenderer. missingIntersection = true; return; //we're done. } } // now take care of c-d. if (indexC == indexD) { //quad connection is the same, just add it if (!node3Rect.Contains(quad1.C)) { dcPoints.Add(quad1.C); } } else if ((indexC == 0 && indexD == 3) || ((indexC != 3 || indexD != 0) && (indexC > indexD))) { if (!node3Rect.Contains(quad1.C)) { dcPoints.Add(quad1.C); } if (!node3Rect.Contains(quad2.D)) { dcPoints.Add(quad2.D); } } else { Point intersection = GetIntersection(quad1.D, quad1.C, quad2.D, quad2.C); Rect node12 = Rect.Union(_operations.GetNodeBounds(previous._lastNode), _operations.GetNodeBounds(_lastNode)); node12.Inflate(1.0, 1.0); //make sure we're not off in space if (node12.Contains(intersection)) { dcPoints.Add(intersection); #if DEBUG_RENDERING_FEEDBACK if (showFeedback) { debugDC.DrawEllipse(Brushes.Orange, null, intersection, feedbackSize, feedbackSize * 1.5); } #endif } else { //if we missed the intersection we'll need to close the stroke segment //this work is done in StrokeRenderer. missingIntersection = true; return; //we're done. } } } } } } } /// /// Returns the intersection between two lines. This code assumes there is an intersection /// and should only be called if that assumption is valid /// ///internal static Point GetIntersection(Point line1Start, Point line1End, Point line2Start, Point line2End) { double a1 = line1End.Y - line1Start.Y; double b1 = line1Start.X - line1End.X; double c1 = (line1End.X * line1Start.Y) - (line1Start.X * line1End.Y); double a2 = line2End.Y - line2Start.Y; double b2 = line2Start.X - line2End.X; double c2 = (line2End.X * line2Start.Y) - (line2Start.X * line2End.Y); double d = (a1 * b2) - (a2 * b1); if (d != 0.0) { double x = ((b1 * c2) - (b2 * c1)) / d; double y = ((a2 * c1) - (a1 * c2)) / d; //capture the min and max points double line1XMin, line1XMax, line1YMin, line1YMax, line2XMin, line2XMax, line2YMin, line2YMax; if (line1Start.X < line1End.X) { line1XMin = Math.Floor(line1Start.X); line1XMax = Math.Ceiling(line1End.X); } else { line1XMin = Math.Floor(line1End.X); line1XMax = Math.Ceiling(line1Start.X); } if (line2Start.X < line2End.X) { line2XMin = Math.Floor(line2Start.X); line2XMax = Math.Ceiling(line2End.X); } else { line2XMin = Math.Floor(line2End.X); line2XMax = Math.Ceiling(line2Start.X); } if (line1Start.Y < line1End.Y) { line1YMin = Math.Floor(line1Start.Y); line1YMax = Math.Ceiling(line1End.Y); } else { line1YMin = Math.Floor(line1End.Y); line1YMax = Math.Ceiling(line1Start.Y); } if (line2Start.Y < line2End.Y) { line2YMin = Math.Floor(line2Start.Y); line2YMax = Math.Ceiling(line2End.Y); } else { line2YMin = Math.Floor(line2End.Y); line2YMax = Math.Ceiling(line2Start.Y); } // now see if we have an intersection between the lines // and not just the projection of the lines if ((line1XMin <= x && x <= line1XMax) && (line1YMin <= y && y <= line1YMax) && (line2XMin <= x && x <= line2XMax) && (line2YMin <= y && y <= line2YMax)) { return new Point(x, y); } } if ((long)line1End.X == (long)line2Start.X && (long)line1End.Y == (long)line2Start.Y) { return new Point(line1End.X, line1End.Y); } return new Point(Double.NaN, Double.NaN); } /// /// This method tells whether the contour of a given stroke node /// intersects with the contour of this node. The contours of both nodes /// include their connecting quadrangles. /// /// ///internal bool HitTest(StrokeNode hitNode) { if (!IsValid || !hitNode.IsValid) { return false; } IEnumerable hittingContour = hitNode.GetContourSegments(); return _operations.HitTest(_lastNode, _thisNode, ConnectingQuad, hittingContour); } /// /// Finds out if a given node intersects with this one, /// and returns findices of the intersection. /// /// ///internal StrokeFIndices CutTest(StrokeNode hitNode) { if ((IsValid == false) || (hitNode.IsValid == false)) { return StrokeFIndices.Empty; } IEnumerable hittingContour = hitNode.GetContourSegments(); // If the node contours intersect, the result is a pair of findices // this segment should be cut at to let the hitNode's contour through it. StrokeFIndices cutAt = _operations.CutTest(_lastNode, _thisNode, ConnectingQuad, hittingContour); return (_index == 0) ? cutAt : BindFIndices(cutAt); } /// /// Finds out if a given linear segment intersects with the contour of this node /// (including connecting quadrangle), and returns findices of the intersection. /// /// /// ///internal StrokeFIndices CutTest(Point begin, Point end) { if (IsValid == false) { return StrokeFIndices.Empty; } // If the node contours intersect, the result is a pair of findices // this segment should be cut at to let the hitNode's contour through it. StrokeFIndices cutAt = _operations.CutTest(_lastNode, _thisNode, ConnectingQuad, begin, end); System.Diagnostics.Debug.Assert(!double.IsNaN(cutAt.BeginFIndex) && !double.IsNaN(cutAt.EndFIndex)); // Bind the found findices to the node and return the result return BindFIndicesForLassoHitTest(cutAt); } #endregion #region Private helpers /// /// Binds a local fragment to this node by setting the integer part of the /// fragment findices equal to the index of the previous node /// /// ///private StrokeFIndices BindFIndices(StrokeFIndices fragment) { System.Diagnostics.Debug.Assert(IsValid && (_index >= 0)); if (fragment.IsEmpty == false) { // Adjust only findices which are on this segment of thew spine (i.e. between 0 and 1) if (!DoubleUtil.AreClose(fragment.BeginFIndex, StrokeFIndices.BeforeFirst)) { System.Diagnostics.Debug.Assert(fragment.BeginFIndex >= 0 && fragment.BeginFIndex <= 1); fragment.BeginFIndex += _index - 1; } if (!DoubleUtil.AreClose(fragment.EndFIndex, StrokeFIndices.AfterLast)) { System.Diagnostics.Debug.Assert(fragment.EndFIndex >= 0 && fragment.EndFIndex <= 1); fragment.EndFIndex += _index - 1; } } return fragment; } internal int Index { get { return _index; } } /// /// Bind the StrokeFIndices for lasso hit test results. /// /// ///private StrokeFIndices BindFIndicesForLassoHitTest(StrokeFIndices fragment) { System.Diagnostics.Debug.Assert(IsValid); if (!fragment.IsEmpty) { // Adjust BeginFIndex if (DoubleUtil.AreClose(fragment.BeginFIndex, StrokeFIndices.BeforeFirst)) { // set it to be the index of the previous node, indicating intersection start from previous node fragment.BeginFIndex = (_index == 0 ? StrokeFIndices.BeforeFirst:_index - 1); } else { // Adjust findices which are on this segment of the spine (i.e. between 0 and 1) System.Diagnostics.Debug.Assert(DoubleUtil.GreaterThanOrClose(fragment.BeginFIndex, 0f)); System.Diagnostics.Debug.Assert(DoubleUtil.LessThanOrClose(fragment.BeginFIndex, 1f)); // Adjust the value to consider index, say from 0.75 to 3.75 (for _index = 4) fragment.BeginFIndex += _index - 1; } //Adjust EndFIndex if (DoubleUtil.AreClose(fragment.EndFIndex, StrokeFIndices.AfterLast)) { // set it to be the index of the current node, indicating the intersection cover the end of the node fragment.EndFIndex = (_isLastNode ? StrokeFIndices.AfterLast:_index); } else { System.Diagnostics.Debug.Assert(DoubleUtil.GreaterThanOrClose(fragment.EndFIndex, 0f)); System.Diagnostics.Debug.Assert(DoubleUtil.LessThanOrClose(fragment.EndFIndex, 1f)); // Ajust the value to consider the index fragment.EndFIndex += _index - 1; } } return fragment; } /// /// Tells whether the StrokeNode instance is valid or not (created via the default ctor) /// internal bool IsValid { get { return _operations != null; } } ////// The quadrangle that connects this and the previous node. /// Can be empty if this node is the first one or if one of the nodes is /// completely inside the other. /// The type Quad is supposed to be internal even if we surface StrokeNode. /// External users of StrokeNode should use GetConnectionPoints instead. /// private Quad ConnectingQuad { get { System.Diagnostics.Debug.Assert(IsValid); if (_isQuadCached == false) { _connectingQuad = _operations.GetConnectingQuad(_lastNode, _thisNode); _isQuadCached = true; } return _connectingQuad; } } ////// Returns an enumerator for edges of the contour comprised by the node /// and connecting quadrangle (_lastNode is excluded) /// Used for hit-testing a stroke against an other stroke (stroke and point erasing) /// private IEnumerableGetContourSegments() { System.Diagnostics.Debug.Assert(IsValid); // Calls thru to the StrokeNodeOperations object if (IsEllipse) { // ISSUE-2004/06/15- temporary workaround to avoid hit-testing with ellipses return _operations.GetNonBezierContourSegments(_lastNode, _thisNode); } return _operations.GetContourSegments(_thisNode, ConnectingQuad); } /// /// Returns the spine point that corresponds to the given findex. /// /// A local findex between the previous index and this one (ex: between 2.0 and 3.0) ///Point on the spine internal Point GetPointAt(double findex) { System.Diagnostics.Debug.Assert(IsValid); if (_lastNode.IsEmpty) { System.Diagnostics.Debug.Assert(findex == 0); return _thisNode.Position; } System.Diagnostics.Debug.Assert((findex >= _index - 1) && (findex <= _index)); if (DoubleUtil.AreClose(findex, (double)_index)) { // // we're being asked for this exact point // if we don't return it here, our algorithm // below doesn't work // return _thisNode.Position; } // // get the spare change to the left of the decimal point // eg turn 2.75 into .75 // double floor = Math.Floor(findex); findex = findex - floor; double xDiff = (_thisNode.Position.X - _lastNode.Position.X) * findex; double yDiff = (_thisNode.Position.Y - _lastNode.Position.Y) * findex; // // return the previous point plus the delta's // return new Point( _lastNode.Position.X + xDiff, _lastNode.Position.Y + yDiff); } #endregion #region Fields // Internal objects created for particular rendering private StrokeNodeOperations _operations; // Node's index on the stroke spine private int _index; // This and the previous node data that used by the StrokeNodeOperations object to build // and/or hit-test the contour of the node/segment private StrokeNodeData _thisNode; private StrokeNodeData _lastNode; // Calculating of the connecting quadrangle is not a cheap operations, therefore, // first, it's computed only by request, and second, once computed it's cached in the StrokeNode private bool _isQuadCached; private Quad _connectingQuad; // Is the current stroke node the last node? private bool _isLastNode; #endregion } #endregion } // File provided for Reference Use Only by Microsoft Corporation (c) 2007. //#define DEBUG_RENDERING_FEEDBACK //------------------------------------------------------------------------ //// 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; namespace MS.Internal.Ink { #region StrokeNode ////// StrokeNode represents a single segment on a stroke spine. /// It's used in enumerating through basic geometries making a stroke contour. /// internal struct StrokeNode { #region Constructors ////// Constructor. /// /// StrokeNodeOperations object created for particular rendering /// Index of the node on the stroke spine /// StrokeNodeData for this node /// StrokeNodeData for the precedeng node /// Whether the current node is the last node 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; } #endregion #region Public API ////// Position of the node on the stroke spine. /// ///internal Point Position { get { return _thisNode.Position; } } /// /// Position of the previous StrokeNode /// ///internal Point PreviousPosition { get { return _lastNode.Position; } } /// /// PressureFactor of the node on the stroke spine. /// ///internal float PressureFactor { get { return _thisNode.PressureFactor; } } /// /// PressureFactor of the previous StrokeNode /// ///internal float PreviousPressureFactor { get { return _lastNode.PressureFactor; } } /// /// Tells whether the node shape (the stylus shape used in the rendering) /// is elliptical or polygonal. If the shape is an ellipse, GetContourPoints /// returns the control points for the quadratic Bezier that defines the ellipse. /// ///true if the shape is ellipse, false otherwise internal bool IsEllipse { get { return IsValid && _operations.IsNodeShapeEllipse; } } ////// Returns true if this is the last node in the enumerator /// internal bool IsLastNode { get { return _isLastNode; } } ////// Returns the bounds of the node shape w/o connecting quadrangle /// ///internal Rect GetBounds() { return IsValid ? _operations.GetNodeBounds(_thisNode) : Rect.Empty; } /// /// Returns the bounds of the node shape and connecting quadrangle /// ///internal Rect GetBoundsConnected() { return IsValid ? Rect.Union(_operations.GetNodeBounds(_thisNode), ConnectingQuad.Bounds) : Rect.Empty; } /// /// Returns the points that make up the stroke node shape (minus the connecting quad) /// internal void GetContourPoints(ListpointBuffer) { if (IsValid) { _operations.GetNodeContourPoints(_thisNode, pointBuffer); } } /// /// Returns the points that make up the stroke node shape (minus the connecting quad) /// internal void GetPreviousContourPoints(ListpointBuffer) { if (IsValid) { _operations.GetNodeContourPoints(_lastNode, pointBuffer); } } /// /// Returns the connecting quad /// internal Quad GetConnectingQuad() { if (IsValid) { return ConnectingQuad; } return Quad.Empty; } ////////// IsPointWithinRectOrEllipse ///// //internal bool IsPointWithinRectOrEllipse(Point point, double xRadiusOrHalfWidth, double yRadiusOrHalfHeight, Point center, bool isEllipse) //{ // if (isEllipse) // { // //determine what delta is required to move the rect to be // //centered at 0,0 // double xDelta = center.X + xRadiusOrHalfWidth; // double yDelta = center.Y + yRadiusOrHalfHeight; // //offset the point by that delta // point.X -= xDelta; // point.Y -= yDelta; // //formula for ellipse is x^2/a^2 + y^2/b^2 = 1 // double a = xRadiusOrHalfWidth; // double b = yRadiusOrHalfHeight; // double res = (((point.X * point.X) / (a * a)) + // ((point.Y * point.Y) / (b * b))); // if (res <= 1) // { // return true; // } // return false; // } // else // { // if (point.X >= (center.X - xRadiusOrHalfWidth) && // point.X <= (center.X + xRadiusOrHalfWidth) && // point.Y >= (center.Y - yRadiusOrHalfHeight) && // point.Y <= (center.Y + yRadiusOrHalfHeight)) // { // return true; // } // return false; // } //} ////// GetPointsAtStartOfSegment /// internal void GetPointsAtStartOfSegment(ListabPoints, List dcPoints #if DEBUG_RENDERING_FEEDBACK , DrawingContext debugDC, double feedbackSize, bool showFeedback #endif ) { if (IsValid) { Quad quad = ConnectingQuad; if (IsEllipse) { Rect startNodeBounds = _operations.GetNodeBounds(_lastNode); //add instructions to arc from D to A abPoints.Add(quad.D); abPoints.Add(StrokeRenderer.ArcToMarker); abPoints.Add(new Point(startNodeBounds.Width, startNodeBounds.Height)); abPoints.Add(quad.A); //simply start at D dcPoints.Add(quad.D); #if DEBUG_RENDERING_FEEDBACK if (showFeedback) { debugDC.DrawEllipse(null, new Pen(Brushes.Pink, feedbackSize / 2), _lastNode.Position, startNodeBounds.Width / 2, startNodeBounds.Height / 2); debugDC.DrawEllipse(Brushes.Red, null, quad.A, feedbackSize, feedbackSize); debugDC.DrawEllipse(Brushes.Blue, null, quad.D, feedbackSize, feedbackSize); } #endif } else { //we're interested in the A, D points as well as the //nodecountour points between them Rect endNodeRect = _operations.GetNodeBounds(_thisNode); #if DEBUG_RENDERING_FEEDBACK if (showFeedback) { debugDC.DrawRectangle(null, new Pen(Brushes.Pink, feedbackSize / 2), _operations.GetNodeBounds(_lastNode)); } #endif Vector[] vertices = _operations.GetVertices(); double pressureFactor = _lastNode.PressureFactor; int maxCount = vertices.Length * 2; int i = 0; bool dIsInEndNode = true; for (; i < maxCount; i++) { //look for the d point first Point point = _lastNode.Position + (vertices[i % vertices.Length] * pressureFactor); if (point == quad.D) { //ab always starts with the D position (only add if it's not in endNode's bounds) if (!endNodeRect.Contains(quad.D)) { dIsInEndNode = false; abPoints.Add(quad.D); dcPoints.Add(quad.D); } #if DEBUG_RENDERING_FEEDBACK if (showFeedback) { debugDC.DrawEllipse(Brushes.Blue, null, quad.D, feedbackSize, feedbackSize); } #endif break; } } if (i == maxCount) { Debug.Assert(false, "StrokeNodeOperations.GetPointsAtStartOfSegment failed to find the D position"); //we didn't find the d point, return return; } //now look for the A position //advance i i++; for (int j = 0; i < maxCount && j < vertices.Length; i++, j++) { //look for the A point now Point point = _lastNode.Position + (vertices[i % vertices.Length] * pressureFactor); //add everything in between to ab as long as it's not already in endNode's bounds if (!endNodeRect.Contains(point)) { abPoints.Add(point); #if DEBUG_RENDERING_FEEDBACK if (showFeedback) { debugDC.DrawEllipse(Brushes.Wheat, null, point, feedbackSize, feedbackSize); } #endif } if (dIsInEndNode) { Debug.Assert(!endNodeRect.Contains(point)); //add the first point after d, clockwise dIsInEndNode = false; dcPoints.Add(point); } if (point == quad.A) { #if DEBUG_RENDERING_FEEDBACK if (showFeedback) { debugDC.DrawEllipse(Brushes.Red, null, point, feedbackSize, feedbackSize); } #endif break; } } } } } /// /// GetPointsAtEndOfSegment /// internal void GetPointsAtEndOfSegment( ListabPoints, List dcPoints #if DEBUG_RENDERING_FEEDBACK , DrawingContext debugDC, double feedbackSize, bool showFeedback #endif ) { if (IsValid) { Quad quad = ConnectingQuad; if (IsEllipse) { Rect bounds = GetBounds(); //add instructions to arc from D to A abPoints.Add(quad.B); abPoints.Add(StrokeRenderer.ArcToMarker); abPoints.Add(new Point(bounds.Width, bounds.Height)); abPoints.Add(quad.C); //don't add to the dc points #if DEBUG_RENDERING_FEEDBACK if (showFeedback) { debugDC.DrawEllipse(null, new Pen(Brushes.Pink, feedbackSize / 2), _thisNode.Position, bounds.Width / 2, bounds.Height / 2); debugDC.DrawEllipse(Brushes.Green, null, quad.B, feedbackSize, feedbackSize); debugDC.DrawEllipse(Brushes.Yellow, null, quad.C, feedbackSize, feedbackSize); } #endif } else { #if DEBUG_RENDERING_FEEDBACK if (showFeedback) { debugDC.DrawRectangle(null, new Pen(Brushes.Pink, feedbackSize / 2), GetBounds()); } #endif //we're interested in the B, C points as well as the //nodecountour points between them double pressureFactor = _thisNode.PressureFactor; Vector[] vertices = _operations.GetVertices(); int maxCount = vertices.Length * 2; int i = 0; for (; i < maxCount; i++) { //look for the d point first Point point = _thisNode.Position + (vertices[i % vertices.Length] * pressureFactor); if (point == quad.B) { abPoints.Add(quad.B); #if DEBUG_RENDERING_FEEDBACK if (showFeedback) { debugDC.DrawEllipse(Brushes.Green, null, point, feedbackSize, feedbackSize); } #endif break; } } if (i == maxCount) { Debug.Assert(false, "StrokeNodeOperations.GetPointsAtEndOfSegment failed to find the B position"); //we didn't find the d point, return return; } //now look for the C position //advance i i++; for (int j = 0; i < maxCount && j < vertices.Length; i++, j++) { //look for the c point last Point point = _thisNode.Position + (vertices[i % vertices.Length] * pressureFactor); if (point == quad.C) { break; } //only add to ab if we didn't find C abPoints.Add(point); #if DEBUG_RENDERING_FEEDBACK if (showFeedback) { debugDC.DrawEllipse(Brushes.Wheat, null, quad.C, feedbackSize, feedbackSize); } #endif } //finally, add the D point dcPoints.Add(quad.C); #if DEBUG_RENDERING_FEEDBACK if (showFeedback) { debugDC.DrawEllipse(Brushes.Yellow, null, quad.C, feedbackSize, feedbackSize); } #endif } } } /// /// GetPointsAtMiddleSegment /// internal void GetPointsAtMiddleSegment( StrokeNode previous, double angleBetweenNodes, ListabPoints, List dcPoints, out bool missingIntersection #if DEBUG_RENDERING_FEEDBACK , DrawingContext debugDC, double feedbackSize, bool showFeedback #endif ) { missingIntersection = false; if (IsValid && previous.IsValid) { Quad quad1 = previous.ConnectingQuad; if (!quad1.IsEmpty) { Quad quad2 = ConnectingQuad; if (!quad2.IsEmpty) { if (IsEllipse) { Rect node1Bounds = _operations.GetNodeBounds(previous._lastNode); Rect node2Bounds = _operations.GetNodeBounds(_lastNode); Rect node3Bounds = _operations.GetNodeBounds(_thisNode); #if DEBUG_RENDERING_FEEDBACK if (showFeedback) { debugDC.DrawEllipse(null, new Pen(Brushes.Pink, feedbackSize / 2), _lastNode.Position, node2Bounds.Width / 2, node2Bounds.Height / 2); } #endif if (angleBetweenNodes == 0.0d || ((quad1.B == quad2.A) && (quad1.C == quad2.D))) { //quads connections are the same, just add them abPoints.Add(quad1.B); dcPoints.Add(quad1.C); #if DEBUG_RENDERING_FEEDBACK if (showFeedback) { debugDC.DrawEllipse(Brushes.Green, null, quad1.B, feedbackSize, feedbackSize); debugDC.DrawEllipse(Brushes.Yellow, null, quad1.C, feedbackSize, feedbackSize); } #endif } else if (angleBetweenNodes > 0.0) { //the stroke angled towards the AB side //this part is easy if (quad1.B == quad2.A) { abPoints.Add(quad1.B); #if DEBUG_RENDERING_FEEDBACK if (showFeedback) { debugDC.DrawEllipse(Brushes.Green, null, quad1.B, feedbackSize, feedbackSize); } #endif } else { Point intersection = GetIntersection(quad1.A, quad1.B, quad2.A, quad2.B); Rect union = Rect.Union(node1Bounds, node2Bounds); union.Inflate(1.0, 1.0); //make sure we're not off in space #if DEBUG_RENDERING_FEEDBACK if (showFeedback) { debugDC.DrawEllipse(Brushes.Green, null, quad1.B, feedbackSize * 1.5, feedbackSize * 1.5); debugDC.DrawEllipse(Brushes.Red, null, quad2.A, feedbackSize, feedbackSize); } #endif if (union.Contains(intersection)) { abPoints.Add(intersection); #if DEBUG_RENDERING_FEEDBACK if (showFeedback) { debugDC.DrawEllipse(Brushes.Orange, null, intersection, feedbackSize, feedbackSize); } #endif } else { //if we missed the intersection we'll need to close the stroke segment //this work is done in StrokeRenderer missingIntersection = true; return; //we're done. } } if (quad1.C == quad2.D) { dcPoints.Add(quad1.C); #if DEBUG_RENDERING_FEEDBACK if (showFeedback) { debugDC.DrawEllipse(Brushes.Yellow, null, quad1.C, feedbackSize, feedbackSize); } #endif } else { //add instructions to arc from quad1.C to quad2.D in reverse order (since we walk this array backwards to render) dcPoints.Add(quad1.C); dcPoints.Add(new Point(node2Bounds.Width, node2Bounds.Height)); dcPoints.Add(StrokeRenderer.ArcToMarker); dcPoints.Add(quad2.D); #if DEBUG_RENDERING_FEEDBACK if (showFeedback) { debugDC.DrawEllipse(Brushes.Yellow, null, quad1.C, feedbackSize, feedbackSize); debugDC.DrawEllipse(Brushes.Blue, null, quad2.D, feedbackSize, feedbackSize); } #endif } } else { //the stroke angled towards the CD side //this part is easy if (quad1.C == quad2.D) { dcPoints.Add(quad1.C); #if DEBUG_RENDERING_FEEDBACK if (showFeedback) { debugDC.DrawEllipse(Brushes.Yellow, null, quad1.C, feedbackSize, feedbackSize); } #endif } else { Point intersection = GetIntersection(quad1.D, quad1.C, quad2.D, quad2.C); Rect union = Rect.Union(node1Bounds, node2Bounds); union.Inflate(1.0, 1.0); //make sure we're not off in space #if DEBUG_RENDERING_FEEDBACK if (showFeedback) { debugDC.DrawEllipse(Brushes.Yellow, null, quad1.C, feedbackSize * 1.5, feedbackSize * 1.5); debugDC.DrawEllipse(Brushes.Blue, null, quad2.D, feedbackSize, feedbackSize); } #endif if (union.Contains(intersection)) { dcPoints.Add(intersection); #if DEBUG_RENDERING_FEEDBACK if (showFeedback) { debugDC.DrawEllipse(Brushes.Orange, null, intersection, feedbackSize, feedbackSize); } #endif } else { //if we missed the intersection we'll need to close the stroke segment //this work is done in StrokeRenderer missingIntersection = true; return; //we're done. } } if (quad1.B == quad2.A) { abPoints.Add(quad1.B); #if DEBUG_RENDERING_FEEDBACK if (showFeedback) { debugDC.DrawEllipse(Brushes.Green, null, quad1.B, feedbackSize, feedbackSize); } #endif } else { //we need to arc between quad1.B and quad2.A along node2 abPoints.Add(quad1.B); abPoints.Add(StrokeRenderer.ArcToMarker); abPoints.Add(new Point(node2Bounds.Width, node2Bounds.Height)); abPoints.Add(quad2.A); #if DEBUG_RENDERING_FEEDBACK if (showFeedback) { debugDC.DrawEllipse(Brushes.Green, null, quad1.B, feedbackSize, feedbackSize); debugDC.DrawEllipse(Brushes.Red, null, quad2.A, feedbackSize, feedbackSize); } #endif } } } else { //rectangle int indexA = -1; int indexB = -1; int indexC = -1; int indexD = -1; Vector[] vertices = _operations.GetVertices(); double pressureFactor = _lastNode.PressureFactor; for (int i = 0; i < vertices.Length; i++) { Point point = _lastNode.Position + (vertices[i % vertices.Length] * pressureFactor); if (point == quad2.A) { indexA = i; } if (point == quad1.B) { indexB = i; } if (point == quad1.C) { indexC = i; } if (point == quad2.D) { indexD = i; } } if (indexA == -1 || indexB == -1 || indexC == -1 || indexD == -1) { Debug.Assert(false, "Couldn't find all 4 indexes in StrokeNodeOperations.GetPointsAtMiddleSegment"); return; } #if DEBUG_RENDERING_FEEDBACK if (showFeedback) { debugDC.DrawRectangle(null, new Pen(Brushes.Pink, feedbackSize / 2), _operations.GetNodeBounds(_lastNode)); debugDC.DrawEllipse(Brushes.Red, null, quad2.A, feedbackSize, feedbackSize); debugDC.DrawEllipse(Brushes.Green, null, quad1.B, feedbackSize, feedbackSize); debugDC.DrawEllipse(Brushes.Yellow, null, quad1.C, feedbackSize, feedbackSize); debugDC.DrawEllipse(Brushes.Blue, null, quad2.D, feedbackSize, feedbackSize); } #endif Rect node3Rect = _operations.GetNodeBounds(_thisNode); //take care of a-b first if (indexA == indexB) { //quad connection is the same, just add it if (!node3Rect.Contains(quad1.B)) { abPoints.Add(quad1.B); } } else if ((indexA == 0 && indexB == 3) || ((indexA != 3 || indexB != 0) && (indexA > indexB))) { if (!node3Rect.Contains(quad1.B)) { abPoints.Add(quad1.B); } if (!node3Rect.Contains(quad2.A)) { abPoints.Add(quad2.A); } } else { Point intersection = GetIntersection(quad1.A, quad1.B, quad2.A, quad2.B); Rect node12 = Rect.Union(_operations.GetNodeBounds(previous._lastNode), _operations.GetNodeBounds(_lastNode)); node12.Inflate(1.0, 1.0); //make sure we're not off in space if (node12.Contains(intersection)) { abPoints.Add(intersection); #if DEBUG_RENDERING_FEEDBACK if (showFeedback) { debugDC.DrawEllipse(Brushes.Orange, null, intersection, feedbackSize, feedbackSize * 1.5); } #endif } else { //if we missed the intersection we'll need to close the stroke segment //this work is done in StrokeRenderer. missingIntersection = true; return; //we're done. } } // now take care of c-d. if (indexC == indexD) { //quad connection is the same, just add it if (!node3Rect.Contains(quad1.C)) { dcPoints.Add(quad1.C); } } else if ((indexC == 0 && indexD == 3) || ((indexC != 3 || indexD != 0) && (indexC > indexD))) { if (!node3Rect.Contains(quad1.C)) { dcPoints.Add(quad1.C); } if (!node3Rect.Contains(quad2.D)) { dcPoints.Add(quad2.D); } } else { Point intersection = GetIntersection(quad1.D, quad1.C, quad2.D, quad2.C); Rect node12 = Rect.Union(_operations.GetNodeBounds(previous._lastNode), _operations.GetNodeBounds(_lastNode)); node12.Inflate(1.0, 1.0); //make sure we're not off in space if (node12.Contains(intersection)) { dcPoints.Add(intersection); #if DEBUG_RENDERING_FEEDBACK if (showFeedback) { debugDC.DrawEllipse(Brushes.Orange, null, intersection, feedbackSize, feedbackSize * 1.5); } #endif } else { //if we missed the intersection we'll need to close the stroke segment //this work is done in StrokeRenderer. missingIntersection = true; return; //we're done. } } } } } } } /// /// Returns the intersection between two lines. This code assumes there is an intersection /// and should only be called if that assumption is valid /// ///internal static Point GetIntersection(Point line1Start, Point line1End, Point line2Start, Point line2End) { double a1 = line1End.Y - line1Start.Y; double b1 = line1Start.X - line1End.X; double c1 = (line1End.X * line1Start.Y) - (line1Start.X * line1End.Y); double a2 = line2End.Y - line2Start.Y; double b2 = line2Start.X - line2End.X; double c2 = (line2End.X * line2Start.Y) - (line2Start.X * line2End.Y); double d = (a1 * b2) - (a2 * b1); if (d != 0.0) { double x = ((b1 * c2) - (b2 * c1)) / d; double y = ((a2 * c1) - (a1 * c2)) / d; //capture the min and max points double line1XMin, line1XMax, line1YMin, line1YMax, line2XMin, line2XMax, line2YMin, line2YMax; if (line1Start.X < line1End.X) { line1XMin = Math.Floor(line1Start.X); line1XMax = Math.Ceiling(line1End.X); } else { line1XMin = Math.Floor(line1End.X); line1XMax = Math.Ceiling(line1Start.X); } if (line2Start.X < line2End.X) { line2XMin = Math.Floor(line2Start.X); line2XMax = Math.Ceiling(line2End.X); } else { line2XMin = Math.Floor(line2End.X); line2XMax = Math.Ceiling(line2Start.X); } if (line1Start.Y < line1End.Y) { line1YMin = Math.Floor(line1Start.Y); line1YMax = Math.Ceiling(line1End.Y); } else { line1YMin = Math.Floor(line1End.Y); line1YMax = Math.Ceiling(line1Start.Y); } if (line2Start.Y < line2End.Y) { line2YMin = Math.Floor(line2Start.Y); line2YMax = Math.Ceiling(line2End.Y); } else { line2YMin = Math.Floor(line2End.Y); line2YMax = Math.Ceiling(line2Start.Y); } // now see if we have an intersection between the lines // and not just the projection of the lines if ((line1XMin <= x && x <= line1XMax) && (line1YMin <= y && y <= line1YMax) && (line2XMin <= x && x <= line2XMax) && (line2YMin <= y && y <= line2YMax)) { return new Point(x, y); } } if ((long)line1End.X == (long)line2Start.X && (long)line1End.Y == (long)line2Start.Y) { return new Point(line1End.X, line1End.Y); } return new Point(Double.NaN, Double.NaN); } /// /// This method tells whether the contour of a given stroke node /// intersects with the contour of this node. The contours of both nodes /// include their connecting quadrangles. /// /// ///internal bool HitTest(StrokeNode hitNode) { if (!IsValid || !hitNode.IsValid) { return false; } IEnumerable hittingContour = hitNode.GetContourSegments(); return _operations.HitTest(_lastNode, _thisNode, ConnectingQuad, hittingContour); } /// /// Finds out if a given node intersects with this one, /// and returns findices of the intersection. /// /// ///internal StrokeFIndices CutTest(StrokeNode hitNode) { if ((IsValid == false) || (hitNode.IsValid == false)) { return StrokeFIndices.Empty; } IEnumerable hittingContour = hitNode.GetContourSegments(); // If the node contours intersect, the result is a pair of findices // this segment should be cut at to let the hitNode's contour through it. StrokeFIndices cutAt = _operations.CutTest(_lastNode, _thisNode, ConnectingQuad, hittingContour); return (_index == 0) ? cutAt : BindFIndices(cutAt); } /// /// Finds out if a given linear segment intersects with the contour of this node /// (including connecting quadrangle), and returns findices of the intersection. /// /// /// ///internal StrokeFIndices CutTest(Point begin, Point end) { if (IsValid == false) { return StrokeFIndices.Empty; } // If the node contours intersect, the result is a pair of findices // this segment should be cut at to let the hitNode's contour through it. StrokeFIndices cutAt = _operations.CutTest(_lastNode, _thisNode, ConnectingQuad, begin, end); System.Diagnostics.Debug.Assert(!double.IsNaN(cutAt.BeginFIndex) && !double.IsNaN(cutAt.EndFIndex)); // Bind the found findices to the node and return the result return BindFIndicesForLassoHitTest(cutAt); } #endregion #region Private helpers /// /// Binds a local fragment to this node by setting the integer part of the /// fragment findices equal to the index of the previous node /// /// ///private StrokeFIndices BindFIndices(StrokeFIndices fragment) { System.Diagnostics.Debug.Assert(IsValid && (_index >= 0)); if (fragment.IsEmpty == false) { // Adjust only findices which are on this segment of thew spine (i.e. between 0 and 1) if (!DoubleUtil.AreClose(fragment.BeginFIndex, StrokeFIndices.BeforeFirst)) { System.Diagnostics.Debug.Assert(fragment.BeginFIndex >= 0 && fragment.BeginFIndex <= 1); fragment.BeginFIndex += _index - 1; } if (!DoubleUtil.AreClose(fragment.EndFIndex, StrokeFIndices.AfterLast)) { System.Diagnostics.Debug.Assert(fragment.EndFIndex >= 0 && fragment.EndFIndex <= 1); fragment.EndFIndex += _index - 1; } } return fragment; } internal int Index { get { return _index; } } /// /// Bind the StrokeFIndices for lasso hit test results. /// /// ///private StrokeFIndices BindFIndicesForLassoHitTest(StrokeFIndices fragment) { System.Diagnostics.Debug.Assert(IsValid); if (!fragment.IsEmpty) { // Adjust BeginFIndex if (DoubleUtil.AreClose(fragment.BeginFIndex, StrokeFIndices.BeforeFirst)) { // set it to be the index of the previous node, indicating intersection start from previous node fragment.BeginFIndex = (_index == 0 ? StrokeFIndices.BeforeFirst:_index - 1); } else { // Adjust findices which are on this segment of the spine (i.e. between 0 and 1) System.Diagnostics.Debug.Assert(DoubleUtil.GreaterThanOrClose(fragment.BeginFIndex, 0f)); System.Diagnostics.Debug.Assert(DoubleUtil.LessThanOrClose(fragment.BeginFIndex, 1f)); // Adjust the value to consider index, say from 0.75 to 3.75 (for _index = 4) fragment.BeginFIndex += _index - 1; } //Adjust EndFIndex if (DoubleUtil.AreClose(fragment.EndFIndex, StrokeFIndices.AfterLast)) { // set it to be the index of the current node, indicating the intersection cover the end of the node fragment.EndFIndex = (_isLastNode ? StrokeFIndices.AfterLast:_index); } else { System.Diagnostics.Debug.Assert(DoubleUtil.GreaterThanOrClose(fragment.EndFIndex, 0f)); System.Diagnostics.Debug.Assert(DoubleUtil.LessThanOrClose(fragment.EndFIndex, 1f)); // Ajust the value to consider the index fragment.EndFIndex += _index - 1; } } return fragment; } /// /// Tells whether the StrokeNode instance is valid or not (created via the default ctor) /// internal bool IsValid { get { return _operations != null; } } ////// The quadrangle that connects this and the previous node. /// Can be empty if this node is the first one or if one of the nodes is /// completely inside the other. /// The type Quad is supposed to be internal even if we surface StrokeNode. /// External users of StrokeNode should use GetConnectionPoints instead. /// private Quad ConnectingQuad { get { System.Diagnostics.Debug.Assert(IsValid); if (_isQuadCached == false) { _connectingQuad = _operations.GetConnectingQuad(_lastNode, _thisNode); _isQuadCached = true; } return _connectingQuad; } } ////// Returns an enumerator for edges of the contour comprised by the node /// and connecting quadrangle (_lastNode is excluded) /// Used for hit-testing a stroke against an other stroke (stroke and point erasing) /// private IEnumerableGetContourSegments() { System.Diagnostics.Debug.Assert(IsValid); // Calls thru to the StrokeNodeOperations object if (IsEllipse) { // ISSUE-2004/06/15- temporary workaround to avoid hit-testing with ellipses return _operations.GetNonBezierContourSegments(_lastNode, _thisNode); } return _operations.GetContourSegments(_thisNode, ConnectingQuad); } /// /// Returns the spine point that corresponds to the given findex. /// /// A local findex between the previous index and this one (ex: between 2.0 and 3.0) ///Point on the spine internal Point GetPointAt(double findex) { System.Diagnostics.Debug.Assert(IsValid); if (_lastNode.IsEmpty) { System.Diagnostics.Debug.Assert(findex == 0); return _thisNode.Position; } System.Diagnostics.Debug.Assert((findex >= _index - 1) && (findex <= _index)); if (DoubleUtil.AreClose(findex, (double)_index)) { // // we're being asked for this exact point // if we don't return it here, our algorithm // below doesn't work // return _thisNode.Position; } // // get the spare change to the left of the decimal point // eg turn 2.75 into .75 // double floor = Math.Floor(findex); findex = findex - floor; double xDiff = (_thisNode.Position.X - _lastNode.Position.X) * findex; double yDiff = (_thisNode.Position.Y - _lastNode.Position.Y) * findex; // // return the previous point plus the delta's // return new Point( _lastNode.Position.X + xDiff, _lastNode.Position.Y + yDiff); } #endregion #region Fields // Internal objects created for particular rendering private StrokeNodeOperations _operations; // Node's index on the stroke spine private int _index; // This and the previous node data that used by the StrokeNodeOperations object to build // and/or hit-test the contour of the node/segment private StrokeNodeData _thisNode; private StrokeNodeData _lastNode; // Calculating of the connecting quadrangle is not a cheap operations, therefore, // first, it's computed only by request, and second, once computed it's cached in the StrokeNode private bool _isQuadCached; private Quad _connectingQuad; // Is the current stroke node the last node? private bool _isLastNode; #endregion } #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
- VerbConverter.cs
- COM2ExtendedUITypeEditor.cs
- HtmlTextArea.cs
- ButtonBaseAdapter.cs
- ITextView.cs
- ServiceDocumentFormatter.cs
- XslAstAnalyzer.cs
- ObjectSecurityT.cs
- BitmapEffectDrawingContextState.cs
- HandlerBase.cs
- DiscoveryClientElement.cs
- LinkDesigner.cs
- HtmlControl.cs
- ObjectQueryExecutionPlan.cs
- ActivityDesignerLayoutSerializers.cs
- TransformConverter.cs
- ObjectStateEntryDbDataRecord.cs
- TraceContextEventArgs.cs
- ListMarkerLine.cs
- Ticks.cs
- DataGridViewDataErrorEventArgs.cs
- ThemeDirectoryCompiler.cs
- IndentedTextWriter.cs
- SystemIPAddressInformation.cs
- BrowserCapabilitiesFactory.cs
- DbParameterHelper.cs
- precedingquery.cs
- LineServices.cs
- AuthStoreRoleProvider.cs
- Monitor.cs
- DataGridViewColumnConverter.cs
- ColumnResizeUndoUnit.cs
- InternalTypeHelper.cs
- BitmapSource.cs
- WriteFileContext.cs
- TextContainerChangeEventArgs.cs
- SemanticBasicElement.cs
- MsdtcWrapper.cs
- SoapObjectReader.cs
- WindowsFormsHostPropertyMap.cs
- PolyQuadraticBezierSegment.cs
- DefinitionUpdate.cs
- PtsContext.cs
- X509ClientCertificateCredentialsElement.cs
- FrameDimension.cs
- ValueSerializer.cs
- COM2ColorConverter.cs
- SystemNetworkInterface.cs
- FontCacheUtil.cs
- PeerToPeerException.cs
- TableChangeProcessor.cs
- ResXBuildProvider.cs
- ProxyDataContractResolver.cs
- ISO2022Encoding.cs
- CompModSwitches.cs
- Keywords.cs
- TakeOrSkipQueryOperator.cs
- AmbientValueAttribute.cs
- AnonymousIdentificationSection.cs
- embossbitmapeffect.cs
- HtmlTitle.cs
- CngUIPolicy.cs
- NavigationWindow.cs
- Literal.cs
- Rules.cs
- DataGridViewRowsAddedEventArgs.cs
- PrivilegeNotHeldException.cs
- StrongTypingException.cs
- XmlDataSource.cs
- PointUtil.cs
- BrowserCapabilitiesFactory.cs
- userdatakeys.cs
- ActivitiesCollection.cs
- ContextBase.cs
- AutomationProperty.cs
- ByteAnimationBase.cs
- SqlDependency.cs
- CalendarDay.cs
- FormatVersion.cs
- CollectionChangedEventManager.cs
- AnnotationResourceCollection.cs
- ListBoxAutomationPeer.cs
- ObjectSecurity.cs
- ResponseStream.cs
- UserNameSecurityTokenProvider.cs
- ShaderEffect.cs
- LeaseManager.cs
- UnsafeNativeMethodsCLR.cs
- CodeSnippetStatement.cs
- UnionCqlBlock.cs
- MouseGestureValueSerializer.cs
- TagMapInfo.cs
- TrackingParameters.cs
- MenuCommandService.cs
- DisposableCollectionWrapper.cs
- ProtectedConfigurationSection.cs
- CLSCompliantAttribute.cs
- HttpResponse.cs
- DecimalMinMaxAggregationOperator.cs
- Dispatcher.cs