/// <summary> /// Implement shape folding on the ellipse boundary /// </summary> /// <param name="geometryHost">The host view</param> /// <param name="potentialPoint">A point on the rectangular boundary of the shape</param> /// <param name="vectorEndPoint">A point on the opposite end of the connecting line</param> /// <returns>A point on the ellipse border</returns> public override PointD DoFoldToShape(IGeometryHost geometryHost, PointD potentialPoint, PointD vectorEndPoint) { // Get an endpoint we can work with NodeShape oppositeShape; vectorEndPoint = GeometryUtility.AdjustVectorEndPoint(geometryHost, vectorEndPoint, out oppositeShape); PointD? customPoint = GeometryUtility.DoCustomFoldShape(geometryHost, vectorEndPoint, oppositeShape); if (customPoint.HasValue) { return customPoint.Value; } vectorEndPoint = GeometryUtility.ResolveProxyConnectorVectorEndPoint(vectorEndPoint, oppositeShape); // The point returned needs to be relative to the upper left corner of the bounding // box. The goal is to get a point on the circle that points to the center of the // line. To do this, we translate the coordinate system to the center of the circle, // get a slope from the vectorEndPoint/circle center, then solve the circle equation // and retranslate the coordinates back out. // The quadrant we're coming in from can be determined by the position of the vectorEndPoint // relative to the ellipse center. // // The pertinent equations are: // vectorEndPoint (relative point) = (xe, ye) // center = (xc, yc) // circle radius = r // slope = m = (ye - yc)/(xe - xc) // line equation: y = mx // circle equation (centered at origin): x^2 + y^2 = r^2 // solving gives us: x = +/-(r/sqrt(1 + m^2)) // Plugging back into the line equation gives us a +/- y value // Final point = (xc, yc) + (x, y) // The quadrant is determined by the relative position of the vectorEndPoint RectangleD box = geometryHost.TranslateGeometryToAbsoluteBounds(geometryHost.GeometryBoundingBox); PointD boxCenter = box.Center; double radius = Math.Min(box.Width / 2, box.Height / 2); if (VGConstants.FuzzEqual(vectorEndPoint.X, boxCenter.X, VGConstants.FuzzDistance)) { return new PointD(box.Width / 2, (vectorEndPoint.Y < boxCenter.Y) ? 0 : box.Height); } else if (VGConstants.FuzzEqual(vectorEndPoint.Y, boxCenter.Y, VGConstants.FuzzDistance)) { return new PointD((vectorEndPoint.X < boxCenter.X) ? 0 : box.Width, box.Height / 2); } else { bool negativeX = vectorEndPoint.X < boxCenter.X; bool negativeY = vectorEndPoint.Y < boxCenter.Y; double slope = (vectorEndPoint.Y - boxCenter.Y) / (vectorEndPoint.X - boxCenter.X); double x = radius / Math.Sqrt(1 + slope * slope); double y = slope * x; x = Math.Abs(x); y = Math.Abs(y); if (negativeX) { x = -x; if (negativeY) { y = -y; } } else if (negativeY) { y = -y; } // Return a point relative to the shape return new PointD(x + radius, y + radius); } }
/// <summary> /// Provide custom shape folding for rectangular fact types /// </summary> /// <param name="geometryHost">The host view</param> /// <param name="potentialPoint">A point on the rectangular boundary of the shape</param> /// <param name="vectorEndPoint">A point on the opposite end of the connecting line</param> /// <returns>A point on the rectangle edge border</returns> public override PointD DoFoldToShape(IGeometryHost geometryHost, PointD potentialPoint, PointD vectorEndPoint) { NodeShape oppositeShape; vectorEndPoint = GeometryUtility.AdjustVectorEndPoint(geometryHost, vectorEndPoint, out oppositeShape); PointD? customPoint = GeometryUtility.DoCustomFoldShape(geometryHost, vectorEndPoint, oppositeShape); if (customPoint.HasValue) { return customPoint.Value; } vectorEndPoint = GeometryUtility.ResolveProxyConnectorVectorEndPoint(vectorEndPoint, oppositeShape); // Fold to the shape // This is used for center to center routing, so the potential point is the // center of the shape. We need to see where a line through the center intersects // the rectangle border and return relative coordinates. RectangleD bounds = geometryHost.TranslateGeometryToAbsoluteBounds(geometryHost.GeometryBoundingBox); PointD center = bounds.Center; vectorEndPoint.Offset(-center.X, -center.Y); bool negativeX = vectorEndPoint.X < 0; bool negativeY = vectorEndPoint.Y < 0; if (VGConstants.FuzzZero(vectorEndPoint.X, VGConstants.FuzzDistance)) { // Vertical line, skip slope calculations return new PointD(bounds.Width / 2, negativeY ? 0 : bounds.Height); } else if (VGConstants.FuzzZero(vectorEndPoint.Y, VGConstants.FuzzDistance)) { // Horizontal line, skip slope calculations return new PointD(negativeX ? 0 : bounds.Width, bounds.Height / 2); } else { double slope = vectorEndPoint.Y / vectorEndPoint.X; // The intersecting line equation is y = mx. We can tell // whether to use the vertical or horizontal lines by // comparing the relative sizes of the rectangle sides // with the slope double x; double y; if (Math.Abs(slope) < (bounds.Height / bounds.Width)) { // Attach to left/right edges // Intersect with line x = +/- bounds.Width / 2 x = bounds.Width / 2; if (negativeX) { x = -x; } y = x * slope; } else { // Attach to top/bottom edges // Intersect with line y = +/- bounds.Height / 2 y = bounds.Height / 2; if (negativeY) { y = -y; } x = y / slope; } return new PointD(x + bounds.Width / 2, y + bounds.Height / 2); } }
/// <summary> /// A utility function to attempt to perfom custom shape folding. /// Custom shape folding uses shape-specific information beyond that /// available to the ShapeGeometry to determine connection points. /// This function should be call after AdjustVectorEndPoint. /// </summary> /// <param name="geometryHost">The geometryHost value passed to DoFoldToShape</param> /// <param name="vectorEndPoint">The vectorEndPoint passed to DoFoldToShape and adjusted by <see cref="AdjustVectorEndPoint(IGeometryHost,PointD,out NodeShape)"/></param> /// <param name="oppositeShape">The opposite shape returned by <see cref="AdjustVectorEndPoint(IGeometryHost,PointD,out NodeShape)"/></param> /// <returns>Nullable<PointD> with a value on success, without a value if custom folding is not available</returns> public static PointD? DoCustomFoldShape(IGeometryHost geometryHost, PointD vectorEndPoint, NodeShape oppositeShape) { ICustomShapeFolding customFolding; IProxyConnectorShape proxyConnector; PointD customPoint; NodeShape realHost; if (oppositeShape != null && null != (customFolding = geometryHost as ICustomShapeFolding) && !(customPoint = customFolding.CalculateConnectionPoint(oppositeShape)).IsEmpty) { // Translate back to local coordinates PointD location = geometryHost.TranslateGeometryToAbsoluteBounds(geometryHost.GeometryBoundingBox).Location; customPoint.Offset(-location.X, -location.Y); return customPoint; } else if (null != (proxyConnector = geometryHost as IProxyConnectorShape) && null != (realHost = proxyConnector.ProxyConnectorShapeFor as NodeShape)) { SizeD size = realHost.Size; customPoint = realHost.ShapeGeometry.DoFoldToShape(new GeometryHostWrapper(realHost), new PointD(size.Width / 2, size.Height / 2), GeometryUtility.VectorEndPointForBase(realHost, vectorEndPoint)); PointD location = geometryHost.TranslateGeometryToAbsoluteBounds(geometryHost.GeometryBoundingBox).Location; PointD realLocation = realHost.AbsoluteBounds.Location; customPoint.Offset(realLocation.X - location.X, realLocation.Y - location.Y); return customPoint; } return null; }
/// <summary> /// Adjust a vector end point retrieved from AdjustVectorEndPoint into /// the value in its original (very strange) coordinate system. /// </summary> /// <param name="geometryHost">The geometryHost passed to FoldToShape</param> /// <param name="vectorEndPoint">An adjusted vector end point</param> /// <returns>An unadjusted value</returns> public static PointD VectorEndPointForBase(IGeometryHost geometryHost, PointD vectorEndPoint) { RectangleD absoluteBoundingBox = geometryHost.TranslateGeometryToAbsoluteBounds(geometryHost.GeometryBoundingBox); #if VISUALSTUDIO_10_0 PointD absoluteCenter = absoluteBoundingBox.Center; return new PointD(absoluteCenter.X - vectorEndPoint.X, absoluteCenter.X - vectorEndPoint.Y); #else return new PointD(absoluteBoundingBox.Right - vectorEndPoint.X, absoluteBoundingBox.Bottom - vectorEndPoint.Y); #endif }
/// <summary> /// Implement shape folding on the triangle boundary /// </summary> /// <param name="geometryHost">The host view</param> /// <param name="potentialPoint">A point on the rectangular boundary of the shape</param> /// <param name="vectorEndPoint">A point on the opposite end of the connecting line</param> /// <returns>A point on the triangular border</returns> public override PointD DoFoldToShape(IGeometryHost geometryHost, PointD potentialPoint, PointD vectorEndPoint) { // Get an endpoint we can work with NodeShape oppositeShape; vectorEndPoint = GeometryUtility.AdjustVectorEndPoint(geometryHost, vectorEndPoint, out oppositeShape); PointD? customPoint = GeometryUtility.DoCustomFoldShape(geometryHost, vectorEndPoint, oppositeShape); if (customPoint.HasValue) { return customPoint.Value; } vectorEndPoint = GeometryUtility.ResolveProxyConnectorVectorEndPoint(vectorEndPoint, oppositeShape); RectangleD bounds = geometryHost.TranslateGeometryToAbsoluteBounds(geometryHost.GeometryBoundingBox); PointD center = bounds.Center; PointD[] trianglePoints = GeometryUtility.GetTrianglePointsD(bounds); double offsetByX = -center.X; double offsetByY = -center.Y; for (int i = 0; i < trianglePoints.Length; ++i) { trianglePoints[i].Offset(offsetByX, offsetByY); } vectorEndPoint.Offset(offsetByX, offsetByY); bool negativeX = vectorEndPoint.X < 0; bool negativeY = vectorEndPoint.Y < 0; double halfWidth = bounds.Width / 2; double halfHeight = bounds.Height / 2; if (VGConstants.FuzzZero(vectorEndPoint.X, VGConstants.FuzzDistance)) { // Vertical line, skip slope calculations return new PointD(halfWidth, trianglePoints[negativeY ? 0 : 1].Y + halfHeight); } else if (VGConstants.FuzzZero(vectorEndPoint.Y, VGConstants.FuzzDistance)) { // Horizontal line, skip slope calculations // Solve two-point form of line between triangle points for x with y = 0. PointD topPoint = trianglePoints[0]; PointD bottomPoint = trianglePoints[negativeX ? 1 : 2]; return new PointD(topPoint.X - topPoint.Y * (bottomPoint.X - topPoint.X)/(bottomPoint.Y - topPoint.Y) + halfWidth, halfHeight); } else { double slope = vectorEndPoint.Y / vectorEndPoint.X; double x; double y; if (negativeY || (Math.Abs(slope) < (halfHeight / halfWidth))) // Reasonable, but allows y to spill below the bottom { // Try to attach to the left/right lines PointD topPoint = trianglePoints[0]; PointD bottomPoint = trianglePoints[negativeX ? 1 : 2]; double inverseTriangleSlope = (bottomPoint.X - topPoint.X) / (bottomPoint.Y - topPoint.Y); x = (topPoint.X - topPoint.Y * inverseTriangleSlope) / (1 - slope * inverseTriangleSlope); y = slope * x; if (y > bottomPoint.Y) { // Adjust for a y below the bottom point. y = bottomPoint.Y; x = y / slope; } } else { // Attach to the bottom edge y = trianglePoints[1].Y; x = y / slope; } return new PointD(x + halfWidth, y + halfHeight); } }
/// <summary> /// Locate the opposite shape based on the given points and /// adjust the opposite the endpoint accordingly. The endpoint /// is also modified to represent an absolute value. Use VectorEndPointForBase /// to restore the vector endpoint to its natural value. /// CenterToCenter routing is assumed. /// </summary> /// <param name="geometryHost">IGeometryHost (passed from DoFoldToShape)</param> /// <param name="vectorEndPoint">PointD (passed from DoFoldToShape)</param> /// <param name="oppositeShape">The located opposite shape at this location</param> /// <returns>Absolute location of end point</returns> public static PointD AdjustVectorEndPoint(IGeometryHost geometryHost, PointD vectorEndPoint, out NodeShape oppositeShape) { oppositeShape = null; // The vectorEndPoint value is coming in (negative, negative) for the lower // right quadrant instead of (positive, positive). All other values are // (positive, positive), so we switch the end point to make the rest of the work // easier. For CenterToCenter routing, subtracting the vectorEndPoint from the // lower right corner gives the correct value. RectangleD absoluteBoundingBox = geometryHost.TranslateGeometryToAbsoluteBounds(geometryHost.GeometryBoundingBox); #if VISUALSTUDIO_10_0 PointD absoluteCenter = absoluteBoundingBox.Center; vectorEndPoint = new PointD(absoluteCenter.X - vectorEndPoint.X, absoluteCenter.Y - vectorEndPoint.Y); #else vectorEndPoint = new PointD(absoluteBoundingBox.Right - vectorEndPoint.X, absoluteBoundingBox.Bottom - vectorEndPoint.Y); #endif NodeShape shape = geometryHost as NodeShape; if (shape != null) { ReadOnlyCollection<LinkConnectsToNode> links = DomainRoleInfo.GetElementLinks<LinkConnectsToNode>(shape, LinkConnectsToNode.NodesDomainRoleId); int linksCount = links.Count; for (int i = 0; i < linksCount; ++i) { LinkConnectsToNode link = links[i]; BinaryLinkShape linkShape = link.Link as BinaryLinkShape; if (link != null) { // Get the opposite shape NodeShape testShape; if (linkShape.FromLinkConnectsToNode == link) { testShape = linkShape.ToShape; } else { testShape = linkShape.FromShape; } PointD shapeCenter = testShape.AbsoluteCenter; if (VGConstants.FuzzEqual(shapeCenter.X, vectorEndPoint.X, VGConstants.FuzzDistance) && VGConstants.FuzzEqual(shapeCenter.Y, vectorEndPoint.Y, VGConstants.FuzzDistance)) { oppositeShape = testShape; ICustomShapeFolding customFolding = testShape as ICustomShapeFolding; if (customFolding != null) { PointD customEndPoint = customFolding.CalculateConnectionPoint(shape); if (!customEndPoint.IsEmpty) { vectorEndPoint = customEndPoint; } } break; } } } } return vectorEndPoint; }
/// <summary> /// Provide custom shape folding for rectangular fact types /// </summary> /// <param name="geometryHost">The host view</param> /// <param name="potentialPoint">A point on the rectangular boundary of the shape</param> /// <param name="vectorEndPoint">A point on the opposite end of the connecting line</param> /// <returns>A point on the rounded rectangle border</returns> public override PointD DoFoldToShape(IGeometryHost geometryHost, PointD potentialPoint, PointD vectorEndPoint) { // Get an endpoint we can work with NodeShape oppositeShape; vectorEndPoint = GeometryUtility.AdjustVectorEndPoint(geometryHost, vectorEndPoint, out oppositeShape); PointD? customPoint = GeometryUtility.DoCustomFoldShape(geometryHost, vectorEndPoint, oppositeShape); if (customPoint.HasValue) { return customPoint.Value; } vectorEndPoint = GeometryUtility.ResolveProxyConnectorVectorEndPoint(vectorEndPoint, oppositeShape); // This is used for center to center routing, so the potential point is the // center of the shape. We need to see where a line through the center intersects // the rectangle border and return relative coordinates. RectangleD bounds = geometryHost.TranslateGeometryToAbsoluteBounds(geometryHost.GeometryBoundingBox); PointD center = bounds.Center; vectorEndPoint.Offset(-center.X, -center.Y); bool negativeX = vectorEndPoint.X < 0; bool negativeY = vectorEndPoint.Y < 0; if (VGConstants.FuzzZero(vectorEndPoint.X, VGConstants.FuzzDistance)) { // Vertical line, skip slope calculations return new PointD(bounds.Width / 2, negativeY ? 0 : bounds.Height); } else if (VGConstants.FuzzZero(vectorEndPoint.Y, VGConstants.FuzzDistance)) { // Horizontal line, skip slope calculations return new PointD(negativeX ? 0 : bounds.Width, bounds.Height / 2); } else { double slope = vectorEndPoint.Y / vectorEndPoint.X; // The intersecting line equation is y = mx. We can tell // whether to use the vertical or horizontal lines by // comparing the relative sizes of the rectangle sides // with the slope double x; double y; double r = Radius; double halfHeight = bounds.Height / 2; double halfWidth = bounds.Width / 2; bool cornerHit; if (Math.Abs(slope) < (bounds.Height / bounds.Width)) { // Attach to left/right edges // Intersect with line x = +/- bounds.Width / 2 x = halfWidth; if (negativeX) { x = -x; } y = x * slope; cornerHit = Math.Abs(y) > (halfHeight - r); } else { // Attach to top/bottom edges // Intersect with line y = +/- bounds.Height / 2 y = halfHeight; if (negativeY) { y = -y; } x = y / slope; cornerHit = Math.Abs(x) > (halfWidth - r); } if (cornerHit) { // The equation here is significantly more complicated than // other shapes because of the off center circle, which is // centered at (ccx, ccy) in these equations. The raw equations are: // (x - ccx)^2 + (y - ccy)^2 = r^2 // y = m*x where m is the slope // Solving for x gives (algebra is non-trivial and ommitted): // v1 = 1 + m*m // v2 = m*ccx + ccy // v3 = sqrt(r^2*v1 - v2^2) // x = (+/-v3 + ccx-m*ccy)/v1 // Note that picking the correct center is absolutely necessary. // Unlike the other shapes, where all lines will pass the ellipse/circle // at some point, the off-center circle means that choosing the wrong // center will result in an unsolvable equation (taking the square // root will throw). double ccx = halfWidth - r; // Corner center x value double ccy = halfHeight - r; // Corner center y value bool useNegativeSquareRoot = false; if (negativeX) // Left quadrants { ccx = -ccx; useNegativeSquareRoot = true; if (!negativeY) { // Lower left quadrant ccy = -ccy; } } else if (!negativeY) // Right quadrants { // Lower right quadrant ccy = -ccy; } double v1 = 1 + slope * slope; double v2 = slope * ccx + ccy; double v3 = Math.Sqrt(r * r * v1 - v2 * v2); if (useNegativeSquareRoot) { v3 = -v3; } x = (v3 + ccx - slope * ccy) / v1; y = slope * x; } return new PointD(x + halfWidth, y + halfHeight); } }