зеркало из https://github.com/stride3d/GraphX.git
505 строки
20 KiB
C#
505 строки
20 KiB
C#
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
|
|
{
|
|
|
|
/// <summary>
|
|
/// Get Intersection point on a rectangular surface
|
|
/// </summary>
|
|
/// <param name="a1">a1 is line1 start</param>
|
|
/// <param name="a2">a2 is line1 end</param>
|
|
/// <param name="b1">b1 is line2 start</param>
|
|
/// <param name="b2">b2 is line2 end</param>
|
|
/// <returns></returns>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Generate PathGeometry object with curved Path using supplied route points
|
|
/// </summary>
|
|
/// <param name="points">Route points</param>
|
|
/// <param name="tension"></param>
|
|
/// <param name="tolerance"></param>
|
|
/// <returns></returns>
|
|
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);
|
|
}
|
|
}
|
|
}
|