/// <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> /// Implements <see cref="IOffsetBorderPoint.OffsetBorderPoint"/> /// </summary> protected PointD? OffsetBorderPoint(IGeometryHost geometryHost, PointD borderPoint, PointD outsidePoint, double offset, bool parallelVector) { double angle = GeometryUtility.CalculateRadiansRotationAngle(outsidePoint, borderPoint); // Get the sample point PointD samplePoint = borderPoint; samplePoint.Offset(-offset * Math.Sin(angle), offset * Math.Cos(angle)); // Figure out the slope, either parallel to the incoming line, or pointed at the outside point PointD slopeThrough = parallelVector ? borderPoint : samplePoint; // Translate the rectangle to the origin RectangleD bounds = geometryHost.GeometryBoundingBox; PointD hostCenter = bounds.Center; double hcx = hostCenter.X; double hcy = hostCenter.Y; samplePoint.Offset(-hcx, -hcy); borderPoint.Offset(-hcx, -hcy); double px = samplePoint.X; double py = samplePoint.Y; double solvedX; double solvedY; double halfWidth = bounds.Width / 2; double halfHeight = bounds.Height / 2; double r = Radius; if (VGConstants.FuzzEqual(slopeThrough.X, outsidePoint.X, VGConstants.FuzzDistance)) { double absX = Math.Abs(px); // Vertical line, hit the same edge as the border point if (absX > (halfWidth + VGConstants.FuzzDistance)) { // Line hits outside rectangle return null; } solvedX = px; solvedY = halfHeight; absX = halfWidth - absX; if (absX < r) { // We're on the rounded corner. Figure out how far down the circle we need to go. solvedY -= r - Math.Sqrt(absX * (r + r - absX)); } if (borderPoint.Y < 0) { solvedY = -solvedY; } } else if (VGConstants.FuzzEqual(slopeThrough.Y, outsidePoint.Y, VGConstants.FuzzDistance)) { double absY = Math.Abs(py); // Horizontal line, hit the same edge as the border point if (absY > (halfHeight + VGConstants.FuzzDistance)) { // Line hits outside rectangle return null; } solvedY = py; solvedX = halfWidth; absY = halfHeight - absY; if (absY < r) { // We're on the rounded corner. Figure out how far down the circle we need to go. solvedX -= r - Math.Sqrt(absY * (r + r - absY)); } if (borderPoint.X < 0) { solvedX = -solvedX; } } else { int hitCount = 0; solvedX = 0; solvedY = 0; double solvedXAlternate = 0; double solvedYAlternate = 0; PointD? corner; // Use for corner tracking (both the center and the hit points) CornerQuadrant quadrant = 0; // We've already checked vertical and horizontal lines, so we know the lines will intersect either // zero or two sides of the rectangle. Find the two sides. // The intersecting line equation is y = m(x - px) + py (solved for y) or x = 1/m(y-py) + px (solved for x) // The rectangle borders are y = halfHeight, y = -halfHeight, x = halfWidth, x = -halfWidth double slope = (outsidePoint.Y - slopeThrough.Y) / (outsidePoint.X - slopeThrough.X); double inverseSlope = 1 / slope; double testIntersect; // Bottom edge testIntersect = inverseSlope * (halfHeight - py) + px; if (Math.Abs(testIntersect) < (halfWidth + VGConstants.FuzzDistance)) { solvedX = testIntersect; solvedY = halfHeight; corner = null; if (solvedX > 0) { if (r > (halfWidth - solvedX)) { corner = new PointD(halfWidth - r, halfHeight - r); quadrant = CornerQuadrant.LowerRight; } } else if (r > (halfWidth + solvedX)) { corner = new PointD(-halfWidth + r, halfHeight - r); quadrant = CornerQuadrant.LowerLeft; } if (corner.HasValue) { corner = FindCornerHit(corner.Value, samplePoint, slope, r, quadrant); if (!corner.HasValue) { return null; } solvedX = corner.Value.X; solvedY = corner.Value.Y; } hitCount = 1; } // Top edge testIntersect = inverseSlope * (-halfHeight - py) + px; if (Math.Abs(testIntersect) < (halfWidth + VGConstants.FuzzDistance)) { solvedXAlternate = testIntersect; solvedYAlternate = -halfHeight; corner = null; if (solvedXAlternate > 0) { if (r > (halfWidth - solvedXAlternate)) { corner = new PointD(halfWidth - r, -halfHeight + r); quadrant = CornerQuadrant.UpperRight; } } else if (r > (halfWidth + solvedXAlternate)) { corner = new PointD(-halfWidth + r, -halfHeight + r); quadrant = CornerQuadrant.UpperLeft; } if (corner.HasValue) { corner = FindCornerHit(corner.Value, samplePoint, slope, r, quadrant); if (!corner.HasValue) { return null; } solvedXAlternate = corner.Value.X; solvedYAlternate = corner.Value.Y; } if (hitCount == 0) { solvedX = solvedXAlternate; solvedY = solvedYAlternate; } ++hitCount; } // Right edge if (hitCount != 2) { testIntersect = slope * (halfWidth - px) + py; if (Math.Abs(testIntersect) < (halfHeight + VGConstants.FuzzDistance)) { solvedYAlternate = testIntersect; solvedXAlternate = halfWidth; corner = null; if (solvedYAlternate > 0) { if (r > (halfHeight - solvedYAlternate)) { corner = new PointD(halfWidth - r, halfHeight - r); quadrant = CornerQuadrant.LowerRight; } } else if (r > (halfHeight + solvedYAlternate)) { corner = new PointD(halfWidth - r, -halfHeight + r); quadrant = CornerQuadrant.UpperRight; } if (corner.HasValue) { corner = FindCornerHit(corner.Value, samplePoint, slope, r, quadrant); if (!corner.HasValue) { return null; } solvedXAlternate = corner.Value.X; solvedYAlternate = corner.Value.Y; } if (hitCount == 0) { solvedX = solvedXAlternate; solvedY = solvedYAlternate; } ++hitCount; } } // Left edge if (hitCount == 1) { testIntersect = slope * (-halfWidth - px) + py; if (Math.Abs(testIntersect) < (halfHeight + VGConstants.FuzzDistance)) { solvedYAlternate = testIntersect; solvedXAlternate = -halfWidth; corner = null; if (solvedYAlternate > 0) { if (r > (halfHeight - solvedYAlternate)) { corner = new PointD(-halfWidth + r, halfHeight - r); quadrant = CornerQuadrant.LowerLeft; } } else if (r > (halfHeight + solvedYAlternate)) { corner = new PointD(-halfWidth + r, -halfHeight + r); quadrant = CornerQuadrant.UpperLeft; } if (corner.HasValue) { corner = FindCornerHit(corner.Value, samplePoint, slope, r, quadrant); if (!corner.HasValue) { return null; } solvedXAlternate = corner.Value.X; solvedYAlternate = corner.Value.Y; } ++hitCount; } } // Choose the best match and translate the point back out if (hitCount == 2) { // Find the point closest to the sample point double xDif = px - solvedX; double yDif = py - solvedY; double xDifAlternate = px - solvedXAlternate; double yDifAlternate = py - solvedYAlternate; if ((xDif * xDif + yDif * yDif) > (xDifAlternate * xDifAlternate + yDifAlternate * yDifAlternate)) { solvedX = solvedXAlternate; solvedY = solvedYAlternate; } } else { // Unsolvable return null; } } return new PointD(solvedX + hcx, solvedY + hcy); }
/// <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> /// 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); } }