using System; using System.Linq; using AIStudio.Wpf.DiagramDesigner.Geometrys; namespace AIStudio.Wpf.DiagramDesigner { public static partial class PathGenerators { public static PathGeneratorResult Smooth(IDiagramViewModel _, ConnectionViewModel link, PointBase[] route, PointBase source, PointBase target) { route = ConcatRouteAndSourceAndTarget(route, source, target); if (route.Length > 2) return CurveThroughPoints(route, link); route = GetRouteWithCurvePoints(link, route); double sourceAngle = SourceMarkerAdjustement(route, link.GetSourceMarkerWidth(), link.GetSourceMarkerHeight()); double targetAngle = TargetMarkerAdjustement(route, link.GetSinkMarkerWidth(), link.GetSinkMarkerHeight()); DoShift(route, link); var path = FormattableString.Invariant($"M {route[0].X} {route[0].Y} C {route[1].X} {route[1].Y}, {route[2].X} {route[2].Y}, {route[3].X} {route[3].Y}"); return new PathGeneratorResult(new[] { path }, sourceAngle, route[0], targetAngle, route[route.Length - 1]); } private static PathGeneratorResult CurveThroughPoints(PointBase[] route, ConnectionViewModel link) { double sourceAngle = SourceMarkerAdjustement(route, link.GetSourceMarkerWidth(), link.GetSourceMarkerHeight()); double targetAngle = TargetMarkerAdjustement(route, link.GetSinkMarkerWidth(), link.GetSinkMarkerHeight()); BezierSpline.GetCurveControlPoints(route, out var firstControlPoints, out var secondControlPoints); DoShift(route, link); DoShift(firstControlPoints, link); DoShift(secondControlPoints, link); var paths = new string[firstControlPoints.Length]; for (var i = 0; i < firstControlPoints.Length; i++) { var cp1 = firstControlPoints[i]; var cp2 = secondControlPoints[i]; paths[i] = FormattableString.Invariant($"M {route[i].X} {route[i].Y} C {cp1.X} {cp1.Y}, {cp2.X} {cp2.Y}, {route[i + 1].X} {route[i + 1].Y}"); } // Todo: adjust marker positions based on closest control points return new PathGeneratorResult(paths, sourceAngle, route[0], targetAngle, route[route.Length - 1]); } private static PointBase[] GetRouteWithCurvePoints(ConnectionViewModel link, PointBase[] route) { if (link.IsPortless) { if (Math.Abs(route[0].X - route[1].X) >= Math.Abs(route[0].Y - route[1].Y)) { var cX = (route[0].X + route[1].X) / 2; return new[] { route[0], new PointBase(cX, route[0].Y), new PointBase(cX, route[1].Y), route[1] }; } else { var cY = (route[0].Y + route[1].Y) / 2; return new[] { route[0], new PointBase(route[0].X, cY), new PointBase(route[1].X, cY), route[1] }; } } else { var cX = (route[0].X + route[1].X) / 2; var cY = (route[0].Y + route[1].Y) / 2; var sourceOrientation = link.SourceConnectorInfo?.Orientation; if (sourceOrientation == ConnectorOrientation.None)//按照线条的四象限来处理。 { var slope = (route[1].Y - route[0].Y) / (route[1].X - route[0].X); if (Math.Abs(slope) < link.SmoothAutoSlope) { if (route[1].X > route[0].X) { sourceOrientation = ConnectorOrientation.Right; } else { sourceOrientation = ConnectorOrientation.Left; } } else { if (route[1].Y > route[0].Y)//Y轴方向是反的 { sourceOrientation = ConnectorOrientation.Bottom; } else { sourceOrientation = ConnectorOrientation.Top; } } } var curvePointA = GetCurvePoint(route[0].X, route[0].Y, cX, cY, link.SmoothMargin, sourceOrientation); var curvePointB = GetCurvePoint(route[1].X, route[1].Y, cX, cY, link.SmoothMargin, link.SinkConnectorInfo?.Orientation); return new[] { route[0], curvePointA, curvePointB, route[1] }; } } private static PointBase GetCurvePoint(double pX, double pY, double cX, double cY, double smoothMargin, ConnectorOrientation? alignment) { var margin = Math.Min(smoothMargin, Math.Pow(Math.Pow(pX - cX, 2) + Math.Pow(pY - cY, 2), .5)); switch (alignment) { case ConnectorOrientation.Top: return new PointBase(pX, Math.Min(pY - margin, cY)); case ConnectorOrientation.Bottom: return new PointBase(pX, Math.Max(pY + margin, cY)); case ConnectorOrientation.TopRight: return new PointBase(Math.Max(pX + margin, cX), Math.Min(pY - margin, cY)); case ConnectorOrientation.BottomRight: return new PointBase(Math.Max(pX + margin, cX), Math.Max(pY + margin, cY)); case ConnectorOrientation.Right: return new PointBase(Math.Max(pX + margin, cX), pY); case ConnectorOrientation.Left: return new PointBase(Math.Min(pX - margin, cX), pY); case ConnectorOrientation.BottomLeft: return new PointBase(Math.Min(pX - margin, cX), Math.Max(pY + margin, cY)); case ConnectorOrientation.TopLeft: return new PointBase(Math.Min(pX - margin, cX), Math.Min(pY - margin, cY)); default: return new PointBase(cX, cY); }; } } }