2
\$\begingroup\$

I wrote a calculation that finds coordinates on an edge of a square, but it came out as a really long nested tangle of conditions. After some reduction and finding common pieces I arrived at this example:

  // targetX, targetY, sourceX, sourceY are provided as parameters
  // and are markers for the centers of the two locations connecting
  let tx;
  let ty;
  const dx = targetX - sourceX;
  const dy = targetY - sourceY;
  const adjY = (radius * Math.tan(Math.atan2(dy, dx)));
  const adjX = (radius / Math.tan(Math.atan2(dy, dx)));
  if (Math.abs(adjY) <= radius) { // A
    if (targetX <= sourceX) { // B
      tx2 = targetX + (Math.abs(adjX) <= radius ? adjX : radius);
      ty2 = targetY + (Math.abs(adjY) <= radius ? adjY : radius);
    } else {
      tx2 = targetX - (Math.abs(adjX) <= radius ? adjX : radius);
      ty2 = targetY - (Math.abs(adjY) <= radius ? adjY : radius);
    }
  } else if (targetY <= sourceY) { // C
    tx2 = targetX + (Math.abs(adjX) <= radius ? adjX : radius);
    ty2 = targetY + (Math.abs(adjY) <= radius ? adjY : radius);
  } else {
    tx2 = targetX - (Math.abs(adjX) <= radius ? adjX : radius);
    ty2 = targetY - (Math.abs(adjY) <= radius ? adjY : radius);
  }

Moving several of the conditions into the lines themselves was possible using ternary operators. But now I'm stuck, so how do I reduce the conditions even further?

I thought of using boolean logic gate reduction techniques, like Karnaugh maps, but fail to find a way to map that process to my problem.

Other solutions involving different math are welcome as well.

The problem is finding the new target X and Y so that they meet the edge of the target rectangle instead of its center.

diagram-of-the-problem

\$\endgroup\$
2
\$\begingroup\$

I think the solution is a little simplier

const dx = targetX - sourceX;
const dy = targetY - sourceY;
const scale = Math.hypot(dx, dy) / Math.hypot(radius,radius * (Math.abs(dy / dx) < 1 ? dy / dx : dx / dy));
const tx = targetX - dx / scale;
const ty = targetY - dy / scale;

If it is just the boundary of the box you are after. The only comparison is to find out which part of the box the line ends on, top/bottom or left/right the rest works by preserving dx and dy's sign. It also works if dx or dy is zero.

The only thing I am not sure of is if the offset is from the target, if so I would change to

const dx = sourceX - targetX;
const dy = sourceY - targetY;
const scale = Math.hypot(dx, dy) / Math.hypot(radius,radius * (Math.abs(dy / dx) < 1 ? dy / dx : dx / dy));
const tx = targetX + dx / scale;
const ty = targetY + dy / scale;
\$\endgroup\$
  • \$\begingroup\$ The first option (with the subtraction) works flawlessly! Thank you very much. A much more elegant solution than my own. \$\endgroup\$ – Evgeny Jan 21 '17 at 15:22
  • \$\begingroup\$ Went into working code at github.com/kesor/vue-ccpm/commit/a3f6b87b \$\endgroup\$ – Evgeny Jan 22 '17 at 4:11
1
\$\begingroup\$

What you need is to extract some values into variables.

First of all, you are always using Math.abs(adjX) <= radius ? adjX : radius for both X and Y so those can be extracted into changeX or changeY.

Next, you are either doing + or - which can be changed into using a multiplier that is either \$1\$ or \$-1\$.

And as a last step, the multiplier can be set using the conditional ternary operator.

Then what you end up with is this:

let tx;
let ty;
const dx = targetX - sourceX;
const dy = targetY - sourceY;
const adjY = (radius * Math.tan(Math.atan2(dy, dx)));
const adjX = (radius / Math.tan(Math.atan2(dy, dx)));

const changeX = Math.abs(adjX) <= radius ? adjX : radius;
const changeY = Math.abs(adjY) <= radius ? adjY : radius;
let multiplier;
if (Math.abs(adjY) <= radius) {
  multiplier = targetX <= sourceX ? 1 : -1;
} else {
  multiplier = targetY <= sourceY ? 1 : -1;
}
tx2 = targetX + multiplier * changeX;
ty2 = targetY + multiplier * changeY;
\$\endgroup\$
  • \$\begingroup\$ Need to change last lines to not mix arithmetic operators. Placing bracers around the multiplication. \$\endgroup\$ – Evgeny Jan 21 '17 at 12:22
  • \$\begingroup\$ @Evgeny Multiplication takes precedence before addition, so no braces needed. \$\endgroup\$ – Simon Forsberg Jan 21 '17 at 12:46
  • \$\begingroup\$ Yes it works. But a good practice, to avoid human error when changing code later, is not to mix arithmetic operators. \$\endgroup\$ – Evgeny Jan 21 '17 at 12:52
  • \$\begingroup\$ @Evgeny That's your preference, personally I don't worry about mixing addition and multiplication like that. If it makes you feel any better, you could do multiplier * changeX + targetX \$\endgroup\$ – Simon Forsberg Jan 21 '17 at 12:55

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.