2

I'm trying to draw a (svg) arc connecting two rectangles. The catch is, that the arc should start at the border of the rectangles, not at the center.

To illustrate:

Connect two rectangles via an arc

I have the center points, width & height of the rectangles C1 w1 h1 C2 w2 h2 and the center and x and y radius of the arc D rx ry. So basically, for drawing the purple arc, i'm missing P1 and P2.

All values are dynamic and can change, so the algorithm needs be agnostic of rx and ry, width and height of the rectangles, how the rectangles are positioned relatively to each other, etc.

Taking the rounded corners into account would be the cherry on top. But that's not really necessary..

Willem Van Onsem
  • 443,496
  • 30
  • 428
  • 555
kulturfunker
  • 123
  • 9
  • Do you always want to paint the shorter arc? How do you decide which arc of the two you paint? – Willem Van Onsem Jun 25 '16 at 13:42
  • Furthermore are C1 and C2 always positioned on the arc, or is this just a coincidence? – Willem Van Onsem Jun 25 '16 at 13:44
  • @WillemVanOnsem I derive the arc from a third point `P3`, not drawn in the picture. `P3` is used to edit the arc, [as described here](http://stackoverflow.com/questions/22791951/algorithm-to-find-an-arc-its-center-radius-and-angles-given-3-points). And yes, the arc always goes through the center points `C1` and `C2`. – kulturfunker Jun 25 '16 at 13:47
  • Is there a reason you are not taking the simple approach and drawing opaque rectangles on top of the arc from C1 to C2? – Paul LeBeau Jun 25 '16 at 19:28
  • @PaulLeBeau Yes, i'm attaching arrows (and other additional information) at the start/end of the arc. – kulturfunker Jun 26 '16 at 09:03

2 Answers2

1

Let's center of ellipse be coordinate origin (if not, just shift rectangle coordinates by -D.X and -D.Y).

In this system ellipse equation is

 x^2/rx^2 + y^2/ry^2 = 1

Substitute rectangle edge coordinates in this equation and check if result actually belongs to rectangle. For example, right edge of top rectangle is X = C1'.X + w1. Find Y from ellipse equation and check it is in range C1'Y - h1 .. C1'Y + h1. If yes, P1 = (C1'.X + w1, CalculatedY)

MBo
  • 77,366
  • 5
  • 53
  • 86
  • Thanks! That idea was actually much simpler than i expected.. I'll check it out and see what i come up with. If it work's out well, i'll mark your answer as accepted. – kulturfunker Jun 26 '16 at 08:59
0

Alright, just for people who might stumble upon this in the future..

Here is what i came up with in javascript (ES6). But it should be easy to port this to other languages..

/**
 * Calculate the intersection of the border of the given rectangle
 * and a circular arc.
 * @param  {Object} rectangle    Rectangle object with dimensions and position properties.
 * @param  {Object} arc          Arc object with center and radius properties.
 * @return {Object}              Resulting intersection point, with x- and y-coordinates.
 */
calculateBorderPoint(rectangle, arc) {
  // Clone the rectangle, because we don't want to mutate the original.
  const a = Object.assign({}, rectangle.position);

  // Treat center of circle as coordinate origin.
  a.x -= arc.center.x;
  a.y -= arc.center.y;

  let points = [];

  // Check east & west with possible x values
  const possibleX = [
    a.x - rectangle.dimensions.width / 2,
    a.x + rectangle.dimensions.width / 2,
  ];
  possibleX.forEach((x) => {
    const ySquared = [
      Math.sqrt(Math.pow(arc.radius, 2) - Math.pow(x, 2)),
      -Math.sqrt(Math.pow(arc.radius, 2) - Math.pow(x, 2)),
    ];
    // Check if the derived y value is in range of rectangle
    ySquared.forEach((y) => {
      if (y >= a.y - rectangle.dimensions.height / 2 &&
          y <= a.y + rectangle.dimensions.height / 2) {
        points.push({x, y});
      }
    });
  });

  // Check north & south with possible y values
  const possibleY = [
    a.y - rectangle.dimensions.height / 2,
    a.y + rectangle.dimensions.height / 2,
  ];
  possibleY.forEach((y) => {
    const xSquared = [
      Math.sqrt(Math.pow(arc.radius, 2) - Math.pow(y, 2)),
      -Math.sqrt(Math.pow(arc.radius, 2) - Math.pow(y, 2)),
    ];
    // Check if the derived x value is in range of rectangle
    xSquared.forEach((x) => {
      if (x >= a.x - rectangle.dimensions.width / 2 &&
          x <= a.x + rectangle.dimensions.width / 2) {
        points.push({x, y});
      }
    });
  });

  // At this point you will propably have multiple possible solutions,
  // because the circle might intersect the rectangle at multiple points.
  // You need to select the point, that makes most sense in your case.
  // One solution would be to select the one closest to the other rectangle.

  // Translate it back.
  points[0].x += arc.center.x;
  points[0].y += arc.center.y;

  return points[0];
}

It's not pretty, but it works. I'm happy to hear any suggestions..

kulturfunker
  • 123
  • 9