using System; using System.Diagnostics; /* Code here is partially used from NodeXL (https://nodexl.codeplex.com/) * * * * */ using Windows.UI.Xaml.Media; using GraphX.Measure; using Point = Windows.Foundation.Point; using Rect = Windows.Foundation.Rect; namespace GraphX { public static class GeometryHelper { /// /// Get Intersection point on a rectangular surface /// /// a1 is line1 start /// a2 is line1 end /// b1 is line2 start /// b2 is line2 end /// public static Measure.Vector? Intersects(Measure.Vector a1, Measure.Vector a2, Measure.Vector b1, Measure.Vector b2) { var b = a2 - a1; var d = b2 - b1; var bDotDPerp = b.X * d.Y - b.Y * d.X; // if b dot d == 0, it means the lines are parallel so have infinite intersection points if (bDotDPerp == 0) return null; var c = b1 - a1; var t = (c.X * d.Y - c.Y * d.X) / bDotDPerp; if (t < 0 || t > 1) { return null; } var u = (c.X * b.Y - c.Y * b.X) / bDotDPerp; if (u < 0 || u > 1) { return null; } return a1 + t * b; } /// /// Generate PathGeometry object with curved Path using supplied route points /// /// Route points /// /// /// public static PolyLineSegment GetCurveThroughPoints(Point[] points, Double tension, Double tolerance) { Debug.Assert(points != null); Debug.Assert(points.Length >= 2); Debug.Assert(tolerance > 0); var oPolyLineSegment = new PolyLineSegment(); if (points.Length == 2) { AddPointsToPolyLineSegment(oPolyLineSegment, points[0], points[0], points[1], points[1], tension, tolerance); } else { var iPoints = points.Length; for (var i = 0; i < iPoints; i++) { if (i == 0) { AddPointsToPolyLineSegment(oPolyLineSegment, points[0], points[0], points[1], points[2], tension, tolerance); } else if (i == iPoints - 2) { AddPointsToPolyLineSegment(oPolyLineSegment, points[i - 1], points[i], points[i + 1], points[i + 1], tension, tolerance); } else if (i != iPoints - 1) { AddPointsToPolyLineSegment(oPolyLineSegment, points[i - 1], points[i], points[i + 1], points[i + 2], tension, tolerance); } } oPolyLineSegment.Points.Insert(0, points[0]); } return oPolyLineSegment; } private static void AddPointsToPolyLineSegment(PolyLineSegment oPolyLineSegment, Point oPoint0, Point oPoint1, Point oPoint2, Point oPoint3, Double dTension, Double dTolerance) { Debug.Assert(oPolyLineSegment != null); Debug.Assert(dTolerance > 0); var iPoints = (Int32)((Math.Abs(oPoint1.X - oPoint2.X) + Math.Abs(oPoint1.Y - oPoint2.Y)) / dTolerance); var oPolyLineSegmentPoints = oPolyLineSegment.Points; if (iPoints <= 2) { oPolyLineSegmentPoints.Add(oPoint2); } else { var dSX1 = dTension * (oPoint2.X - oPoint0.X); var dSY1 = dTension * (oPoint2.Y - oPoint0.Y); var dSX2 = dTension * (oPoint3.X - oPoint1.X); var dSY2 = dTension * (oPoint3.Y - oPoint1.Y); var dAX = dSX1 + dSX2 + 2 * oPoint1.X - 2 * oPoint2.X; var dAY = dSY1 + dSY2 + 2 * oPoint1.Y - 2 * oPoint2.Y; var dBX = -2 * dSX1 - dSX2 - 3 * oPoint1.X + 3 * oPoint2.X; var dBY = -2 * dSY1 - dSY2 - 3 * oPoint1.Y + 3 * oPoint2.Y; var dCX = dSX1; var dCY = dSY1; var dDX = oPoint1.X; var dDY = oPoint1.Y; // Note that this starts at 1, not 0. for (var i = 1; i < iPoints; i++) { var t = (Double)i / (iPoints - 1); var oPoint = new Point( dAX * t * t * t + dBX * t * t + dCX * t + dDX, dAY * t * t * t + dBY * t * t + dCY * t + dDY ); oPolyLineSegmentPoints.Add(oPoint); } } } public static PathFigure GetPathFigureFromPathSegments(Point oStartPoint, Boolean bPathFigureIsFilled, bool freezeAll, params PathSegment[] aoPathSegments) { Debug.Assert(aoPathSegments != null); var oPathFigure = new PathFigure { StartPoint = oStartPoint, IsFilled = bPathFigureIsFilled }; var oSegments = oPathFigure.Segments; foreach (var oPathSegment in aoPathSegments) oSegments.Add(oPathSegment); return oPathFigure; } public static PathGeometry GetPathGeometryFromPathSegments(Point oStartPoint, Boolean bPathFigureIsFilled, params PathSegment[] aoPathSegments) { Debug.Assert(aoPathSegments != null); var oPathFigure = new PathFigure { StartPoint = oStartPoint, IsFilled = bPathFigureIsFilled }; var oSegments = oPathFigure.Segments; foreach (var oPathSegment in aoPathSegments) oSegments.Add(oPathSegment); var oPathGeometry = new PathGeometry(); oPathGeometry.Figures.Add(oPathFigure); // FreezeIfFreezable(oPathGeometry); return (oPathGeometry); } public static Point GetEdgeEndpoint(Point source, Rect sourceSize, Point target, VertexShape shape) { switch (shape) { case VertexShape.Circle: return GetEdgeEndpointOnCircle(source, Math.Max(sourceSize.Height, sourceSize.Width) * .5, target); case VertexShape.Diamond: return GetEdgeEndpointOnDiamond(source, sourceSize.Width * .5, target); case VertexShape.Triangle: return GetEdgeEndpointOnTriangle(source, sourceSize.Width * .5, target); default: return GetEdgeEndpointOnRectangle(source, sourceSize, target); } } public static Point GetEdgeEndpointOnCircle(Point oVertexALocation, Double dVertexARadius, Point oVertexBLocation) { Debug.Assert(dVertexARadius >= 0); var dEdgeAngle = MathHelper.GetAngleBetweenPointsRadians(oVertexALocation, oVertexBLocation); return new Point( oVertexALocation.X + (dVertexARadius * Math.Cos(dEdgeAngle)), oVertexALocation.Y - (dVertexARadius * Math.Sin(dEdgeAngle)) ); } public static Point GetEdgeEndpointOnTriangle(Point oVertexLocation, Double m_dHalfWidth, Point otherEndpoint) { // Instead of doing geometry calculations similar to what is done in // VertexDrawingHistory.GetEdgePointOnRectangle(), make use of that // method by making the triangle look like a rectangle. First, figure // out how to rotate the triangle about the vertex location so that the // side containing the endpoint is vertical and to the right of the // vertex location. var dEdgeAngle = MathHelper.GetAngleBetweenPointsRadians( oVertexLocation, otherEndpoint); var dEdgeAngleDegrees = MathHelper.RadiansToDegrees(dEdgeAngle); Double dAngleToRotateDegrees; if (dEdgeAngleDegrees >= -30.0 && dEdgeAngleDegrees < 90.0) { dAngleToRotateDegrees = 30.0; } else if (dEdgeAngleDegrees >= -150.0 && dEdgeAngleDegrees < -30.0) { dAngleToRotateDegrees = 270.0; } else { dAngleToRotateDegrees = 150.0; } // Now create a rotated rectangle that is centered on the vertex // location and that has the vertical, endpoint-containing triangle // side as the rectangle's right edge. var dWidth = 2.0 * m_dHalfWidth; var oRotatedRectangle = new Rect( oVertexLocation.X, oVertexLocation.Y - m_dHalfWidth, dWidth * MathHelper.Tangent30Degrees, dWidth ); var oMatrix = GetRotatedMatrix(oVertexLocation, dAngleToRotateDegrees); // Rotate the other vertex location. var oRotatedOtherVertexLocation = oMatrix.Transform(otherEndpoint); // GetEdgeEndpointOnRectangle will compute an endpoint on the // rectangle's right edge. var oRotatedEdgeEndpoint = GetEdgeEndpointOnRectangle(oVertexLocation, oRotatedRectangle, oRotatedOtherVertexLocation); // Now rotate the edge endpoint in the other direction. oMatrix = GetRotatedMatrix(oVertexLocation, -dAngleToRotateDegrees); return oMatrix.Transform(oRotatedEdgeEndpoint); } public static Point GetEdgeEndpointOnDiamond(Point oVertexLocation, Double m_dHalfWidth, Point otherEndpoint) { // A diamond is just a rotated square, so the // GetEdgePointOnRectangle() can be used if the // diamond and the other vertex location are first rotated 45 degrees // about the diamond's center. var dHalfSquareWidth = m_dHalfWidth / Math.Sqrt(2.0); var oRotatedDiamond = new Rect( oVertexLocation.X - dHalfSquareWidth, oVertexLocation.Y - dHalfSquareWidth, 2.0 * dHalfSquareWidth, 2.0 * dHalfSquareWidth ); var oMatrix = GetRotatedMatrix(oVertexLocation, 45); var oRotatedOtherVertexLocation = oMatrix.Transform(otherEndpoint); var oRotatedEdgeEndpoint = GetEdgeEndpointOnRectangle(oVertexLocation, oRotatedDiamond, oRotatedOtherVertexLocation); // Now rotate the computed edge endpoint in the other direction. oMatrix = GetRotatedMatrix(oVertexLocation, -45); return oMatrix.Transform(oRotatedEdgeEndpoint); // } public static Point GetEdgeEndpointOnRectangle(Point oVertexALocation, Rect oVertexARectangle, Point oVertexBLocation) { /* if (oVertexALocation == oVertexBLocation) return oVertexALocation; Double dVertexAX = oVertexALocation.X; Double dVertexAY = oVertexALocation.Y; Double dVertexBX = oVertexBLocation.X; Double dVertexBY = oVertexBLocation.Y; Double dHalfVertexARectangleWidth = oVertexARectangle.Width / 2.0; Double dHalfVertexARectangleHeight = oVertexARectangle.Height / 2.0; // Get the angle between vertex A and vertex B. Double dEdgeAngle = MathHelper.GetAngleBetweenPointsRadians( oVertexALocation, oVertexBLocation); // Get the angle that defines the aspect ratio of vertex A's rectangle. Double dAspectAngle = Math.Atan2( dHalfVertexARectangleHeight, dHalfVertexARectangleWidth); if (dEdgeAngle >= -dAspectAngle && dEdgeAngle < dAspectAngle) { // For a square, this is -45 degrees to 45 degrees. Debug.Assert(dVertexBX != dVertexAX); return new Point( dVertexAX + dHalfVertexARectangleWidth, dVertexAY + dHalfVertexARectangleWidth * ((dVertexBY - dVertexAY) / (dVertexBX - dVertexAX)) ); } if (dEdgeAngle >= dAspectAngle && dEdgeAngle < Math.PI - dAspectAngle) { // For a square, this is 45 degrees to 135 degrees. //Debug.Assert(dVertexBY != dVertexAY); return new Point( dVertexAX + dHalfVertexARectangleHeight * ((dVertexBX - dVertexAX) / (dVertexAY - dVertexBY)), dVertexAY - dHalfVertexARectangleHeight ); } if (dEdgeAngle < -dAspectAngle && dEdgeAngle >= -Math.PI + dAspectAngle) { // For a square, this is -45 degrees to -135 degrees. Debug.Assert(dVertexBY != dVertexAY); return new Point( dVertexAX + dHalfVertexARectangleHeight * ((dVertexBX - dVertexAX) / (dVertexBY - dVertexAY)), dVertexAY + dHalfVertexARectangleHeight ); } // For a square, this is 135 degrees to 180 degrees and -135 degrees to // -180 degrees. Debug.Assert(dVertexAX != dVertexBX); return new Point( dVertexAX - dHalfVertexARectangleWidth, dVertexAY + dHalfVertexARectangleWidth * ((dVertexBY - dVertexAY) / (dVertexAX - dVertexBX)) );*/ var leftSide = Intersects(new Measure.Vector(oVertexALocation.X, oVertexALocation.Y), new Measure.Vector(oVertexBLocation.X, oVertexBLocation.Y), new Measure.Vector(oVertexARectangle.X, oVertexARectangle.Y), new Measure.Vector(oVertexARectangle.X, oVertexARectangle.Y + oVertexARectangle.Height)); var bottomSide = Intersects(new Measure.Vector(oVertexALocation.X, oVertexALocation.Y), new Measure.Vector(oVertexBLocation.X, oVertexBLocation.Y), new Measure.Vector(oVertexARectangle.X, oVertexARectangle.Y + oVertexARectangle.Height), new Measure.Vector(oVertexARectangle.X + oVertexARectangle.Width, oVertexARectangle.Y + oVertexARectangle.Height)); var rightSide = Intersects(new Measure.Vector(oVertexALocation.X, oVertexALocation.Y), new Measure.Vector(oVertexBLocation.X, oVertexBLocation.Y), new Measure.Vector(oVertexARectangle.X + oVertexARectangle.Width, oVertexARectangle.Y), new Measure.Vector(oVertexARectangle.X + oVertexARectangle.Width, oVertexARectangle.Y + oVertexARectangle.Height)); var topSide = Intersects(new Measure.Vector(oVertexALocation.X, oVertexALocation.Y), new Measure.Vector(oVertexBLocation.X, oVertexBLocation.Y), new Measure.Vector(oVertexARectangle.X, oVertexARectangle.Y), new Measure.Vector(oVertexARectangle.X + oVertexARectangle.Width, oVertexARectangle.Y)); var pt = new Point(oVertexALocation.X, oVertexALocation.Y); // Get the rectangle side where intersection of the proposed Edge path occurred. if (leftSide != null) pt = new Point(leftSide.Value.X, leftSide.Value.Y); else if (bottomSide != null) pt = new Point(bottomSide.Value.X, bottomSide.Value.Y); else if (rightSide != null) pt = new Point(rightSide.Value.X, rightSide.Value.Y); else if (topSide != null) pt = new Point(topSide.Value.X, topSide.Value.Y); return pt; } public static PathFigure GenerateOldArrow(Point ip1, Point ip2) { var p1 = new Vector(ip1.X, ip1.Y); var p2 = new Vector(ip2.X, ip2.Y); var v = p1 - p2; v = v / v.Length * 5; var n = new Measure.Vector(-v.Y, v.X) * 0.7; var ov1 = p2 + v - n; var ov2 = p2 + v + n; var fig = new PathFigure { StartPoint = ip2, Segments = new PathSegmentCollection { new LineSegment() {Point = new Point(ov1.X, ov1.Y)}, new LineSegment {Point = new Point(ov2.X, ov2.Y)} }, IsClosed = true }; //TryFreeze(fig); return fig; } public static PathFigure GenerateArrow(Point oArrowTipLocation, Point start, Point end, double customAngle = 0.1) { //Debug.Assert(dEdgeWidth > 0); // Compute the arrow's dimensions. The width factor is arbitrary and // was determined experimentally. //const Double WidthFactor = 1.5; var dArrowAngle = customAngle == 0.1 ? MathHelper.GetAngleBetweenPointsRadians(start, end) : customAngle; var dArrowTipX = oArrowTipLocation.X; var dArrowTipY = oArrowTipLocation.Y; var dArrowWidth = 3.0; var dArrowHalfHeight = dArrowWidth / 2.0; var dX = dArrowTipX - dArrowWidth; // Compute the arrow's three points as if the arrow were at an angle of // zero degrees, then use a rotated transform to adjust for the actual // specified angle. var aoPoints = new Point[] { // Index 0: Arrow tip. oArrowTipLocation, // Index 1: Arrow bottom. new Point(dX, dArrowTipY - dArrowHalfHeight), // Index 2: Arrow top. new Point(dX, dArrowTipY + dArrowHalfHeight), // Index 3: Center of the flat end of the arrow. // // Note: The 0.2 is to avoid a gap between the edge endcap and the // flat end of the arrow, but it sometimes causes the two to // overlap slightly, and that can show if the edge isn't opaque. // What is the correct way to get the endcap to merge invisibly // with the arrow? new Point(dX + 0.2, dArrowTipY) }; var oMatrix = GetRotatedMatrix(oArrowTipLocation, -MathHelper.RadiansToDegrees(dArrowAngle)); foreach (var item in aoPoints) oMatrix.Transform(item); //oMatrix.Transform(aoPoints); return GetPathFigureFromPoints(aoPoints[0], aoPoints[1], aoPoints[2]); } public static Matrix GetRotatedMatrix(Point centerOfRotation, Double angleToRotateDegrees) { var oMatrix = Matrix.Identity; oMatrix.RotateAt(angleToRotateDegrees, centerOfRotation.X, centerOfRotation.Y); return (oMatrix); } public static PathFigure GetPathFigureFromPoints(Point startPoint, params Point[] otherPoints) { var oPathFigure = new PathFigure() { StartPoint = startPoint }; var oPathSegmentCollection = new PathSegmentCollection(); foreach (var item in otherPoints) oPathSegmentCollection.Add(new LineSegment() { Point = item }); oPathFigure.Segments = oPathSegmentCollection; oPathFigure.IsClosed = true; return oPathFigure; } public static PathGeometry GetPathGeometryFromPoints(Point startPoint, params Point[] otherPoints) { Debug.Assert(otherPoints != null); var iOtherPoints = otherPoints.Length; Debug.Assert(iOtherPoints > 0); var oPathFigure = new PathFigure() { StartPoint = startPoint }; var oPathSegmentCollection = new PathSegmentCollection(); foreach (var item in otherPoints) oPathSegmentCollection.Add(new LineSegment() { Point = item }); oPathFigure.Segments = oPathSegmentCollection; oPathFigure.IsClosed = true; var oPathGeometry = new PathGeometry(); oPathGeometry.Figures.Add(oPathFigure); return (oPathGeometry); } } }